【发布时间】:2014-12-30 15:22:06
【问题描述】:
正在用 d3 绘制一个缩进树。我从Mike Rostock's code 开始并进行了一些修改,以便:1)显示一个右/下箭头,除了叶子; 2)在每一行添加一个复选框; 3) 隐藏根节点。
代码如下,它接受任何数据,我用两个参数调用 drawIntentedTree 函数:根节点和绘制树的 div id。
如图所示,代码中存在一些问题,希望得到帮助: 1. root/start 节点在扩展一个树枝的同时被重绘,导致左右箭头重叠,见 SCL 线。 2. 复选框也出现了类似的问题,它基本上是一个隐藏在白色矩形上的透明 x。我的第一个意图是用笔触颜色填充框,但必须弄清楚每行的 css 颜色是什么,因为它会发生变化。
除了解决这两个问题之外,我还打算在节点之间绘制直线,但原始代码改为绘制卷线,并允许在折叠和展开之间使用 45° 旋转箭头的中间状态(部分折叠),仅显示分支中的复选框。此外,我希望在扩展另一个分支时折叠或部分折叠分支以避免向下滚动。
Mike Bostock 正在使用一种技巧来显示/隐藏树的一部分,他在 _children 中备份子项,然后将子项分配给 null 以隐藏折叠的分支,但重绘总是从根节点开始,我没有设法: 1)避免根节点重绘; 2) 将已存在的左三角形旋转 90 或 90°。
一篇文章中有很多问题,我将不胜感激任何方面的帮助。 jsfiddle link.
d3js 代码:
function drawIndentedTree(root, wherein) {
var width = 300, minHeight = 800;
var barHeight = 20, barWidth = 50;
var margin = {
top: -10,
bottom: 10,
left: 0,
right: 10
}
var i = 0, duration = 200;
var tree = d3.layout.tree()
.nodeSize([0, 20]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("#"+wherein).append("svg")
.attr("width", width + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// set initial coordinates
root.x0 = 0;
root.y0 = 0;
// collapse all nodes recusively, hence initiate the tree
function collapse(d) {
d.Selected = false;
if (d.children) {
d.numOfChildren = d.children.length;
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
else {
d.numOfChildren = 0;
}
}
root.children.forEach(collapse);
update(root);
function update(source) {
// Compute the flattened node list. TODO use d3.layout.hierarchy.
var nodes = tree.nodes(root);
height = Math.max(minHeight, nodes.length * barHeight + margin.top + margin.bottom);
d3.select("svg").transition()
.duration(duration)
.attr("height", height);
d3.select(self.frameElement).transition()
.duration(duration)
.style("height", height + "px");
// Compute the "layout".
nodes.forEach(function(n, i) {
n.x = i * barHeight;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.index || (d.index = ++i); });
var nodeEnter = node.enter().append("g").filter(function(d) { return d.id != root.id })
.attr("class", "node")
.style("opacity", 0.001)
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
});
// Enter any new nodes at the parent's previous position.
nodeEnter.append("path").filter(function(d) { return d.numOfChildren > 0 && d.id != root.id })
.attr("width", 9)
.attr("height", 9)
.attr("d", "M -3,-4, L -3,4, L 4,0 Z")
.attr("class", function(d) { return "node "+d.type; } )
.attr("transform", function(d) {
if (d.children) {
return "translate(-14, 0)rotate(90)";
}
else {
return "translate(-14, 0)rotate(0)";
}
})
.on("click", click);
// Enter any new nodes at the parent's previous position.
nodeEnter.append("rect").filter(function(d) { return d.id != root.id })
.attr("width", 11)
.attr("height", 11)
.attr("y", -5)
.attr("class", function(d) { return "node "+d.type; } );
// check box filled with 'x' or '+'
nodeEnter.append("text")
.attr("dy", 4)
.attr("dx", 2)
.attr("class", function(d) { return "node "+d.type+" text"; } )
.text("x");
nodeEnter.append("rect").filter(function(d) { return d.parent })
.attr("width", 9)
.attr("height", 9)
.attr("x", 1)
.attr("y", -4)
.attr("class", "node select")
.attr("style", function(d) { return "fill: "+boxStyle(d) })
.on("click", check);
nodeEnter.append("text")
.attr("dy", 5)
.attr("dx", 14)
.attr("class", function(d) { return "node "+d.type+" text"; } )
.text(function(d) { return d.Name; });
// Transition nodes to their new position.
nodeEnter.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.style("opacity", 1);
node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.style("opacity", 1)
.select("rect");
// Transition exiting nodes to the parent's new position.
node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.style("opacity", 1e-6)
.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) {
d3.select(this).attr("translate(-14, 0)rotate(90)");
d._children = d.children;
d.children = null;
} else if (d._children) {
d.children = d._children;
d._children = null;
}
update(d);
}
// Toggle check box on click.
function check(d) {
d.Selected = !d.Selected;
d3.select(this).style("fill", boxStyle(d));
}
function boxStyle(d) {
return d.Selected ? "transparent" : "white";
}
}
var wherein = "chart";
var root = {
"name": "AUT-1",
"children": [
{
"name": "PUB-1","children": [
{"name": "AUT-11","children": [
{"name": "AFF-111"},
{"name": "AFF-112"}
]},
{"name": "AUT-12","children": [
{"name": "AFF-121"}
]},
{"name": "AUT-13","children": [
{"name": "AFF-131"},
{"name": "AFF-132"}
]},
{"name": "AUT-14","children": [
{"name": "AFF-141"}
]}
]
},
{
"name": "PUB-2","children": [
{"name": "AUT-21"},
{"name": "AUT-22"},
{"name": "AUT-23"},
{"name": "AUT-24"},
{"name": "AUT-25"},
{"name": "AUT-26"},
{"name": "AUT-27"},
{"name": "AUT-28","children":[
{"name": "AFF-281"},
{"name": "AFF-282"},
{"name": "AFF-283"},
{"name": "AFF-284"},
{"name": "AFF-285"},
{"name": "AFF-286"}
]}
]
},
{"name": "PUB-3"},
{
"name": "PUB-4","children": [
{"name": "AUT-41"},
{"name": "AUT-42"},
{"name": "AUT-43","children": [
{"name": "AFF-431"},
{"name": "AFF-432"},
{"name": "AFF-433"},
{"name": "AFF-434","children":[
{"name": "ADD-4341"},
{"name": "ADD-4342"},
]}
]},
{"name": "AUT-44"}
]
}
]
};
CSS:
.node {
font: 12px sans-serif;
fill: #ccebc5;
stroke: #7c9b75;
stroke-width: 1px;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
cursor: pointer;
}
.node rect {
width: 11px;
height: 11px;
cursor: pointer;
}
.node.select {
width: 9px;
height: 9px;
cursor: pointer;
fill: red;
stroke-width: 0px;
}
.node path {
width: 11px;
height: 11px;
cursor: pointer;
}
.node text Panel {
stroke: #08519c;
stroke-width: 0.5px;
}
.node text Cell {
stroke: #a50f15;
stroke-width: 0.5px;
}
.node.Root {
fill: #f7f7f7;
stroke: #505050;
stroke-width: 1.0px;
}
.node.Root.text {
fill: #505050;
stroke-width: 0px;
font-size: 10px;
font-family: sans-serif;
}
.node.Panel {
fill: #eff3ff;
stroke: #08519c;
stroke-width: 1.0px;
}
.node.Panel.text {
fill: #08519c;
stroke-width: 0px;
font-size: 12px;
font-family: sans-serif;
}
.node.Cell {
fill: #fee5d9;
stroke: #a50f15;
stroke-width: 1.0px;
}
.node.Cell.text {
fill: #a50f15;
stroke-width: 0px;
font-size: 12px;
font-family: sans-serif;
}
【问题讨论】:
-
您能否用您的
styles和您的数据示例更新您的问题? -
我觉得这个说法不太正确“但是重绘总是从根节点开始”。进入节点最初是在 root 的位置开始的,但这仅适用于 root 的子节点。稍后每个节点都在其父位置启动(因为
update(d)在click函数中)。还值得注意的是,更新节点的 html 不会重新生成。因此,他们从他们所在的位置开始,然后转换(翻译)到他们的新位置(参见注释为// Transition nodes to their new position.的块)。
标签: d3.js