<!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>
这是一个非常好的开始!然而,弧线都有自己的路径,当你使用大量数据时,它们看起来并不像它们在同一弧线上(http://i.imgur.com/GxnmWsZ.png)。可能解决这个问题的方法是,不会从线条末端绘制弧线,而是始终绘制连接到每个儿童的相同外部圆,以便只改变大小,而不是半径或中心。我会尽力建立在这个基础上,但随时更新它,我会接受你的答案。 – MorganFR
也许进一步提高中点位置(找到合适的位置)可以解决问题吗?我的数学非常糟糕,所以我目前正试图弄清楚如何移动中点,以及弧的起点。 – MorganFR
@MorganFR,检查上面的更新。修复您提出的问题。这是一个更好的实现。 – Mark