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.
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");
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.