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 相关文章推荐
5 cool javascript apps
Mar 24 Javascript
js中传递特殊字符(+,&amp;)的方法
Jan 16 Javascript
JavaScript中字符串分割函数split用法实例
Apr 07 Javascript
jquery+json实现数据二级联动的方法
Nov 28 Javascript
Bootstrap中表单控件状态(验证状态)
Aug 04 Javascript
javascript实现右下角广告框效果
Feb 01 Javascript
angular.fromJson与toJson方法用法示例
May 17 Javascript
详解ES6中的 Set Map 数据结构学习总结
Nov 06 Javascript
Vue将页面导出为图片或者PDF
Aug 17 Javascript
基于Vue 撸一个指令实现拖拽功能
Oct 09 Javascript
JavaScript 防抖和节流遇见的奇怪问题及解决
Nov 20 Javascript
JavaScript实现简单的音乐播放器
Aug 14 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
php下批量挂马和批量清马代码
2011/02/27 PHP
Laravel 5.5官方推荐的Nginx配置学习教程
2017/10/06 PHP
JavaScript 密码强度判断代码
2009/09/05 Javascript
JavaScript 函数调用规则
2009/09/14 Javascript
Javascript实现仿WebQQ界面的“浮云”兼容 IE7以上版本及FF
2011/04/27 Javascript
让你的CSS像Jquery一样做筛选的实现方法
2011/07/10 Javascript
ECMAScript 5中的属性描述符详解
2015/03/02 Javascript
nodejs实现OAuth2.0授权服务认证
2017/12/27 NodeJs
php中and 和 &amp;&amp;出坑指南
2018/07/13 Javascript
更优雅的微信小程序骨架屏实现详解
2019/08/07 Javascript
微信小程序点击view动态添加样式过程解析
2020/01/21 Javascript
jQuery实现鼠标放置名字上显示详细内容气泡提示框效果的方法分析
2020/04/04 jQuery
vue如何使用外部特殊字体的操作
2020/07/30 Javascript
微信小程序实现自定义动画弹框/提示框的方法实例
2020/11/06 Javascript
Vue实现todo应用的示例
2021/02/20 Vue.js
[02:48]DOTA2英雄基础教程 暗夜魔王
2013/12/12 DOTA
[01:46]DOTA2上海特锦赛小组赛英文解说KotlGuy采访
2016/02/27 DOTA
Opencv实现抠图背景图替换功能
2019/05/21 Python
Django之编辑时根据条件跳转回原页面的方法
2019/08/21 Python
python:目标检测模型预测准确度计算方式(基于IoU)
2020/01/18 Python
Python 实现使用空值进行赋值 None
2020/03/12 Python
python下对hsv颜色空间进行量化操作
2020/06/04 Python
护士的岗位职责
2013/12/04 职场文书
教师党性分析材料
2014/02/04 职场文书
《狐假虎威》教学反思
2014/02/07 职场文书
数控技校生自我鉴定
2014/03/02 职场文书
公司接待方案
2014/03/08 职场文书
班风口号
2014/06/18 职场文书
2014年化妆品销售工作总结
2014/12/01 职场文书
四年级小学生评语
2014/12/26 职场文书
实习协议书
2015/01/27 职场文书
只用20行Python代码实现屏幕录制功能
2021/06/02 Python
Python中Numpy和Matplotlib的基本使用指南
2021/11/02 Python
python flappy bird小游戏分步实现流程
2022/02/15 Python
Python可视化学习之seaborn调色盘
2022/02/24 Python
Python可视化神器pyecharts之绘制地理图表练习
2022/07/07 Python