构图(d3.layout)——树图(Tree)的展开和折叠
树的基本操作就是展开和折叠,在D3里面,树的展开和折叠是通过设置树的子节点属性来实现的,tree.nodes(root)返回树的当前节点集合,通过操作这个集合,可以实现节点的动态变化。
- children - the array of child nodes, or null for leaf nodes.
- x - the computed x-coordinate of the node position.
- y - the computed y-coordinate of the node position.
取得节点集合可用如下代码:
var nodes = tree.nodes(root).reverse(); var links = tree.links(nodes);
为了能够区分节点是新加入的还是冗余的,我们为节点数据集指定id属性,然后我们就可以通过这个属性,使用.exit()来获取冗余节点:
var node = svg.selectAll("g.node") .data(nodes,function(d){return d.id|| (d.id = ++index);});
为了能够保证动画的流畅,我们需要记录节点的当前位置,当节点数目变化,产生新的坐标以后,我们使用当前位置过渡到新的位置:
nodes.forEach(function(d) { d.x0 = d.x; d.y0 = d.y; });
树的展开和折叠代码如下:
< script type = "text/javascript" >//图像区域大小
var R = 600;
//动画持续时间
var duration = 1000;
//节点编号
var index = 0;
//动画时长
var duration = 1000;//定义一个Tree对象,定义旋转角度和最大半径
var tree = d3.layout.tree()
.size([360, R / 2 - 120])
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth;
});
//定义布局方向
var diagonal = d3.svg.diagonal()
.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("body").append("svg")
.attr("width", R)
.attr("height", R)
.append("g")
.attr("transform", function(d) {
return "translate(" + R / 2 + "," + R / 2 + ")";
});//根据JSON数据生成树
d3.json("treeDataC.php", function(error, data) {var root = data;
//根据数据生成nodes集合
var nodes = tree.nodes(data);//记录现在的位置
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});//获取node集合的关系集合
var links = tree.links(nodes);//根据node集合生成节点,添加id是为了区分是否冗余的节点
var node = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id || (d.id = ++index);
});//为关系集合设置贝塞尔曲线连接
var link = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
})
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal);node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.on("click", nodeClick);//为节点添加圆形标记,如果有子节点为红色,否则绿色
node.append("circle")
.attr("fill", function(d) {
return d.children == null ? "#0F0" : "#F00";
})
.attr("r", 5);//为节点添加说明文字
node.append("text")
.attr("dy", ".4em")
.text(function(d) {
return d.level;
})
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)";
});//点击的话,隐藏或者显示子节点
function nodeClick(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}//更新显示
function update(source) {
//取得现有的节点数据,因为设置了Children属性,没有Children的节点将被删除
var nodes = tree.nodes(root).reverse();
var links = tree.links(nodes);//为节点更新数据
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++index);
});//为链接更新数据
var link = svg.selectAll("path.link").data(links, function(d) {
return d.target.id;
});//更新链接
link.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
});link.transition()
.duration(duration)
.attr("d", diagonal);//移除无用的链接
link.exit()
.transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();//更新节点集合
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "rotate(" + (source.x0 - 90) + ")translate(" + source.y0 + ")";
})
.on("click", nodeClick);//为节点添加圆形标记,如果有子节点为红色,否则绿色
node.append("circle")
.attr("fill", function(d) {
return d.children == null && d._children == null ? "#0F0" : "#F00";
})
.attr("r", 5);//为节点添加说明文字
node.append("text")
.attr("dy", ".4em")
.text(function(d) {
return d.level;
})
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)";
});//节点动画
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
});//将无用的子节点删除
var nodeExit = node.exit()
.transition()
.duration(duration)
.attr("transform", function(d) {
return "rotate(" + (source.x - 90) + ")translate(" + source.y + ")";
})
.remove();//记录下当前位置,为下次动画记录初始值
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
}); < /script>
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <title>画一个树图(Tree)</title> | |||
| <script type="text/javascript" src="js/d3.js"></script> | |||
| </head> | |||
| <style> | |||
| .node circle { | |||
| stroke: steelblue; | |||
| stroke-width: 1px; | |||
| cursor:pointer; | |||
| } | |||
| .node { | |||
| font: 12px sans-serif; | |||
| display:block; | |||
| } | |||
| .hidenode { | |||
| font: 12px sans-serif; | |||
| display:none; | |||
| } | |||
| .link { | |||
| fill: none; | |||
| stroke: #ccc; | |||
| stroke-width: 1px; | |||
| } | |||
| </style> | |||
| <body> | |||
| <script type="text/javascript"> | |||
| //图像区域大小 | |||
| var R = 600; | |||
| //动画持续时间 | |||
| var duration=1000; | |||
| //节点编号 | |||
| var index=0; | |||
| //定义一个Tree对象,定义旋转角度和最大半径 | |||
| var tree = d3.layout.tree() | |||
| .size([360,R/2-120]) | |||
| .separation(function(a,b) { return (a.parent == b.parent ? 1 : 2)/a.depth;}); | |||
| //定义布局方向 | |||
| var diagonal = d3.svg.diagonal() | |||
| .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("body").append("svg") | |||
| .attr("width", R) | |||
| .attr("height", R) | |||
| .append("g") | |||
| .attr("transform", function(d){ return "translate("+R/2+"," + R/2 + ")";}); | |||
| //根据JSON数据生成树 | |||
| d3.json("treeDataC.php", function(error, data) { | |||
| var root=data; | |||
| //根据数据生成nodes集合 | |||
| var nodes = tree.nodes(data); | |||
| //记录现在的位置 | |||
| nodes.forEach(function(d){ | |||
| d.x0 = d.x; | |||
| d.y0 = d.y; | |||
| }); | |||
| //获取node集合的关系集合 | |||
| var links = tree.links(nodes); | |||
| //根据node集合生成节点,添加id是为了区分是否冗余的节点 | |||
| var node = svg.selectAll(".node") | |||
| .data(nodes,function(d){return d.id|| (d.id = ++index);}); | |||
| //为关系集合设置贝塞尔曲线连接 | |||
| var link=svg.selectAll(".link") | |||
| .data(links, function(d) { return d.target.id;}) | |||
| .enter() | |||
| .append("path") | |||
| .attr("class", "link") | |||
| .attr("d",diagonal); | |||
| node.enter() | |||
| .append("g") | |||
| .attr("class", "node") | |||
| .attr("transform",function(d){return "rotate(" + (d.x-90) + ")translate(" + d.y + ")"; }) | |||
| .on("click",nodeClick); | |||
| //为节点添加圆形标记,如果有子节点为红色,否则绿色 | |||
| node.append("circle") | |||
| .attr("fill",function(d){return d.children==null?"#0F0":"#F00";}) | |||
| .attr("r", 5); | |||
| //为节点添加说明文字 | |||
| node.append("text") | |||
| .attr("dy", ".4em") | |||
| .text(function(d){return d.level;}) | |||
| .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) | |||
| .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }); | |||
| //点击的话,隐藏或者显示子节点 | |||
| function nodeClick(d) | |||
| { | |||
| if (d.children) | |||
| { | |||
| d._children = d.children; | |||
| d.children = null; | |||
| } | |||
| else | |||
| { | |||
| d.children = d._children; | |||
| d._children = null; | |||
| } | |||
| update(d); | |||
| } | |||
| //更新显示 | |||
| function update(source) | |||
| { | |||
| //取得现有的节点数据,因为设置了Children属性,没有Children的节点将被删除 | |||
| var nodes = tree.nodes(root).reverse(); | |||
| var links = tree.links(nodes); | |||
| //为节点更新数据 | |||
| var node = svg.selectAll("g.node") | |||
| .data(nodes,function(d){return d.id|| (d.id = ++index);}); | |||
| //为链接更新数据 | |||
| var link = svg.selectAll("path.link").data(links, function(d) {return d.target.id;}); | |||
| //更新链接 | |||
| link.enter() | |||
| .append("path") | |||
| .attr("class", "link") | |||
| .attr("d", function(d) { | |||
| var o = {x: source.x, y: source.y}; | |||
| return diagonal({source: o, target: o}); | |||
| }); | |||
| link.transition() | |||
| .duration(duration) | |||
| .attr("d",diagonal); | |||
| //移除无用的链接 | |||
| link.exit() | |||
| .transition() | |||
| .duration(duration) | |||
| .attr("d", function(d) { | |||
| var o = {x: source.x, y: source.y}; | |||
| return diagonal({source: o, target: o}); | |||
| }) | |||
| .remove(); | |||
| //更新节点集合 | |||
| var nodeEnter = node.enter() | |||
| .append("g") | |||
| .attr("class", "node") | |||
| .attr("transform",function(d){return "rotate(" + (source.x0-90) + ")translate(" + source.y0 + ")"; }) | |||
| .on("click",nodeClick); | |||
| //为节点添加圆形标记,如果有子节点为红色,否则绿色 | |||
| node.append("circle") | |||
| .attr("fill",function(d){return d.children==null && d._children==null?"#0F0":"#F00";}) | |||
| .attr("r", 5); | |||
| //为节点添加说明文字 | |||
| node.append("text") | |||
| .attr("dy", ".4em") | |||
| .text(function(d){return d.level;}) | |||
| .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) | |||
| .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }); | |||
| //节点动画 | |||
| var nodeUpdate = node.transition() | |||
| .duration(duration) | |||
| .attr("transform", function(d) { return "rotate(" + (d.x-90) + ")translate(" + d.y + ")"; }); | |||
| //将无用的子节点删除 | |||
| var nodeExit =node.exit() | |||
| .transition() | |||
| .duration(duration) | |||
| .attr("transform", function(d){return "rotate(" + (source.x-90) + ")translate(" + source.y + ")"; }) | |||
| .remove(); | |||
| //记录下当前位置,为下次动画记录初始值 | |||
| nodes.forEach(function(d) { | |||
| d.x0 = d.x; | |||
| d.y0 = d.y; | |||
| }); | |||
| } | |||
| }); | |||
| </script> | |||
| </body> | |||
|
</html>
|