如何避免创建多个 svg 元素?

How to avoid multiple svg elements from being created?

我正在尝试在 angular2 组件中构建 D3 图表。每当我单击 link 创建 D3 图表时,它都会创建一个新实例。请注意 HTML 处创建了多个 SVG 标签副本。任何想法为什么会发生以及如何避免它? 每次我单击 link 创建 D3 图表时,它应该 clear/null 现有实例并创建一个新的图表组件。

从父组件创建新实例的代码,

import { Component } from '@angular/core';
import { BubbleChart } from '../Charts/BubbleChart';

@Component({
    template: `
<div id="divBubbleChart">
    <bubble-chart></bubble-chart>
</div>
`,
    directives: [BubbleChart]
})

export class CacheVisualization {
    constructor() {
        console.log("CacheVisualization component being called");
    }
}

子 d3 组件

import { Component, ViewEncapsulation } from '@angular/core';
import { HTTP_PROVIDERS, Http } from '@angular/http';
import { Configuration } from '../Configuration/Configuration';

declare var d3: any;

@Component({
    selector: 'bubble-chart',
    styleUrls: ['css/BubbleChart.css'],
    providers: [Configuration, HTTP_PROVIDERS],
    template: ``,
    encapsulation: ViewEncapsulation.None 
})
export class BubbleChart {
    public resultData: any;
    public chartData: any;
    margin = 5;
    diameter = 660;

    constructor(private _Configuration: Configuration) {
        console.log("In constructor of BubbleChartComponent");
        this.DrawBubbleChart();
    }

    private DrawBubbleChart(): void {
        console.log("Inside DrawBubbleChart in BubbleChartComponent");
        //console.log(this.resultData);

        var color = d3.scale.linear()
            .domain([-1, 5])
            .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
            .interpolate(d3.interpolateHcl);

        var pack = d3.layout.pack()
            .padding(2)
            .size([this.diameter - this.margin, this.diameter - this.margin])
            .value(function (d) { return d.size; })

        var svg = d3.select("body").append("svg")
            .attr("width", this.diameter)
            .attr("height", this.diameter)
            .append("g")
            .attr("transform", "translate(" + this.diameter / 2 + "," + this.diameter / 2 + ")");

        var chart = d3.json(this._Configuration.BLUESKYDATACACHEAPI_GETEXTRACTORQUEUESLATEST, (error, root) => {
            if (error) throw error;

            var focus = root,
                nodes = pack.nodes(root),
                view;

            var circle = svg.selectAll("circle")
                .data(nodes)
                .enter().append("circle")
                .attr("class", function (d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
                .style("fill", (d) => { return d.children ? color(d.depth) : null; })
                .on("click", (d) => { if (focus !== d) zoom.call(this, d), d3.event.stopPropagation(); });

            var text = svg.selectAll("text")
                .data(nodes)
                .enter().append("text")
                .attr("class", "label")
                .style("fill-opacity", function (d) { return d.parent === root ? 1 : 0; })
                .style("display", function (d) { return d.parent === root ? "inline" : "none"; })
                .text(function (d) { return d.name; });

            var node = svg.selectAll("circle,text");

            d3.select("body")
                .style("background", "white")
                //.style("vertical-align", "top")
                //.style("background", color(-1))
                .on("click", () => { zoom.call(this, root); });

            zoomTo.call(this, [root.x, root.y, root.r * 2 + this.margin]);

            function zoom(d) {
                var focus0 = focus; focus = d;

                var transition = d3.transition()
                    .duration(d3.event.altKey ? 7500 : 750)
                    .tween("zoom", (d) => {
                        var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + this.margin]);
                        return (t) => { zoomTo.call(this, i(t)); };
                    });

                transition.selectAll("text")
                    .filter(function (d) { return d.parent === focus || this.style.display === "inline"; })
                    .style("fill-opacity", function (d) { return d.parent === focus ? 1 : 0; })
                    .each("start", function (d) { if (d.parent === focus) this.style.display = "inline"; })
                    .each("end", function (d) { if (d.parent !== focus) this.style.display = "none"; });
            }

            function zoomTo(v) {
                var k = this.diameter / v[2]; view = v;
                node.attr("transform", function (d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
                circle.attr("r", function (d) { return d.r * k; });
            }//end zoomTo

        });//end chart

    }//end DrawBubbleChart

}

将 ID 分配给创建的组件后,它会为父 html 标签而不是 "svg" 标签创建 ID。参考下面的快照

要删除您正在创建的元素,您应该在删除组件时删除它们。 Angular 2 有 OnDestory lyfecycle hook。尝试实施它。 在它里面,你从正文中删除了 svg 元素。

ngOnDestroy() {
  // save the element on creation and..
  // remove element from body here
}

解决方案 1:在创建 SVG 元素之前检查 svg 元素是否已经存在 d3.select("body").追加("svg")。如果存在,请使用它而不是附加新的 SVG

var svg = d3.select('#mySVG').transition()

解决方案 2:创建应为图表更新调用的新函数 'UpdateDrawBubbleChart()'。在 BubbleChart 构造函数中检查 class 的实例是否已存在并调用 'UpdateDrawBubbleChart',在此函数中删除 SVG 元素或使用 d3 转换。