樹狀布局Tree-Layout簡介
顯而易見,樹狀布局就是我們通常所理解的數(shù)據(jù)結(jié)構(gòu)中的樹,由一個根節(jié)點(diǎn)向外展開,與前面力導(dǎo)向圖的數(shù)據(jù)結(jié)構(gòu)模型其實(shí)是一致的,只是展示形式略有差別,用樹狀結(jié)構(gòu)展示看起來更加簡潔清晰一點(diǎn)。
API
樹結(jié)構(gòu)
-
d3.layout.tree()
使用默認(rèn)設(shè)置創(chuàng)建一個新的樹狀布局:
-
tree.nodes(root)
根據(jù)傳入數(shù)據(jù)計算樹的布局返回一組節(jié)點(diǎn)數(shù)組
-
tree.links(nodes)
根據(jù)節(jié)點(diǎn)數(shù)組返回節(jié)點(diǎn)之間的父子連接關(guān)系
-
tree.separation([separation]):
設(shè)置或取得兩個節(jié)點(diǎn)之間的間距
-
tree.size([size])
指定樹布局的尺寸一個
svg形狀
因?yàn)樾枰獙?shí)現(xiàn)一個輻射布局,并且連線是貝塞爾曲線,需要用到坐標(biāo)投影來實(shí)現(xiàn)路徑形狀生成。
路徑形狀有很多種選擇,比如d3.svg.line()、d3.svg.area()、d3.svg.diagonal()等等。這里選用d3.svg.diagonal(),它能利用projection的API來生成svg中的path形狀的路徑。
- d3.svg.diagonal.radial().projection()
示例代碼
根據(jù)官網(wǎng)一些人提供的Demo修改來寫了一個輻射布局。里面做了一些注釋,但仍然有一些疑惑點(diǎn),比如d.x-90那里的原因還是比較模糊。
下面是完整demo代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tree layout</title>
<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;
}
</style>
</head>
<body>
<script src="http://cdn.bootcss.com/d3/3.2.2/d3.v3.min.js"></script>
<script>
var width = 800,
height = 800;
var i = 0,
duration = 6000,
root;
var tree = d3.layout.tree()
.size([360, 320]) //360代表展開的最大角度也就是圓,后面的r值代表整個輻射布局的展開最大半徑,并不是指某一層級的半徑而是整個樹,超過這個范圍的就不顯示了,后面85行d.y的才是某一層級的半徑設(shè)置。
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth; //適應(yīng)radial布局,可以簡單的理解為深度越高,相鄰子節(jié)點(diǎn)之間的距離越小。
});
var diagonal = d3.svg.diagonal.radial()
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); //d.y代表半徑,(d.x/180)*PI就是各個節(jié)點(diǎn)的弧度表示。
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.json("flare1.json", function(error, flare) {
if (error) throw error;
root = flare;
root.x0 = height / 2; //由于是輻射布局這邊當(dāng)y0設(shè)置為0,那么這個角度x0隨便設(shè)置也無所謂 因?yàn)榭隙ㄊ窃趫A心。這里的理解不知道對不對
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
});
// // Hack to make this example display correctly in an iframe on bl.ocks.org 為了在iframe中正常顯示而已。
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. 根據(jù)深度來給定相應(yīng)半徑,也就是深度越大半徑越長。
nodes.forEach(function(d) { d.y = d.depth * 180; });
// 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 "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
// .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })//小于180度的文字放在前面,否則放在后面
.attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; })
// .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.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 + ")"; })
//translate 與 rotate 的作用效果是怎么樣的???
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.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.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
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);
}
</script>
</body>
</html>
json數(shù)據(jù)文件flare1.json如下
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{"name": "AgglomerativeCluster", "size": 3938},
{"name": "CommunityStructure", "size": 3812},
{"name": "HierarchicalCluster", "size": 6714},
{"name": "MergeEdge", "size": 743}
]
},
{
"name": "graph",
"children": [
{"name": "BetweennessCentrality", "size": 3534},
{"name": "LinkDistance", "size": 5731},
{"name": "MaxFlowMinCut", "size": 7840},
{"name": "ShortestPaths", "size": 5914},
{"name": "SpanningTree", "size": 3416}
]
},
{
"name": "optimization",
"children": [
{"name": "AspectRatioBanker", "size": 7074}
]
}
]
},
{
"name": "animate",
"children": [
{"name": "Easing", "size": 17010},
{"name": "FunctionSequence", "size": 5842},
{
"name": "interpolate",
"children": [
{"name": "ArrayInterpolator", "size": 1983},
{"name": "ColorInterpolator", "size": 2047},
{"name": "DateInterpolator", "size": 1375},
{"name": "Interpolator", "size": 8746},
{"name": "MatrixInterpolator", "size": 2202},
{"name": "NumberInterpolator", "size": 1382},
{"name": "ObjectInterpolator", "size": 1629},
{"name": "PointInterpolator", "size": 1675},
{"name": "RectangleInterpolator", "size": 2042}
]
},
{"name": "ISchedulable", "size": 1041},
{"name": "Parallel", "size": 5176},
{"name": "Pause", "size": 449},
{"name": "Scheduler", "size": 5593},
{"name": "Sequence", "size": 5534},
{"name": "Transition", "size": 9201},
{"name": "Transitioner", "size": 19975},
{"name": "TransitionEvent", "size": 1116},
{"name": "Tween", "size": 6006}
]
},
{
"name": "data",
"children": [
{
"name": "converters",
"children": [
{"name": "Converters", "size": 721},
{"name": "DelimitedTextConverter", "size": 4294},
{"name": "GraphMLConverter", "size": 9800},
{"name": "IDataConverter", "size": 1314},
{"name": "JSONConverter", "size": 2220}
]
},
{"name": "DataField", "size": 1759},
{"name": "DataSchema", "size": 2165},
{"name": "DataSet", "size": 586},
{"name": "DataSource", "size": 3331},
{"name": "DataTable", "size": 772},
{"name": "DataUtil", "size": 3322}
]
}
]
}
參考資料
1.官方手冊