我正在制作Mike Bostock的collapsible tree的径向版本。
我希望每组分支都有一个分支,而不是每个元素分支。
var flare
中额外注释的行只是用于密度测试的额外行,用于查看大量信息的外观,如预期的那样。
白色圆圈用于隐藏根元素和白色背景上第一级的链接。要删除它,只需删除以下行:
var circle = svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", radius - 5)
.style("fill", "white");
从root到1级的工作原理无关紧要,只要1级元素形成一个圆(这里有3个1级元素),因为我仍然会将0级(根)隐藏到1级
但是,父元素的间距必须根据折叠的孩子而变化。
答案 0 :(得分:3)
这是我对你的插图的看法。从本质上讲,它是一个非常自定义的路径生成器。我试图对此发表评论,如果您有任何疑问,请告诉我。
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];
}
});
更新了小提琴here。
评论更新
检查此版本。它删除了我写的自定义弧,而是使用d3.svg.arc生成器。它还将绘图限制为仅删除冗余路径的第一个和最后一个子节点。
<!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 = d.y, a = (d.x - 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) {
d.y = 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(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.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 d.x < 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(" + (d.x - 90) + ")translate(" + d.y + ")";
})
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 d.x < 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 += c.x;
});
// 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 = d.x;
d.y0 = d.y;
});
}
// 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版本here。