在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     //条形图之间的距离
    };

效果:
效果

源代码:

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;

猜你喜欢

转载自blog.csdn.net/zhy13087344578/article/details/80434530
今日推荐