0

我有一个 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 级节点时,我看到这是正确的数据 - 在此处输入图像描述

当我在 2 级之前展开 3 级节点时,我看到这是不正确的数据。第 4 级显示的数据在第 3 级(突出显示)中也重复 - 在此处输入图像描述

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

4

1 回答 1

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); });

它现在完美运行:)

于 2018-04-27T12:38:02.847 回答