Angular2使用SVG自定义图表(条形图、折线图)组件示例


Posted in Javascript onMay 10, 2019

本文实例讲述了Angular2使用SVG自定义图表(条形图、折线图)组件。分享给大家供大家参考,具体如下:

要求:用户将数据作为参数传进来,通过类型决定渲染何种类型的图标。

demo:

html:

<ngo-chart [inputParams]="options"></ngo-chart>

ts:

options = {
    type: 'line',   //图表类型
    xAxis: {      //X轴的数据
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    yAxis: {      //X轴的数据
      data: [120, 220, 150, 111, -150, 55, 60],
    },
    width: 600,    //宽
    height: 500,    //高
    dataPadding: 8   //条形图之间的距离
  };

效果:

Angular2使用SVG自定义图表(条形图、折线图)组件示例

源代码:

import {
  Input,
  OnInit,
  ViewChild,
  Component,
  ViewEncapsulation,
  ElementRef,
  AfterViewInit,
  ChangeDetectorRef,
} from '@angular/core';
import { NgoChartSvgParams, Scale, Axis, Chart } from './chart-svg-params';
@Component({
  selector: 'ngo-chart-svg',
  templateUrl: './chart-svg.html',
  styleUrls: ['./chart-svg.scss'],
  encapsulation: ViewEncapsulation.Native
})
export class NgoChartSvg implements OnInit, AfterViewInit {
  @Input() inputParams: NgoChartSvgParams;
  @ViewChild('svg') svg: ElementRef;
  @ViewChild('polyline') polyline: ElementRef;
  params: NgoChartSvgParams;
  AxisY: Axis; // Y轴
  AxisX: Axis; // X轴
  valueToPxRatio: number; // 值转px的比率
  Y0: number; // 坐标轴 (0,0)的Y轴
  Yscale: Array<Scale> = []; // Y轴刻度值
  Xscale: Array<Scale> = []; // X轴刻度值
  XgapWidth: number; // X轴刻度之间的间隙宽度
  data: Array<Chart> = [];
  color: string;
  type: string;
  polyLinePoints: string;
  polyLineLength: number;
  constructor(
    private ele: ElementRef,
    private cd: ChangeDetectorRef
  ) { }
  ...
 ngOnInit() {
    this.initParams();
    const svg = this.svg.nativeElement;
    const _width = this.params.width;
    const _height = this.params.height;
    svg.setAttribute('width', _width);
    svg.setAttribute('height', _height);
    // 绘制 y轴
    this.drawAxisY();
    this.drawScaleY();
    // 绘制 x轴
    this.drawAxisX();
    this.drawScaleX();
    this.drawRect();
    if (this.params.type === 'line') {
      this.drawLine();
    }
  }
  ngAfterViewInit() {
    if (this.polyline) {
      this.polyLineLength = this.polyline.nativeElement.getTotalLength();
      this.cd.detectChanges();
    }
  }
}

html

<svg #svg>
  <!-- Y轴 -->
  <g>
    <line [attr.x1]="AxisY.x1" [attr.y1]="AxisY.y1+15" [attr.x2]="AxisY.x2" [attr.y2]="AxisY.y2" [attr.stroke]="color" [attr.fill]="color"
      style="stroke-width:3" />
    <polygon [attr.points]="AxisY.arrow" />
    <ng-container *ngFor="let scale of Yscale">
      <line class="dash" [attr.x1]="scale.x1" [attr.x2]="scale.x2" [attr.y1]="scale.y1" [attr.y2]="scale.y2" stroke="rgba(0,0,0,0.3)"
      />
      <text class="_label" [attr.x]="scale.x1-5" style="text-anchor: end" [attr.y]="scale.y1" [attr.fill]="color" [attr.fill]="color">{{scale.label}}</text>
    </ng-container>
  </g>
  <!-- X轴 -->
  <g>
    <line [attr.x1]="AxisX.x1-15" [attr.x2]="AxisX.x2" [attr.y1]="AxisX.y1" [attr.y2]="AxisX.y2" [attr.stroke]="color" [attr.fill]="color"
      style="stroke-width:3" />
    <polygon [attr.points]="AxisX.arrow" />
    <ng-container *ngFor="let scale of Xscale">
      <line [attr.x1]="scale.x1" [attr.x2]="scale.x2" [attr.y1]="scale.y1" [attr.y2]="scale.y2" [attr.stroke]="color" [attr.fill]="color"
        style="stroke-width:1" />
      <text class="_label" [attr.x]="scale.x1-XgapWidth/2" [attr.y]="AxisY.y1+15" [attr.fill]="color" style="text-anchor: middle;">{{scale.label}}</text>
    </ng-container>
  </g>
  <!-- 矩形 -->
  <ng-container *ngIf="type==='bar'">
    <text x="10" y="20" fill="red">bar</text>
    <g>
      <ng-container *ngFor="let item of data">
        <ng-container *ngIf="item.value<=0">
          <rect class="_rect" [attr.x]="item.x" [attr.y]="item.y" [attr.width]="item.w" [attr.height]="item.h" fill="color">
            <animate attributeName="height" [attr.from]="item.h*0.6" [attr.to]="item.h" begin="0s" dur="1.1s" />
          </rect>
          <text [attr.x]="item.x+item.w/2" [attr.y]="item.y+item.h-5" fill="white" style="text-anchor: middle;">{{item.value}}</text>
        </ng-container>
        <ng-container *ngIf="item.value>0">
          <rect [attr.x]="item.x" [attr.y]="item.y" [attr.width]="item.w" [attr.height]="item.h" fill="color">
            <animate attributeName="y" [attr.from]="item.y+item.h*0.4" [attr.to]="item.y" begin="0s" dur="1.1s" />
            <animate attributeName="height" [attr.from]="item.h*0.6" [attr.to]="item.h" begin="0s" dur="1.1s" />
          </rect>
          <text class="_label" [attr.x]="item.x+item.w/2" [attr.y]="item.y+18" fill="white" style="text-anchor: middle;">{{item.value}}
            <animate attributeName="opacity" from="0" to="1" begin="0s" dur="1.1s" />
          </text>
        </ng-container>
      </ng-container>
    </g>
  </ng-container>
  <!--折线 -->
  <ng-container *ngIf="type==='line'">
    <text x="10" y="20" fill="red">line</text>
    <g>
      <polyline #polyline class="_polyline" [attr.points]="polyLinePoints" fill="none" [attr.stroke]='color' [attr.stroke-dasharray]="polyLineLength"
        [attr.stroke-dashoffset]="polyLineLength" />
      <ng-container *ngFor="let item of data">
        <circle [attr.cx]="item.x+item.w/2" [attr.cy]="item.y" r="2" [attr.fill]="color" [attr.stroke]='color' />
        <text class="_label" [attr.x]="item.x+item.w/2" [attr.y]="item.y+20" fill="white" style="text-anchor: middle;">{{item.value}}
          <animate attributeName="opacity" from="0" to="1" begin="0s" dur="1.1s" />
        </text>
      </ng-container>
    </g>
  </ng-container>
</svg>

css

svg {
 background: rgba(0, 0, 0, 0.2);
 border: 1px solid black;
}
svg * {
 position: static;
 font-size: 16px;
}
._polyline {
 fill: none;
 animation: lineMove 1.5s ease-in-out forwards;
}
@keyframes lineMove {
 to {
  stroke-dashoffset: 0;
 }
}

一、初始化参数

//首先获取传入的参数
 @Input() inputParams;
//初始化
 const _params: NgoChartSvgParams = {
   xAxis: this.inputParams.xAxis,
   yAxis: this.inputParams.yAxis,
   type: this.inputParams.type ? this.inputParams.type : 'bar',
   width: this.inputParams.width ? this.inputParams.width : 700,
   height: this.inputParams.height ? this.inputParams.height : 500,
   dataPadding: this.inputParams.dataPadding !== undefined ? this.inputParams.dataPadding : 8,
   YscaleNo: this.inputParams.YscaleNo >= 3 ? this.inputParams.YscaleNo : 6,
};
this.color = 'black';
this.type = _params.type;
this.params = _params;

二:绘制坐标轴Y轴

const _height = this.params.height;
const _pad = this.params.padding;
const _arrow = _pad + ',' + (_pad - 5) + ' ' + (_pad - 6) + ',' + (_pad + 12) + ' ' + (_pad + 6) + ',' + (_pad + 12);
 this.AxisY = {
   x1: _pad,
   y1: _height - _pad,
   x2: _pad,
   y2: _pad,
   arrow: _arrow
};

三、绘制Y轴的刻度

const _height = this.params.height;
const _width = this.params.width;
// 显示label的边距
const _padding = this.params.padding;
const _Ydata = this.params.yAxis.data;
// 显示的刻度数
const _YscaleNo = this.params.YscaleNo;
const _dataMax = this.getMinAndMaxData(_Ydata).dataMax;
const _dataMin = this.getMinAndMaxData(_Ydata).dataMin;
let _YminValue;
let _YgapValue;
if (_dataMin < 0) {
   _YgapValue = Math.ceil((_dataMax - _dataMin) / (_YscaleNo) / 10) * 10;
   _YminValue = Math.floor(_dataMin / _YgapValue) * _YgapValue;
} else {
   _YgapValue = Math.ceil((_dataMax) / (_YscaleNo) / 10) * 10;
   _YminValue = 0;
}
// Y轴坐标点
const _y2 = this.AxisY.y2;
const _y1 = this.AxisY.y1;
const _x1 = this.AxisY.x1;
// Y轴刻度的间隙宽度
const _YgapWidth = (_y1 - _y2) / (this.params.YscaleNo);
this.valueToPxRatio = _YgapValue / _YgapWidth;
// 坐标轴(0,0)的Y轴坐标
const _Y0 = _y1 - Math.abs(_YminValue / this.valueToPxRatio);
this.Y0 = _Y0;
for (let i = 0; i < this.params.YscaleNo; i++) {
   const _obj: Scale = { x1: 0, x2: 0, y1: 0, y2: 0, label: '', value: 0 };
   _obj.x1 = _x1;
   _obj.y1 = _y1 - _YgapWidth * i;
   _obj.x2 = _x1 + _width - 2 * _padding;
   _obj.y2 = _y1 - _YgapWidth * i;
   _obj.label = _YminValue + _YgapValue * i;
   this.Yscale.push(_obj);
}

四、绘制X坐标轴

const _width = this.params.width;
// 显示label的边距
const _pad = this.params.padding;
const _x2 = _width - _pad;
const _y2 = this.Y0;
const _arrow = (_x2 + 5) + ',' + _y2 + ' ' + (_x2 - 10) + ',' + (_y2 - 6) + ' ' + (_x2 - 10) + ',' + (_y2 + 6);
this.AxisX = {
  x1: _pad,
  y1: _y2,
  x2: _x2,
  y2: _y2,
  arrow: _arrow
};

五、绘制X轴刻度

const _width = this.params.width;
const _Xdata = this.params.xAxis.data;
const _Ydata = this.params.yAxis.data;
const Y0 = this.Y0;
const _x1 = this.AxisX.x1;
const _x2 = this.AxisX.x2;
const XgapWidth = ((_x2 - _x1) / (this.params.xAxis.data.length + 1));
this.XgapWidth = XgapWidth;
for (let i = 0; i < _Xdata.length; i++) {
   const _obj: Scale = { x1: 0, x2: 0, y1: 0, y2: 0, value: 0, label: '' };
   _obj.y1 = Y0;
   _obj.y2 = Y0 + 5;
   _obj.label = _Xdata[i];
   _obj.value = _Ydata[i];
   _obj.x1 = _x1 + XgapWidth * (i + 1);
   _obj.x2 = _x1 + XgapWidth * (i + 1);
   this.Xscale.push(_obj);

六、绘制矩形

const _value = this.params.yAxis.data;
const _dataPadding = this.params.dataPadding;
const _XgapWidth = this.XgapWidth;
for (let i = 0; i < _value.length; i++) {
   const element = _value[i];
   const _obj: Chart = { x: 0, y: 0, w: 0, h: 0, value: 0 };
   _obj.w = _XgapWidth - 2 * _dataPadding;
   _obj.x = this.Xscale[i].x1 - _obj.w - _dataPadding;
   _obj.h = Math.abs(this.Xscale[i].value / this.valueToPxRatio);
   _obj.value = this.Xscale[i].value;
   if (this.Xscale[i].value >= 0) {
      _obj.y = this.Y0 - (this.Xscale[i].value) / this.valueToPxRatio;
   } else {
      _obj.y = this.Y0;
   }
      this.data.push(_obj);
   }
}

七、绘制折线

const _data = this.data;
let _str = '';
_data.forEach(ele => {
if (ele.value < 0) {
   ele.y = ele.y + ele.h;
}
   _str += (ele.x + ele.w / 2) + ',' + ele.y + ' ';
});
this.polyLinePoints = _str;

希望本文所述对大家AngularJS程序设计有所帮助。

Javascript 相关文章推荐
JS JavaScript获取Url参数,src属性参数
Mar 09 Javascript
学习面向对象之面向对象的基本概念:对象和其他基本要素
Nov 30 Javascript
jquery如何获取复选框的值
Dec 12 Javascript
js验证输入是否为手机号码或电话号码示例
Dec 30 Javascript
js全选实现和判断是否有复选框选中的方法
Feb 17 Javascript
AngularJS手动表单验证
Feb 01 Javascript
JS iFrame加载慢怎么解决
May 13 Javascript
原生js和jquery分别实现横向导航菜单效果
May 13 Javascript
AngularJS基础 ng-model 指令详解及示例代码
Aug 02 Javascript
jQuery Ztree行政地区树状展示(点击加载)
Nov 09 Javascript
javascript基于原型链的继承及call和apply函数用法分析
Dec 15 Javascript
Javascript新手入门之字符串拼接与变量的应用
Dec 03 Javascript
vue 实现搜索的结果页面支持全选与取消全选功能
May 10 #Javascript
Vue项目中配置pug解析支持
May 10 #Javascript
Angular2实现的秒表及改良版示例
May 10 #Javascript
node中IO以及定时器优先级详解
May 10 #Javascript
使用Node.js写一个代码生成器的方法步骤
May 10 #Javascript
Easyui 去除jquery-easui tab页div自带滚动条的方法
May 10 #jQuery
使用vue脚手架(vue-cli)搭建一个项目详解
May 09 #Javascript
You might like
在win7中搭建Linux+PHP 开发环境
2014/10/08 PHP
PHP中SESSION的注销与清除
2015/04/16 PHP
PHP命令行执行整合pathinfo模拟定时任务实例
2016/08/12 PHP
谈谈从phpinfo中能获取哪些值得注意的信息
2017/03/28 PHP
PHP简单验证码功能机制实例详解
2019/03/27 PHP
任意位置显示html菜单
2007/02/01 Javascript
各种常用浏览器getBoundingClientRect的解析
2009/05/21 Javascript
原生javascript实现图片轮播效果代码
2010/09/03 Javascript
Web前端设计模式  制作漂亮的弹出层
2010/10/29 Javascript
解决ExtJS在chrome或火狐中正常显示在ie中不显示的浏览器兼容问题
2013/01/11 Javascript
javascipt匹配单行和多行注释的正则表达式
2013/11/20 Javascript
通过JS来判断页面控件是否获取焦点
2014/01/03 Javascript
浅谈JavaScript function函数种类
2014/12/29 Javascript
js分页工具实例
2015/01/28 Javascript
jquery实现全选、反选、获得所有选中的checkbox
2020/09/13 Javascript
关于JavaScript作用域你想知道的一切
2016/02/04 Javascript
关于javascript中限定时间内防止按钮重复点击的思路详解
2016/08/16 Javascript
通过命令行创建vue项目的方法
2017/07/20 Javascript
Vue2.0中集成UEditor富文本编辑器的方法
2018/03/03 Javascript
jQuery中元素选择器(element)简单用法示例
2018/05/14 jQuery
vue仿element实现分页器效果
2018/09/13 Javascript
vue2 设置router-view默认路径的实例
2018/09/20 Javascript
axios封装,使用拦截器统一处理接口,超详细的教程(推荐)
2019/05/02 Javascript
浅谈layui使用模板引擎动态渲染元素要注意的问题
2019/09/14 Javascript
Vue循环中多个input绑定指定v-model实例
2020/08/31 Javascript
python基础教程之lambda表达式使用方法
2014/02/12 Python
Python实现抓取城市的PM2.5浓度和排名
2015/03/19 Python
WxPython实现无边框界面
2019/11/18 Python
西班牙英格列斯百货英国官网:El Corte Inglés英国
2017/10/30 全球购物
极度干燥澳大利亚官方网站:Superdry澳大利亚
2019/03/28 全球购物
好的自荐信包括什么内容
2013/11/07 职场文书
大学生旷课检讨书
2014/01/22 职场文书
运动会通讯稿50字
2014/01/30 职场文书
廉洁使者实施方案
2014/03/29 职场文书
《成长的天空》读后感3篇
2019/12/06 职场文书
react合成事件与原生事件的相关理解
2021/05/13 Javascript