D3.JS树形图 - 点击顺序会弄乱数据

时间:2018-04-26 16:34:40

标签: d3.js tree nodes interchange

我有一个D3树形图,它在点击节点时从API加载数据。我注意到点击节点的顺序正在影响树中数据的显示。数据是正确的,但节点的顺序根据点击顺序而变化(const nodes = treeData.descendants())。

我的代码

node_clicked(d) {
      d.data.isCollapsed = !d.data.isCollapsed;

      this.service.linkedConcepts(d.data.id)
      .then(data => {
        if (data) {
          const items = data as any[];
          this.appendData(d.data.id, this.treeData, items);
          if (d.children) {
            d._children = d.children;
            d.children = null;
          } else {
            d.children = d._children;
            d._children = null;
          }
          this.root = d3.hierarchy(this.treeData, d => d.children);

          this.update(d);
        }
      })
      .catch(err => {
        this.removeFromDataBeingRetrieved(d.data.id);
        console.error(err);
      });
  }

appendData(parentid, node, items) {
    if (node.id === parentid) {
      items.forEach(item  => {
        node.children.push({
          name: item.Label,
          id: item.ConceptID,
          isCollapsed: true,
          isTopConcept: false,
          count: item.LinkedConceptsCount,
          children: []
        });
      });
      node.isCollapsed = false;
    } else if (node.children) {
      node.children.forEach(item => this.appendData(parentid, item, items));
    }
  }

update(source) {

    const treeData = this.treemap(this.root);

    // Compute the new tree layout.
    const nodes = treeData.descendants(),
        links = treeData.descendants().slice(1);

        // Normalize for fixed-depth.
    nodes.forEach(d =>  d.y = d.depth * 180);

    // ****************** Nodes section ***************************

    let i = 0;

    // Update the nodes...
    const node = this.svg.selectAll('g.node')
        .data(nodes, function(d) {return d.id || (d.id = ++i); });

        // Enter any new modes at the parent's previous position.
    const nodeEnter = node.enter().append('g')
        .attr('class', 'node')
        .attr('transform', d => 'translate(' + source.y0 + ',' + source.x0 + ')')
        .on('click', d => this.node_clicked(d));

        // Add Circle for the nodes
    nodeEnter.append('circle')
        .attr('class', 'node')
        .attr('r', 1e-6)
        .style('stroke', function(d) {
          return d.data.count > 0 ? '#fff' : 'steelblue'; })
        .style('fill', function(d) {
            return d.isCollapsed && d.data.count > 0 ? 'lightsteelblue' : '#fff';
        })
        .append('title')
        .text(d => d.data.count + ' linked articles');

        // Add labels for the nodes
    nodeEnter.append('text')
        .attr('dy', '.35em')
        .attr('x', function(d) {
            return d.children || d._children ? -13 : 13;
        })
        .attr('text-anchor', function(d) {
            return (d.data.isTopConcept === true) ? 'end' : 'start';
        })
        .text(function(d) { return d.data.name; })
        .call(d => this.wrap(d));


        // UPDATE
    const nodeUpdate = nodeEnter.merge(node);

    // Transition to the proper position for the node
    nodeUpdate.transition()
      .duration(this.duration)
      .attr('transform', function(d) {
          return 'translate(' + d.y + ',' + d.x + ')';
       });

       // Update the node attributes and style
    nodeUpdate.select('circle.node')
      .attr('r', 10)
      .style('fill', function(d) {
        if (d.data.count === 0) {
          return '#fff';
        } else if ( d.data.isCollapsed === true ) {
          return 'lightsteelblue';
        } else {
          return '#fff';
        }
      })
      .style('stroke-width', '3px')
      .style('stroke', function(d) {
        return d._children > 0 ? '#fff' : 'lightsteelblue'; })
      .attr('cursor', 'pointer');

    // Remove any exiting nodes
    const nodeExit = node.exit().transition()
        .duration(this.duration)
        .attr('transform', function(d) {
            return 'translate(' + source.y + ',' + source.x + ')';
        })
        .remove();

        // On exit reduce the node circles size to 0
    nodeExit.select('circle')
      .attr('r', 1e-6);

    // On exit reduce the opacity of text labels
    nodeExit.select('text')
      .style('fill-opacity', 1e-6);

      // ****************** links section ***************************

    // Update the links...
    const link = this.svg.selectAll('path.link')
        .data(links, d => d['id']);

        // Enter any new links at the parent's previous position.
    const linkEnter = link.enter().insert('path', 'g')
        .attr('class', 'link')
        .style('fill', 'none')
        .style('stroke', '#ccc')
        .style('stroke-width', '3px')
        .attr('d', d => this.diagonal({x: source.x, y: source.y},
                {x: source.x, y: source.y}));

        // UPDATE
    const linkUpdate = linkEnter.merge(link);

    // Transition back to the parent element position
    linkUpdate.transition()
        .duration(this.duration)
        .attr('d', d => this.diagonal(d, d.parent) );

        // Remove any exiting links
    const linkExit = link.exit().transition()
        .duration(this.duration)
        .attr('d', d => this.diagonal({x: source.x, y: source.y},
                {x: source.x, y: source.y}))
        .remove();

        // Store the old positions for transition.
    nodes.forEach(function(d){
      d.x0 = d.x;
      d.y0 = d.y;
    });

  }

问题的说明: 当我在扩展3级节点之前扩展2级节点时,我看到这是正确的数据 - enter image description here

当我在第2级之前展开3级节点时,我看到这是不正确的数据。第4级显示的数据也在第3级(突出显示)中重复 - enter image description here

我是否必须手动管理treeData.descendants()的顺序来处理此问题?还有其他内置方式吗?

1 个答案:

答案 0 :(得分:0)

这行代码导致了问题



   const node = this.svg.selectAll('g.node')
        .data(nodes, function(d) {return d.id || (d.id = ++i); });




我把它改成了



    const node = this.svg.selectAll('g.node')
        .data(nodes, function(d) {return d.id || (d.id = d.data.id); });




现在完美无缺:)