简体   繁体   中英

D3.js Tree with Numbering on Nodes

index.html:

 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script> <style> .node circle { fill: #fff; stroke: steelblue; stroke-width: 3px; } .node rect { fill: #fff; stroke: steelblue; stroke-width: 3px; } .node text { font: 12px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 2px; } .arrow { fill: none; stroke: #ccc; stroke-width: 1px; } </style> </head> <body> <script> var treeData = { "name": "File 1", "children": [{ "name": "File 2", "children": [ { "name": "File 3", "children": [{ "name": "File 4", "type": "data" }] }, { "name": "File 5", "children": [{ "name": "File 6", "type": "data", "children": [ { "name": "File 7" }, { "name": "File 8", "type": "data" } ] }] } ], }] }; // Set the dimensions and margins of the diagram var margin = {top: 20, right: 90, bottom: 30, left: 90}, width = 5000 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; // append the svg object to the body of the page // appends a 'group' element to 'svg' // moves the 'group' element to the top left margin var svg = d3.select("body").append("svg") .attr("width", width + margin.right + margin.left) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var i = 0, duration = 750, root; // declares a tree layout and assigns the size var treemap = d3.tree().size([height, width]); // Assigns parent, children, height, depth root = d3.hierarchy(treeData, function(d) { return d.children; }); root.x0 = height / 2; root.y0 = 0; // Collapse after the second level root.children.forEach(collapse); update(root); // Collapse the node and all it's children function collapse(d) { if(d.children) { d._children = d.children d._children.forEach(collapse) d.children = null } } function update(source) { // Assigns the x and y position for the nodes var treeData = treemap(root); // Compute the new tree layout. var nodes = treeData.descendants(), links = treeData.descendants().slice(1); // Normalize for fixed-depth. nodes.forEach(function(d){ dy = d.depth * 180}); // ****************** Nodes section *************************** // Update the nodes... var node = svg.selectAll('g.node') .data(nodes, function(d) {return d.id || (d.id = ++i); }); // Enter any new modes at the parent's previous position. var nodeEnter = node.enter().append('g') .attr('class', 'node') .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) .on('click', click); // Add Circle for the nodes nodeEnter.filter(function(d){ return (!d.data.type || d.data.type !== 'data'); }).append('circle') .attr('class', 'node') .attr('r', 1e-6) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeEnter.filter(function(d){ return (d.data.type && d.data.type === 'data'); }).append('rect') .attr('class', 'node') .attr('width', 20) .attr('height', 20) .attr('y', -10) .attr('x', -10) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); // Add labels for the nodes nodeEnter.append('text') .attr("dy", "2em") .attr("x", function(d) { return d.children || d._children ? 13 : 13; }) .attr("text-anchor", function(d) { return d.children || d._children ? "start" : "start"; }) .text(function(d) { return d.data.name; }); // UPDATE var nodeUpdate = nodeEnter.merge(node); // Transition to the proper position for the node nodeUpdate.transition() .duration(duration) .attr("transform", function(d) { return "translate(" + dy + "," + dx + ")"; }); // Update the node attributes and style nodeUpdate.select('circle.node') .attr('r', 10) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }) .attr('cursor', 'pointer'); // Remove any exiting nodes var nodeExit = node.exit().transition() .duration(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... var link = svg.selectAll('path.link') .data(links, function(d) { return d.id; }); // Enter any new links at the parent's previous position. var linkEnter = link.enter().insert('path', "g") .attr("class", "link") .attr('d', function(d){ var o = {x: source.x0, y: source.y0} return diagonal(o, o) }); svg.append("text") .attr("id", "curve-text") .append("textPath") .attr("xlink:href", "#link") .data(treeData, function(d, i){return i;}) // UPDATE var linkUpdate = linkEnter.merge(link); // Transition back to the parent element position linkUpdate.transition() .duration(duration) .attr('d', function(d){ return diagonal(d, d.parent) }); // Remove any exiting links var linkExit = link.exit().transition() .duration(duration) .attr('d', function(d) { var o = {x: source.x, y: source.y} return diagonal(o, o) }) .remove(); // Store the old positions for transition. nodes.forEach(function(d){ d.x0 = dx; d.y0 = dy; }); // Creates a curved (diagonal) path from parent to the child nodes function diagonal(s, d) { path = `M ${sy} ${sx} C ${(sy + dy) / 2} ${sx}, ${(sy + dy) / 2} ${dx}, ${dy} ${dx}` return path } // Toggle children on click. function click(d) { if (d.children) { d._children = d.children; d.children = null; } else { d.children = d._children; d._children = null; } update(d); } } </script> </body> 

Desired product:

在此处输入图片说明

For each node which has multiple paths to the right of it, the top path should have a 1 , increasing as you're going down, as in the image above. I have already seen https://gist.github.com/mbostock/2565344 and draw text in d3 arc javascript , but have not been able to add the numbers to the diagram.

I get that you're probably supposed to use .append('text') , but I'm not sure how to "append" this to the paths, nor do I know how to appropriately set the properties and the text value in this case. Any answer to this should be scalable - ie, do not assume that there are only two paths.

Another fun question. I'd do it like this.

First, change the linkEnter to append a g and into this g append the path and text .

// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('g', 'g')
  .attr("class", "link");

linkEnter.append('path')
  .attr('d', function(d) {
    var o = {
      x: source.x0,
      y: source.y0
    }
    return diagonal(o, o)
  });

linkEnter.append('text')
...

Second, To calculate the count, here's a tricky little function based off the parent's children's length and where you are in the iteration of the links:

.text(function(d,i) {
  if (d.parent && d.parent.children.length > 1){
    if (!d.parent.index) d.parent.index = 0;
    return ++d.parent.index;
  }
})

Finally, for the position, I wouldn't mess around with a textPath , it's a bit overkill. Just calculate the midpoint of the link.

.attr('transform', function(d){
  if (d.parent) {
    return 'translate(' + ((d.parent.y + d.y) / 2) + ',' + ((d.parent.x + d.x) / 2) + ')'
  }
})

Putting it all together:

 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script> <style> .node circle { fill: #fff; stroke: steelblue; stroke-width: 3px; } .node rect { fill: #fff; stroke: steelblue; stroke-width: 3px; } .node text { font: 12px sans-serif; } .link path { fill: none; stroke: #ccc; stroke-width: 2px; } .link text { font: 12px sans-serif; } .arrow { fill: none; stroke: #ccc; stroke-width: 1px; } </style> </head> <body> <script> var treeData = { "name": "File 1", "children": [{ "name": "File 2", "children": [{ "name": "File 3", "children": [{ "name": "File 4", "type": "data" }] }, { "name": "File 5", "children": [{ "name": "File 6", "type": "data", "children": [{ "name": "File 7" }, { "name": "File 8", "type": "data" }] }] }], }] }; // Set the dimensions and margins of the diagram var margin = { top: 20, right: 90, bottom: 30, left: 90 }, width = 5000 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; // append the svg object to the body of the page // appends a 'group' element to 'svg' // moves the 'group' element to the top left margin var svg = d3.select("body").append("svg") .attr("width", width + margin.right + margin.left) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var i = 0, duration = 750, root; // declares a tree layout and assigns the size var treemap = d3.tree().size([height, width]); // Assigns parent, children, height, depth root = d3.hierarchy(treeData, function(d) { return d.children; }); root.x0 = height / 2; root.y0 = 0; // Collapse after the second level root.children.forEach(collapse); update(root); // Collapse the node and all it's children function collapse(d) { if (d.children) { d._children = d.children d._children.forEach(collapse) d.children = null } } function update(source) { // Assigns the x and y position for the nodes var treeData = treemap(root); // Compute the new tree layout. var nodes = treeData.descendants(), links = treeData.descendants().slice(1); // Normalize for fixed-depth. nodes.forEach(function(d) { dy = d.depth * 180 }); // ****************** Nodes section *************************** // Update the nodes... var node = svg.selectAll('g.node') .data(nodes, function(d) { return d.id || (d.id = ++i); }); // Enter any new modes at the parent's previous position. var nodeEnter = node.enter().append('g') .attr('class', 'node') .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) .on('click', click); // Add Circle for the nodes nodeEnter.filter(function(d) { return (!d.data.type || d.data.type !== 'data'); }).append('circle') .attr('class', 'node') .attr('r', 1e-6) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeEnter.filter(function(d) { return (d.data.type && d.data.type === 'data'); }).append('rect') .attr('class', 'node') .attr('width', 20) .attr('height', 20) .attr('y', -10) .attr('x', -10) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); // Add labels for the nodes nodeEnter.append('text') .attr("dy", "2em") .attr("x", function(d) { return d.children || d._children ? 13 : 13; }) .attr("text-anchor", function(d) { return d.children || d._children ? "start" : "start"; }) .text(function(d) { return d.data.name; }); // UPDATE var nodeUpdate = nodeEnter.merge(node); // Transition to the proper position for the node nodeUpdate.transition() .duration(duration) .attr("transform", function(d) { return "translate(" + dy + "," + dx + ")"; }); // Update the node attributes and style nodeUpdate.select('circle.node') .attr('r', 10) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }) .attr('cursor', 'pointer'); // Remove any exiting nodes var nodeExit = node.exit().transition() .duration(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... var link = svg.selectAll('g.link') .data(links, function(d) { return d.id; }); // Enter any new links at the parent's previous position. var linkEnter = link.enter().insert('g', 'g') .attr("class", "link"); linkEnter.append('path') .attr('d', function(d) { var o = { x: source.x0, y: source.y0 } return diagonal(o, o) }); linkEnter.append('text') .text(function(d,i) { if (d.parent && d.parent.children.length > 1){ if (!d.parent.index) d.parent.index = 0; return ++d.parent.index; } }) .attr('dy', "-1em"); // UPDATE var linkUpdate = linkEnter.merge(link); // Transition back to the parent element position linkUpdate.select('path').transition() .duration(duration) .attr('d', function(d) { return diagonal(d, d.parent) }); linkUpdate.select('text').transition() .duration(duration) .attr('transform', function(d){ if (d.parent) { return 'translate(' + ((d.parent.y + dy) / 2) + ',' + ((d.parent.x + dx) / 2) + ')' } }) // Remove any exiting links link.exit().each(function(d){ d.parent.index = 0; }) var linkExit = link.exit() .transition() .duration(duration); linkExit.select('path') .attr('d', function(d) { var o = { x: source.x, y: source.y } return diagonal(o, o) }) linkExit.select('text') .style('opacity', 0); linkExit.remove(); // Store the old positions for transition. nodes.forEach(function(d) { d.x0 = dx; d.y0 = dy; }); // Creates a curved (diagonal) path from parent to the child nodes function diagonal(s, d) { path = `M ${sy} ${sx} C ${(sy + dy) / 2} ${sx}, ${(sy + dy) / 2} ${dx}, ${dy} ${dx}` return path } // Toggle children on click. function click(d) { if (d.children) { d._children = d.children; d.children = null; } else { d.children = d._children; d._children = null; } update(d); } } </script> </body> 

You need to add text by creating a d3-selection and appending a text element for each link.

// Select all text links
var linkText = svg.selectAll('text.link')
    .data(links, function(d) {return d.id});

// Then append the text on the enter selection
var linkEnter = linkText.enter().insert('text','g')
    .attr('class','link')
    .attr('x',function(d){return (d.target.x-d.source.x)/2;})
    .attr('y',function(d){return (d.target.y-d.source.y)/2;})
    .text(function(d){return d['cardinality'];});

Note: this only to add the text elements. You'll also need to update the text positions on and update and remove them when those links have been removed. Follow the same structure that you have in the path.link d3-selections that you have for this.

You'll also need a way to calculate 'cardinality' for each link. Which I would assume you can accomplish by looping through the links array and checking if more than one link has the same source, then count up, otherwise give that link a cardinality of 0 or something special.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM