npm install d3 --save
npm install @types/d3 --save-dev
import * as d3 from 'd3';
<div #svgContainer (window:resize)="onResize()"></div>
import { AfterViewInit, Component, ElementRef, HostListener, Input, OnChanges, SimpleChanges, ViewChild} from '@angular/core';
import * as d3 from 'd3';
import {ScaleBand} from 'd3';
@Component({
selector: 'app-d3chart',
templateUrl: './d3chart.component.html',
styleUrls: ['./d3chart.component.less']
})
export class D3chartComponent implements AfterViewInit, OnChanges {
@Input() data;//!: { name: string, series: { name: string, value: number }[] }[];
@Input() height = 300;
@Input() margin = {top: 10, left: 50, right: 10, bottom: 20};
@Input() innerPadding = 0.1;
@Input() outerPadding = 0.1;
@Input() seriesInnerPadding = 0.1;
domain = [0, 1000];
@Input() barColors = ['#00aeef', '#f98e2b', '#7C77AD'];
// @Input() domain = [0, 1000];
// @Input() barColors = ['#00aeef', '#f98e2b', '#7C77AD'];
// @Input() data!: { name: string, series: { name: string, value: number }[] }[];
public svg!: d3.Selection<SVGGElement, unknown, null, undefined>;
public isRendered = false;
@ViewChild('svgContainer', {read: ElementRef, static: true}) svgContainerRef!: ElementRef<HTMLDivElement>;
constructor() {
this.barColors = ['#a9ce97', '#a5b5de'];
this.domain = [100, 1000];
this.data = [
{
name: 'Row1',
series: [
{name: 'Bar1', value: 150},
{name: 'Bar2', value: 200}
],
},
{
name: 'Row2',
series: [
{name: 'Bar1', value: 300},
{name: 'Bar2', value: 400}
],
},
{
name: 'Row3',
series: [
{name: 'Bar1', value: 500},
{name: 'Bar2', value: 1000}
],
}
];
}
@HostListener('window:resize')
onResize() {
this.createChart();
}
ngOnChanges(changes: SimpleChanges) {
if (this.isRendered) {
this.createChart();
}
}
ngAfterViewInit(): void {
this.createChart();
this.isRendered = true;
}
private createSVG(): void {
this.svg = d3.select(this.svgContainerRef.nativeElement)
.append('svg')
.attr('width', '100%')
.attr('height', this.height)
.append('g')
.attr('width', '100%')
.attr('transform', 'translate(0, 0)')
.attr('class', 'bar-chart-vertical'); /// chart type
}
private isDataValid(): boolean {
return this.data && this.data.length > 0;
}
private getBandScale(domain: string[], range: any, innerPadding = 0, outerPadding = 0) {
const scale: any | ScaleBand<string> = d3.scaleBand()
.range(range)
.domain(domain)
.paddingInner(innerPadding)
.paddingOuter(outerPadding);
scale.type = 'BAND';
return scale;
}
private createChart(): void {
if (!this.isRendered) {
this.createSVG();
}
if (this.isDataValid()) {
const margin = {
top: this.margin.top,
right: this.margin.right,
bottom: this.margin.bottom,
left: this.margin.left,
}
let height = this.height - margin.top - margin.bottom;
const width = this.svgContainerRef.nativeElement.getBoundingClientRect().width - margin.left - margin.right;
const groupNames = this.data.map(item => item.name);
const groupLabels = this.data.length > 0 ? this.data[0].series.map(item => item.name) : [];
const xScale = this.getBandScale(groupNames, [0, width], this.innerPadding, this.outerPadding).round(true);
const x1Scale = this.getBandScale(groupLabels, [0, xScale.bandwidth()], this.seriesInnerPadding, this.outerPadding).round(true);
let chartContainer = this.svg.selectAll<SVGGElement, number>('g.chart-container').data([1]);
chartContainer = chartContainer.enter()
.append('g')
.attr('class', 'chart-container')
.merge(chartContainer)
.attr('transform', `translate(${margin.left}, ${margin.right})`);
let chartWrap = chartContainer.selectAll<SVGGElement, number>('g.chart-wrap').data([1]);
chartWrap = chartWrap.enter()
.append('g')
.attr('class', 'chart-wrap')
.merge(chartWrap)
.attr('transform', 'translate(0, 0)');
const xAxis = chartWrap.selectAll<SVGGElement, number>('g.x-axis').data([1]);
xAxis.enter()
.append('g')
.attr('class', 'x-axis')
.merge(xAxis)
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(xScale)).selectAll('text')
.style('text-anchor', 'middle');
const y = d3.scaleLinear().domain(this.domain).nice().rangeRound([height, 0]);
let barWrap = chartWrap.selectAll<SVGGElement, number>('g.bar-wrap').data([1]);
barWrap.exit().remove();
barWrap = barWrap.enter().append('g')
.attr('class', 'bar-wrap')
.merge(barWrap);
let barGroup = barWrap.selectAll<SVGGElement, {name: string, series: {name: string, value: number}}>('g.bar-group').data(this.data);
barGroup.exit().remove();
barGroup = barGroup.enter().append('g')
.attr('class', 'bar-group')
.merge(barGroup)
.attr('transform', d => `translate(${xScale(d.name)}, 0)`);
let barRects = barGroup.selectAll<SVGRectElement, {name: string, value: number}>('rect.bar').data(d => d.series.map(item => item));
barRects.enter()
.append('rect')
.merge(barRects)
.attr('class', 'bar')
.attr('width', x1Scale.bandwidth())
.attr('height', d => height - y(d.value))
.attr('x', (d: any) => x1Scale(d.name))
.attr('y', d => y(d.value))
.attr('fill', (d, i) => this.barColors[i]);
let yAxis = chartWrap.selectAll<SVGGElement, number>('g.y-axis').data([1]);
yAxis.enter()
.append('g')
.attr('class', 'y-axis')
.merge(yAxis)
.call(d3.axisLeft(y));
}
}
}
No comments:
Post a Comment