简体   繁体   中英

Using Multiple Link Types in D3 Tree Layout

I'm using Tree Layout to construct a complex diagram in D3 v3. My diagram normally has a tree structure, so I'm using the diagonal links as follows:

var diagonal = d3.svg.diagonal()
  .projection(function (d) {
    return [d.x, d.y];
  });

However, for some of the nodes in my diagram, I have a different type of nodes which show the attributes of their parent nodes. Therefore, I want them to be very close to their parent nodes rather than in the same level with the other children nodes. I thought a possible solution could be using radial links as follows:

var radialDiagonal = d3.svg.diagonal.radial()
  .projection(function (d) {
    return [d.y, d.x / 180 * Math.PI];
  });

But, I couldn't find any example on how to use multiple link types in a diagram. Is it possible to do this? Or, is there any solution to make some nodes so close to their parent nodes?

Thanks in advance.

I'm using d3v4+ syntax for a tree, the approaches below are adoptable to v3 relatively easily

It does not sound like the ability to use different types of links is what you are looking for.

The link type, d3.svg.diaganol here is irrelevant to node placement. The link only connects two points with some sort of pre-defined curve. So if you want to allow nodes "to be very close to their parent nodes rather than in the same level with the other children nodes", you want to manipulate the location of the nodes, not the actual links. One option would be to manually move nodes that should be close to their parent node. In the example below I use a node property ( near ) to filter through nodes (after running the layout generator) and move those that should be closer to their parent:

 root.descendants().reverse().forEach(function(d) {
  if(d.data.near) d.y = (d.parent.y+d.y)/2;
 })

 var data = { "name": "Parent", "children": [ { "name": "Child A", near:true, "children": [ { "name": "Grandkid A", near: true }, { "name": "Grandkid B" }, { "name": "Grandkid C" } ] }, { "name": "Child B", }, { "name": "Child C", children: [ { "name": "Grandkid D", near:true }, { "name": "Grandkid E" } ]} ] }; var width = 400; var height = 300; margin = {left: 50, top: 10, right:50, bottom: 10} var svg = d3.select("body").append("svg").attr("width", width).attr("height", height); var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')'); var root = d3.hierarchy(data); var tree = d3.tree().size([height-margin.top-margin.bottom,width-margin.left-margin.right]); tree(root) root.descendants().reverse().forEach(function(d) { if(d.data.near) dy = (d.parent.y+dy)/2; }) var link = g.selectAll(".link").data(root.links()).enter().append("path").attr("class", "link").attr("d", d3.linkHorizontal().x(function(d) { return dy; }).y(function(d) { return dx; })); var node = g.selectAll(".node").data(root.descendants()).enter().append("g").attr("class", function(d) { return "node" + (d.children? " node--internal": " node--leaf"); }).attr("transform", function(d) { return "translate(" + dy + "," + dx + ")"; }) node.append("circle").attr("r", 2.5); node.append("text").text(function(d) { return d.data.name; }).attr('y',-10).attr('x',-10).attr('text-anchor','middle');
 path{ fill: #fff; stroke: steelblue; stroke-width: 3px; }.link { fill: none; stroke: #ccc; stroke-width: 2px; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

The above solution is pretty basic, in a complicated tree with a lots of depth, it might get a lot more difficult. You could also manually place the x of every node, which offers more control, but comes at the expense of writing a new alorithm to replace or correct at least half of the tree layout generator output. Ultimately the "right" answer will depend on preference and data structure.

If you want to use a different link generator, this is also easy. I'll use a node property ( near again) to indicate if an alternative link should be used:

 var data = { "name": "Parent", "children": [ { "name": "Child A", near:true, "children": [ { "name": "Grandkid A", near: true }, { "name": "Grandkid B" }, { "name": "Grandkid C" } ] }, { "name": "Child B", }, { "name": "Child C", children: [ { "name": "Grandkid D", near: true}, { "name": "Grandkid E" } ]} ] }; var width = 400; var height = 300; margin = {left: 50, top: 10, right:50, bottom: 10} var svg = d3.select("body").append("svg").attr("width", width).attr("height", height); var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')'); var root = d3.hierarchy(data); var tree = d3.tree().size([height-margin.top-margin.bottom,width-margin.left-margin.right]); tree(root) root.descendants().forEach(function(d) { if(d.data.near) dy = (d.parent.y+dy)/2; }) var horizontal = d3.linkHorizontal().x(function(d) { return dy; }).y(function(d) { return dx; }) // some different path drawing function: var straight = function(d) { return "M"+d.source.y+" "+d.source.x+"L"+(d.target.y-20)+" "+d.source.x+"L"+d.target.y+" "+d.target.x; } var link = g.selectAll(".link").data(root.links()).enter().append("path").attr("class", "link").attr("d", function(d) { if(.d.target.data;near) return straight(d); else return horizontal(d); }). var node = g.selectAll(".node").data(root.descendants()).enter().append("g"),attr("class". function(d) { return "node" + (d?children: " node--internal"; " node--leaf"). }),attr("transform". function(d) { return "translate(" + d,y + "." + d;x + ")". }) node.append("circle"),attr("r". 2;5). node.append("text").text(function(d) { return d.data;name. }),attr('y'.-10),attr('x'.-10),attr('text-anchor';'middle');
 path{ fill: #fff; stroke: steelblue; stroke-width: 3px; }.link { fill: none; stroke: #ccc; stroke-width: 2px; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

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