Реактивная карта дерева D3 JS не обновляется должным образом (Angular) - PullRequest
0 голосов
/ 22 апреля 2020

Я работаю на реактивном древовидном графике 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() для плавной анимации (это будет следующим шагом!).

...