abstruse
Version:
Abstruse CI
142 lines (115 loc) • 3.6 kB
text/typescript
import { Component, ElementRef, OnChanges, OnDestroy, SimpleChanges, Input } from '@angular/core';
import { select, pie, arc, interpolate, easeCubic } from 'd3';
export class AppProgressChartComponent implements OnChanges, OnDestroy {
percent: number;
el: HTMLElement;
pathChart: any;
middleCount: any;
arcLine: any;
ratio: number;
lastCount: number;
constructor(private elementRef: ElementRef) {
this.lastCount = 0;
}
ngOnChanges(changes: SimpleChanges) {
if ('percent' in changes) {
if (!this.el) {
this.render();
}
this.animate();
}
}
ngOnDestroy() { }
render(): void {
this.el = this.elementRef.nativeElement.querySelector('.progress-chart');
this.ratio = this.percent / 100;
const p = pie().value((d: any) => d).sort(null);
const w = this.el.clientWidth;
const h = this.el.clientHeight;
const outerRadius = (w / 2) - 10;
const innerRadius = 100;
const color = ['#5AD946', '#2BB415', '#E2E7EE'];
const svg = select(this.el)
.append('svg')
.attr('width', w)
.attr('height', h);
const g = svg
.append('g')
.attr('transform', `translate(${w / 2}, ${h / 2})`);
this.createGradient(svg, color[0], color[1], 'progressGradient');
const a = arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0)
.endAngle(2 * Math.PI);
this.arcLine = arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0);
const pathBackground = g.append('path')
.attr('d', a)
.style('fill', color[2]);
this.pathChart = g.append('path')
.datum({ endAngle: 0 })
.attr('d', this.arcLine)
.style('fill', `url(${document.location.href}#progressGradient)`);
this.middleCount = g.append('text')
.text((d: any) => d)
.attr('class', 'middle-text')
.attr('text-anchor', 'middle')
.attr('dy', 30)
.attr('dx', -10)
.style('fill', color[1])
.style('font-size', '60px');
g.append('text')
.text('%')
.attr('class', 'percent')
.attr('text-anchor', 'middle')
.attr('dx', 35)
.attr('dy', 0)
.style('fill', color[1])
.style('font-size', '40px');
}
arcTween = (transition: any, newAngle: number) => {
transition.attrTween('d', (d: any) => {
const i = interpolate(d.endAngle, newAngle);
const iCount = interpolate(this.lastCount, this.percent);
return (t) => {
d.startAngle = d.endAngle;
d.endAngle = i(t);
this.lastCount = Math.floor(iCount(t));
this.middleCount.text(this.lastCount);
return this.arcLine(d);
};
});
}
animate = () => {
this.ratio = this.percent / 100;
this.pathChart.transition()
.duration(250)
.ease(easeCubic)
.call(this.arcTween, ((2 * Math.PI)) * this.ratio);
}
createGradient(svg: any, color1: string, color2: string, id: string): void {
const defs = svg.append('defs');
const gradient = defs.append('linearGradient')
.attr('id', id)
.attr('x1', '0%')
.attr('y1', '0%')
.attr('x2', '50%')
.attr('y2', '100%')
.attr('spreadMethod', 'pad');
gradient.append('stop')
.attr('offset', '50%')
.attr('stop-color', color1)
.attr('stop-opacity', 1);
gradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', color2)
.attr('stop-opacity', 1);
}
}