如何更新D3中的树形图

时间:2017-06-21 19:21:03

标签: javascript d3.js

当我在d3中使用树形图时,我通过在树形图中添加节点来获得我想要的行为。但是,当我向图表添加节点时,节点(和路径)会被放置,但节点不会平衡,之前的节点也不会移动。

我在这里发布了整个代码的工作版本:https://codepen.io/auser/pen/mwwVJL

JS代码(为了完整起见):

const pathGraph = (eleName, treeData, opts = {}) => {
  var margin = { top: 40, right: 90, bottom: 50, left: 90 },
    width = 660 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  const treemap = d3.tree().size([width, height]);

  const svg = d3
    .select(eleName)
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom);

  let g = svg
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  const updateData = newData => {
    //  assigns the data to a hierarchy using parent-child relationships
    let nodes = treemap(d3.hierarchy(newData));

    const color = d3.scaleOrdinal(d3.schemeCategory10).domain(d3.range(0, 8));

    // adds the links between the nodes
    const link = g
      .selectAll(".link")
      .data(nodes.descendants().slice(1))
      .enter()
      .append("path")
      .attr("class", "link")
      .style("stroke-width", 1)
      .attr("d", function(d) {
        return (
          "M" + d.x + "," + d.y + "C" + d.x + "," + (d.y + d.parent.y) / 2 + " " + d.parent.x + "," + (d.y + d.parent.y) / 2 + " " + d.parent.x + "," + d.parent.y
        );
      });

    link.exit().remove();

    // adds each node as a group
    const node = g
      .selectAll(".node")
      .data(nodes.descendants())
      .enter()
      .append("g")
      .attr("class", function(d) {
        return "node" + (d.children ? " node--internal" : " node--leaf");
      })
      .attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")";
      });

    node.exit().remove();

  const parentTree = (d) => {
    let nodeLinks = []
    while(d.parent) {
      nodeLinks.push(d)
      d = d.parent
    }
    nodeLinks = nodeLinks.concat(d)
    return nodeLinks
  }

  const activeLink = (d, o) => {
    if (d === o || d.parent === o) return true;
  }
  node
    .on('mouseover', function(d) {
      const data = d3.select(this)
      const linkedNodes = parentTree(d)
      link
        .style('stroke-width', o => activeLink(d, o) ? 4 : 1)
        .style('stroke', o => activeLink(d, o) ? 'red' : '#333')
          .transition(500)

      rect
        .style('stroke-width', o => activeLink(d, o) ? 4 : 1)
        .transition(500)

    })
    .on('mouseout', d => {
      const data = d3.select(this);
      // console.log('d ->', d)
      link
        .style('stroke-width', 1)
        .style('stroke', '#333')

      rect
        .style('stroke-width', 1)
    })

    // adds the circle to the node
    const rect = node
      .append("rect")
      .attr("height", 50)
      .attr("width", 50)
      .style("fill", (d, i) => color(i))
      .attr("x", "-0.7em");

    // adds the text to the node
    node
      .append("text")
      .attr("dy", ".52em")
      .attr("y", function(d) {
        return d.children ? -18 : 20;
      })
      .attr("dx", "-.2em")
      .style("text-anchor", "middle")
      .text(function(d) {
        return d.data.name;
      });
  };

  updateData(treeData);
  return updateData;
};

const data =   {
    "name": "Root",
    "children": [
      {
		"name": "A",
        "children": [
          { "name": "B" },
          { "name": "C" }
        ]
      },
      { "name": "D" },
      { "name": "E",
        "children":[
          { "name": "F"}
        ] }
    ]
  };

const mount = document.querySelector('#treea')
const updateData = pathGraph(mount, data)

setTimeout(function() {
  data.children[2].children.push({ name: "H" })
  updateData(data, { update: true })
}, 2000)
.tree .node rect, .tree .node circle {
    fill: blue;
    rounding: 5px;
 }
 .tree .link {
    fill: none;
    stroke: #222;
    stroke-opacity: 1;
    stroke-width: 1.5px;
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>

<svg id="treea" class="tree"></svg>

非常感谢任何帮助......

1 个答案:

答案 0 :(得分:1)

你需要一个合适的&#34;输入&#34;,&#34;更新&#34;和&#34;退出&#34;选择。像这样:

//this is the update selection
const link = g
    .selectAll(".link")
    .data(nodes.descendants().slice(1));

//this is the enter selection, up to the 'merge'
link.enter()
    .append("path")
    .attr("class", "link")
    .merge(link)//from now one, update + enter
    .style("stroke-width", 1)
    .attr("d", function(d) {
        return (
            "M" + d.x + "," + d.y + "C" + d.x + "," +
            (d.y + d.parent.y) / 2 + " " + d.parent.x + "," +
            (d.y + d.parent.y) / 2 + " " + d.parent.x + "," + d.parent.y
        );
    });

//this is the exit selection
link.exit().remove();

以下是更新的Codepen:https://codepen.io/anon/pen/RggVPO?editors=0010

这里是Stack片段:

&#13;
&#13;
const pathGraph = (eleName, treeData, opts = {}) => {
  var margin = { top: 40, right: 90, bottom: 50, left: 90 },
    width = 660 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

  const treemap = d3.tree().size([width, height]);

  const svg = d3
    .select(eleName)
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom);

  let g = svg
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  const updateData = newData => {
    //  assigns the data to a hierarchy using parent-child relationships
    let nodes = treemap(d3.hierarchy(newData));

    const color = d3.scaleOrdinal(d3.schemeCategory20).domain(d3.range(0, 8));

    // adds the links between the nodes
    const link = g
      .selectAll(".link")
      .data(nodes.descendants().slice(1));
    
      link.enter()
      .append("path")
      .attr("class", "link")
      .merge(link)
      .style("stroke-width", 1)
      .attr("d", function(d) {
        return (
          "M" + d.x + "," + d.y + "C" + d.x + "," + (d.y + d.parent.y) / 2 + " " + d.parent.x + "," + (d.y + d.parent.y) / 2 + " " + d.parent.x + "," + d.parent.y
        );
      }).lower();

    link.exit().remove();
    
    const node = g
      .selectAll(".node")
      .data(nodes.descendants())
    .attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")";
      });
    
    const nodeEnter = node.enter()
      .append("g")
      .attr("class", function(d) {
        return "node " + (d.children ? " node--internal" : " node--leaf");
      })
      .attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")";
      });

    node.exit().remove();

  const parentTree = (d) => {
    let nodeLinks = []
    while(d.parent) {
      nodeLinks.push(d)
      d = d.parent
    }
    nodeLinks = nodeLinks.concat(d)
    return nodeLinks
  }

  const activeLink = (d, o) => {
    if (d === o || d.parent === o) return true;
  }
  node
    .on('mouseover', function(d) {
      const data = d3.select(this)
      const linkedNodes = parentTree(d)
      link
        .style('stroke-width', o => activeLink(d, o) ? 4 : 1)
        .style('stroke', o => activeLink(d, o) ? 'red' : '#333')
          .transition(500)

      rect
        .style('stroke-width', o => activeLink(d, o) ? 4 : 1)
        .transition(500)

    })
    .on('mouseout', d => {
      const data = d3.select(this);
      // console.log('d ->', d)
      link
        .style('stroke-width', 1)
        .style('stroke', '#333')

      rect
        .style('stroke-width', 1)
    })

    // adds the circle to the node
    const rect = nodeEnter
      .append("rect")
      .attr("height", 50)
      .attr("width", 50)
      .style("fill", (d, i) => color(i))
      .style('padding', 5)
      .attr("rx", 6)
      .attr("ry", 6)
      .attr("x", "-0.7em");

    // adds the text to the node
    nodeEnter
      .append("text")
      .attr("dy", "0.8em")
      .attr("y", function(d) {
        return d.children ? -18 : 20;
      })
      .attr("dx", "0.8em")
      .style("text-anchor", "middle")
      .text(function(d) {
        return d.data.name;
      });
    
    node.select("rect").attr("x", "-0.7em");
    
  };

  updateData(treeData);
  return updateData;
};

const data =   {
    "name": "Root",
    "children": [
      {
		"name": "A",
        "children": [
          { "name": "B" },
          { "name": "C" }
        ]
      },
      { "name": "D" },
      { "name": "E",
        "children":[
          { "name": "F"}
        ] }
    ]
  };

const mount = document.querySelector('#treea')
const updateData = pathGraph(mount, data)

setTimeout(function() {
  data.children[2].children.push({ name: "H" })
  updateData(data, { update: true })
}, 2000)
&#13;
.tree .node rect, .tree .node circle {
    fill: blue;
    rounding: 5px;
 }
 .tree .link {
    fill: none;
    stroke: #222;
    stroke-opacity: 1;
    stroke-width: 1.5px;
  }
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>

<svg id="treea" class="tree"></svg>
&#13;
&#13;
&#13;

PS:节点的事情比较复杂,因为你有一组有矩形和文本的组。我很快就改变了选择,但我建议你相应地重构那部分代码。