Я работаю на реактивном древовидном графике D3 JS (адаптирован к Angular из https://www.d3-graph-gallery.com/graph/treemap_custom.html).
Когда я изменяю данные, диаграмма портится.
Вот мой код, и он также доступен в https://stackblitz.com/edit/angular-ydehjc
treemap-chart .component.ts
import { Component, ElementRef, OnChanges, OnInit, SimpleChanges, Input } from '@angular/core';
import { formatNumber } from '@angular/common';
import * as d3 from 'd3';
// credit to https://www.d3-graph-gallery.com/graph/treemap_custom.html
@Component({
selector: 'app-treemap-chart',
template: '',
styleUrls: ['./treemap-chart.component.scss']
})
export class TreemapChartComponent implements OnChanges, OnInit {
@Input() data: any[];
hostElement;
svg;
colorScale;
margin;
width;
height;
viewBoxWidth = 1000;
viewBoxHeight = 600;
root;
color;
opacity;
titles;
constructor(
private elRef: ElementRef,
) {
this.initChart();
}
ngOnInit() {
}
private initChart() {
this.hostElement = this.elRef.nativeElement;
this.margin = { top: 0, right: 0, bottom: 0, left: 0 },
this.width = this.viewBoxWidth - this.margin.left - this.margin.right,
this.height = this.viewBoxHeight - this.margin.top - this.margin.bottom;
}
ngOnChanges(changes: SimpleChanges) {
if (changes.data) {
this.data = changes.data.currentValue;
this.updateChart();
this.createChart();
this.updateTitles();
}
}
public updateChart() {
this.createOrUpdateSvg();
}
private createOrUpdateSvg() {
if (this.svg) {
d3.select(this.hostElement)
.select('svg')
.attr('viewBox', '0 0 ' + this.viewBoxWidth + ' ' + this.viewBoxHeight);
} else {
this.svg = d3.select(this.hostElement)
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('preserveAspectRatio', 'xMidYMid meet')
// .classed('svg-content', true)
.attr('viewBox', '0 0 ' + this.viewBoxWidth + ' ' + this.viewBoxHeight)
.attr('id', 'd3-plot')
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
// prepare a color scale
this.color = d3.scaleOrdinal()
.domain(['Sales', 'Marketing', 'Dev'])
.range(['blue', 'Purple', 'Black']);
// And a opacity scale
this.opacity = d3.scaleLinear()
.domain([10, 30])
.range([.5, 1]);
}
}
private createChart() {
this.root = d3.hierarchy(this.data).sum(d => d.value); // Here the size of each leave is given in the 'value' field in input data
// Then d3.treemap computes the position of each element of the hierarchy
d3.treemap()
.size([this.width, this.height])
.paddingTop(28)
.paddingRight(7)
.paddingInner(3) // Padding between each rectangle
// .paddingOuter(6)
// .padding(20)
(this.root);
const leaf = this.svg.selectAll('g')
.data(this.root.leaves())
.join('g')
.attr('transform', d => `translate(${d.x0},${d.y0})`);
leaf.append('title')
.text(d => `${d.data.name}\n${d.value}`);
leaf.append('rect')
.attr('id', (d, i) => (d.leafUid = 'leaf-' + i))
.style('fill', d => this.color(d.parent.data.name))
.style('opacity', d => this.opacity(d.data.value))
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0);
leaf.append('clipPath')
.attr('id', (d, i) => (d.clipUid = 'clip-' + i))
.append('use')
.attr('href', d => '#' + d.leafUid);
leaf.append('text')
.attr('clip-path', d => 'url(#' + d.clipUid + ')')
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][a-z])|\s+/g).concat(d.value))
.join('tspan')
.attr('x', 3)
.attr('y', (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
.attr('fill-opacity', (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
.text(d => d)
.style('font', '12px Roboto-Regular')
.attr('fill', 'white');
}
updateTitles() {
// Add title for the 3 groups
if (this.titles) {
this.titles
.selectAll('text')
.attr('x', d => d.x0)
.attr('y', d => d.y0 + 21);
} else {
this.titles = this.svg
.selectAll('titles')
.data(this.root.descendants().filter(d => d.depth === 1));
this.titles
.enter()
.append('text')
.style('font', '19px Roboto-Bold')
.attr('fill', '#757575')
.attr('x', d => d.x0)
.attr('y', d => d.y0 + 21)
.text(d => d.data.name);
}
}
}
Есть идеи, чтобы решить эту проблему?
Кстати, я понятия не имею, куда добавить метод transition()
для плавной анимации (это будет следующим шагом!).