简体   繁体   中英

D3.js radial collapsible radial tree (1 branch per ramification)

I am making a radial version of the collapsible tree by Mike Bostock.

I would like to have a single branch per group of ramification instead of a branch per element.

Here is what I have so far: 在此输入图像描述

The JSFiddle for my current representation is here .

The extra commented lines in the var flare are just extra lines meant for density testing, to see how it looks with a lot of info, as it is intended.

The white circle is used to hide the root element and the links to the first level on a white background. To remove it, simply delete the following lines:

var circle = svg.append("circle")
    .attr("cx", 0)
    .attr("cy", 0)
    .attr("r", radius - 5)
    .style("fill", "white");

Below is what I want to have: 在此输入图像描述

How it works from root to level 1 does not matter, as long as the level 1 elements form a circle (here with 3 level 1 elements), as I will still be hiding level 0 (root) to level 1

However, it is important that the spacing of the parent element changes according to the children being collapsed or not.

Here's my take on your illustration. Essentially, it's a very custom path generator. I've attempted to comment it well, so let me know if you have any questions.

link.transition()
    .duration(duration)
    .attr("d", function(d) {

      // depth zero, don't draw, this is your "hidden" links
      if (d.source.depth === 0) return "";

      // if we have children
      if (d.source.children) {

        // sum the angles to find the midpoint of the children
        var pad = 20, //<-- pad is the step off distance to children
           sum = 0;
        d.source.children.forEach(function(c) {
          sum += c.x;
        });

        // this is the mid point position
        var ma = ((sum / d.source.children.length) - 90) * (Math.PI / 180),
          mr = d.source.children[0].y - pad,
          mid = [mr * Math.cos(ma), mr * Math.sin(ma)]; //x,y position

        // this is the source position
        var sa = (d.source.x - 90) * (Math.PI / 180),
          sr = d.source.y - pad,
          source = [sr * Math.cos(sa), sr * Math.sin(sa)];

        // this is the final target position
        var ta = (d.target.x - 90) * (Math.PI / 180),
          tr = d.target.y - pad,
          target = [tr * Math.cos(ta), tr * Math.sin(ta)];

        // this is the arc from mid to target
        var dx = target[0] - source[0],
          dy = target[1] - source[1],
          dr = Math.sqrt(dx * dx + dy * dy);

        // this is the line from the source, to the mid and arced to children
        return "M" + source +
          "L" + mid +
          "A" + dr + "," + dr + " 0 0," + (ma < ta ? 1 : 0) + " " + target[0] + "," + target[1];
      }
    });

Updated fiddle here .

UPDATES TO COMMENTS

Check this version out. It drops the custom arc I wrote and instead uses a d3.svg.arc generator. It also limits the drawing to just the first and last child to remove redundant paths.

 <!DOCTYPE html> <html> <head> <style> .node { cursor: pointer; } .node circle { fill: #fff; stroke: steelblue; stroke-width: 1.5px; } .node text { font: 10px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 1.5px; } #svg { height: 500px; width: 500px; } </style> </head> <body> <script src="//d3js.org/d3.v3.min.js"></script> <div id="svg"></div> <script> flare = { "name": "root", "children": [{ "name": "item1", "children": [{ "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item2" }, { "name": "item3" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item4", "children": [{ "name": "item5" }, { "name": "item6" }, { "name": "item7" }] }, { "name": "item8", "children": [{ "name": "item9" }, { "name": "item10" }] } ] }; //variables used to modify some basic properties of the svg elements var divHeight = document.getElementById('svg').offsetHeight; var divWidth = document.getElementById('svg').clientWidth; var radius = 75; var separation = 2; var diameter = 800; var margin = { top: 20, right: 120, bottom: 20, left: 120 }, width = diameter, height = diameter; var i = 0, duration = 350, root; var tree = d3.layout.tree() .size([360, diameter / 2 - 80]) .separation(function(a, b) { return (a.parent == b.parent ? 1 : separation) / a.depth; }); //last line is the separation between branches of the tree when clicked var diagonal = d3.svg.line.radial(); // .projection(function(d) { var r = dy, a = (dx - 90) / 180 * Math.PI; // return [r * Math.cos(a), r * Math.sin(a)]; }); var svg = d3.select("#svg").append("svg") //.attr("width", width) //.attr("height", height) .attr("width", divWidth) .attr("height", divHeight) .call(d3.behavior.zoom().on("zoom", function() { svg.attr("transform", "translate(" + d3.event.translate[0] + "," + d3.event.translate[1] + ")" + " scale(" + d3.event.scale + ")") })).on("dblclick.zoom", null) .append("g") .attr("transform", "translate(" + divWidth / 2 + "," + divHeight / 2 + ")") .append("g"); //alert("W = " + divWidth + ", H = " + divHeight); root = flare; root.x0 = height / 2; root.y0 = 0; root.children.forEach(collapse); // start with all children collapsed update(root); //create a circle in the center to remove root and first level of links var circle = svg.append("circle") .attr("cx", 0) .attr("cy", 0) .attr("r", radius - 5) .style("fill", "white"); d3.select(self.frameElement).style("height", "800px"); function update(source) { // Compute the new tree layout. var nodes = tree.nodes(root), links = tree.links(nodes); // Normalize for fixed-depth. nodes.forEach(function(d) { dy = d.depth * radius; }); // Update the nodes… var node = svg.selectAll("g.node") .data(nodes, function(d) { return d.id || (d.id = ++i); }); // Enter any new nodes at the parent's previous position. var nodeEnter = node.enter().append("g") .attr("class", "node") //.attr("transform", function(d) { return "rotate(" + (dx - 90) + ")translate(" + dy + ")"; }) .on("click", click); nodeEnter.append("circle") .attr("r", 1e-6) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeEnter.append("text") .attr("x", 10) .attr("dy", ".35em") .attr("text-anchor", "start") //.attr("transform", function(d) { return dx < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length * 8.5) + ")"; }) .text(function(d) { return d.name; }) .style("fill-opacity", 1e-6); // Transition nodes to their new position. var nodeUpdate = node.transition() .duration(duration) .attr("transform", function(d) { return "rotate(" + (dx - 90) + ")translate(" + dy + ")"; }) nodeUpdate.select("circle") .attr("r", 4.5) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeUpdate.select("text") .style("fill-opacity", 1) .attr("transform", function(d) { return dx < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length + 50) + ")"; }); // TODO: appropriate transform var nodeExit = node.exit().transition() .duration(duration) //.attr("transform", function(d) { return "diagonal(" + source.y + "," + source.x + ")"; }) .remove(); nodeExit.select("circle") .attr("r", 1e-6); nodeExit.select("text") .style("fill-opacity", 1e-6); // Update the links… var link = svg.selectAll("path.link") .data(links, function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. link.enter().insert("path", "g") .attr("class", "link") .attr("d", function(d) { var o = { x: source.x0, y: source.y0 }; return diagonal({ source: o, target: o }); }); // Transition links to their new position. var arc = d3.svg.arc(); link.transition() .duration(duration) .attr("d", function(d) { if (d.source.depth === 0) return ""; if (d.source.children) { if (d.source.children[0] !== d.target && d.source.children[d.source.children.length - 1] !== d.target) return "" var pad = 10, sum = 0; d.source.children.forEach(function(c) { sum += cx; }); // this is the mid point position var ma1 = ((sum / d.source.children.length) - 90) * (Math.PI / 180), ma2 = ((sum / d.source.children.length)) * (Math.PI / 180), mr = d.source.children[0].y - pad, mid = [mr * Math.cos(ma1), mr * Math.sin(ma1)]; // this is the source position var sa = (d.source.x - 90) * (Math.PI / 180), sr = d.source.y - pad, source = [sr * Math.cos(sa), sr * Math.sin(sa)]; // this is the final target position var ta = (d.target.x) * (Math.PI / 180), tr = d.target.y - pad, target = [tr * Math.cos(ta), tr * Math.sin(ta)]; // this is the arc from mid to target var dx = target[0] - source[0], dy = target[1] - source[1], dr = Math.sqrt(dx * dx + dy * dy); arc.innerRadius(tr-1) .outerRadius(tr) .startAngle(ma2) .endAngle(ta); console.log(arc()) return "M" + source + "L" + mid + arc(); /* return "M" + source + "L" + mid + "A" + dr + "," + dr + " 0 0," + (ma < ta ? 1 : 0) + " " + target[0] + "," + target[1]; */ } }); // Transition exiting nodes to the parent's new position. link.exit().transition() .duration(duration) .attr("d", function(d) { var o = { x: source.x, y: source.y }; return diagonal({ source: o, target: o }); }) .remove(); // Stash the old positions for transition. nodes.forEach(function(d) { d.x0 = dx; d.y0 = dy; }); } // 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); } // Collapse nodes function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; } } </script> </body> </html> 

Plunker version here .

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