利用我有限的D3知识,我结合了一些树布局示例来制作带有分支标签的可折叠树。这是功能提取:
var root = {
children:[
{
title:"Node title",
children:[
{
type:"end",
items:[],
optionTitle:"Branch 1"
},
{
type:"end",
items:[],
optionTitle:"Branch 2"
}
]
}
]
}
var maxLabelLength = 23;
var i = 0;
var duration = 750;
// Define the root
root.x0 = viewerHeight / 2;
root.y0 = 0;
var viewerWidth = 800;
var viewerHeight = 300;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
var baseSvg = d3.select('.tree').append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "tree-container");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function centerNode(source) {
var scale = 1;
var x = 20;
var y = -source.x0;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
}
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
expand(d);
}
return d;
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
if (d.children.length == 1) {
expand(d.children[0])
}
}
}
function click(d) {
if (d._children) {
if (d.type!='end') {
expandCollapse(d);
}
} else {
expandCollapse(d);
}
}
function expandCollapse(d) {
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25;
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 8));
if (d.x<root.x) {
d.x -= (root.x-d.x)*3;
} else if (d.x>root.x) {
d.x += (d.x-root.x)*3;
}
});
// Update the nodes…
var node = svgGroup.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('class', 'nodeCircle');
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", 6)
.style("fill", function(d) {
return getNodeFill(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.title;
})
.style("fill-opacity", 0);
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.text(function(d) {
if (d.type!='end') {
return d.title
} else {
return 'End node'
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
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 "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.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.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Update the link text
var linktext = svgGroup.selectAll("g.link")
.data(links, function (d) {
return d.target.id;
});
linktext.enter()
.insert("g")
.attr("class", "link")
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.target.optionTitle;
});
// Transition link text to their new positions
linktext.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
})
//Transition exiting link text to the parent's new position.
linktext.exit().transition().remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
var svgGroup = baseSvg.append("g");
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
svgGroup
.append('defs')
.append('pattern')
.attr('id', function(d,i){
return 'pic_plus';
})
.attr('height',60)
.attr('width',60)
.attr('x',0)
.attr('y',0)
.append('image')
.attr('xlink:href',function(d,i){
return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
})
.attr('height',12)
.attr('width',12)
.attr('x',0)
.attr('y',0);
function getNodeFill(d) {
if (isFinal(d)) {
return '#0f0';
} else if (d._children || (!d._children&&!d.children)) {
return 'url(#pic_plus)'
} else {
return '#fff'
}
}
function isFinal(node) {
return node.type=='end';
}
body {
background-color: #ddd;
}
.tree-custom,
.tree {
width:100%;
height: 100%;
background-color: #fff;
}
.holder {
margin: 0 auto;
width: 1000px;
height: 800px;
background-color: #fff;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link text {
font: 10px sans-serif;
fill: #666;
}
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div class="tree"></div>
<script src="code.js"></script>
</body>
</html>
在我的代码中,节点是圆圈,节点旁边是节点标签:
当我折叠一个节点时,会显示一个plus
标志。
现在,我正尝试将节点标签放在此example code中所示的框中。
我知道我必须像示例代码中那样将circles
更改为foreignObjects
,但是当我这样做时,路径并没有调整到框内。
如何将circles
更改为foreignObjects
,并保持相同的扩展/折叠/加号功能?
答案 0 :(得分:1)
您需要对当前布局进行的更改是
移动蓝色框,使其与圆垂直对齐,并且其右边缘与每个圆的左侧相邻;
更改路径以在每个蓝色框的左侧终止
第一个可以使用变换来更改蓝色框的位置,第二个可以通过更改每条线的目标点的坐标来实现。
查看链接到的样本,在rect
元素后面有foreignObject
个元素提供了颜色,并且foreignObject
从{{1}的位置偏移了}元素。我自由地将rect
元素添加到您的代码中,并将rect
和rect
元素分组在一起,以便可以在单个转换中移动它们:
foreignObject
如果您查看生成的树,则var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp');
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('width', rectNode.width)
.attr('height', rectNode.height)
.attr('class', 'node-rect');
rectGrpEnter.append('foreignObject')
.attr('x', rectNode.textMargin)
.attr('y', rectNode.textMargin)
.attr('width', function() {
return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.width - rectNode.textMargin * 2)
})
.attr('height', function() {
return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.height - rectNode.textMargin * 2)
})
.append('xhtml').html(function(d) {
return '<div style="width: ' +
(rectNode.width - rectNode.textMargin * 2) + 'px; height: ' +
(rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' +
'<b>' + d.title + '</b>' +
'</div>';
});
/ rect
组需要沿x方向平移foreignObject
元素的长度+ rect
半径轴},并将circle
元素沿y轴的高度减半。因此,首先让我们添加一个代表圆半径的变量,然后用该变量替换硬编码数字:
rect
现在编写转换:
var circleRadius = 6;
// a bit further on
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
检查生成的树:
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp')
.attr('transform', 'translate('
+ -(rectNode.width + circleRadius) + ',' // note the transform is negative
+ -(rectNode.height/2) + ')' );
var rectNode = {
width: 120,
height: 45,
textMargin: 5
};
var root = {
slideId: 100,
children: [{
title: "Node title",
children: [{
type: "end",
items: [],
optionTitle: "Branch 1"
},
{
type: "end",
items: [],
optionTitle: "Branch 2"
}
]
}]
}
var maxLabelLength = 23;
var i = 0;
var duration = 750;
// Define the root
root.x0 = viewerHeight / 2;
root.y0 = 0;
var viewerWidth = 800;
var viewerHeight = 300;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
var baseSvg = d3.select('.tree').append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "tree-container");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function centerNode(source) {
var scale = 1;
var x = 20;
var y = -source.x0;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
}
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
expand(d);
}
return d;
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
if (d.children.length == 1) {
expand(d.children[0])
}
}
}
function click(d) {
if (d._children) {
if (d.type != 'end') {
expandCollapse(d);
}
} else {
expandCollapse(d);
}
}
function expandCollapse(d) {
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25;
var circleRadius = 6;
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 8));
if (d.x < root.x) {
d.x -= (root.x - d.x) * 3;
} else if (d.x > root.x) {
d.x += (d.x - root.x) * 3;
}
});
// Update the nodes…
var node = svgGroup.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);
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp')
.attr('transform', 'translate('
+ -(rectNode.width + circleRadius) + ',' // note the transform is negative
+ -(rectNode.height/2) + ')' );
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('width', rectNode.width)
.attr('height', rectNode.height)
.attr('class', 'node-rect');
rectGrpEnter.append('foreignObject')
.attr('x', rectNode.textMargin)
.attr('y', rectNode.textMargin)
.attr('width', function() {
return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.width - rectNode.textMargin * 2)
})
.attr('height', function() {
return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.height - rectNode.textMargin * 2)
})
.append('xhtml').html(function(d) {
return '<div style="width: ' +
(rectNode.width - rectNode.textMargin * 2) + 'px; height: ' +
(rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' +
'<b>' + d.title + '</b>' +
'</div>';
});
nodeEnter.append("circle")
.attr('class', 'nodeCircle');
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.title;
})
.style("fill-opacity", 0);
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.text(function(d) {
if (d.type != 'end') {
return d.title
} else {
return 'End node'
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
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 "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.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.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Update the link text
var linktext = svgGroup.selectAll("g.link")
.data(links, function(d) {
return d.target.id;
});
linktext.enter()
.insert("g")
.attr("class", "link")
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.target.optionTitle;
});
// Transition link text to their new positions
linktext.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
})
//Transition exiting link text to the parent's new position.
linktext.exit().transition().remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
var svgGroup = baseSvg.append("g");
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
svgGroup
.append('defs')
.append('pattern')
.attr('id', function(d, i) {
return 'pic_plus';
})
.attr('height', 60)
.attr('width', 60)
.attr('x', 0)
.attr('y', 0)
.append('image')
.attr('xlink:href', function(d, i) {
return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
})
.attr('height', 12)
.attr('width', 12)
.attr('x', 0)
.attr('y', 0);
function getNodeFill(d) {
if (isFinal(d)) {
return '#0f0';
} else if (d._children || (!d._children && !d.children)) {
return 'url(#pic_plus)'
} else {
return '#fff'
}
}
function isFinal(node) {
return node.type == 'end';
}
function isCollapsed(node) {
return d._children || (!d._children && !d.children);
}
body {
background-color: #ddd;
}
.tree-custom,
.tree {
width: 100%;
height: 100%;
background-color: #fff;
}
.holder {
margin: 0 auto;
width: 1000px;
height: 800px;
background-color: #fff;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link text {
font: 10px sans-serif;
fill: #666;
}
.node-rect {
fill: #00f;
}
.node-text {
color: #fff;
}
下一个任务是更改链接,使其在<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div class="tree"></div>
</body>
</html>
框的边缘终止。如果检查覆盖链接位置和过渡的代码,则rect
和enter
选择都将使用exit
节点的位置。我们感兴趣的代码是这样的:
source
对角函数在哪里
link.transition()
.duration(duration)
.attr("d", diagonal);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
采用以下形式的对象
d3.svg.diagonal()
,如果您查看{ source: { x: 10, y: 10 }, target: { x: 20, y: 50 } }
数组中的每个项目,就会发现它的格式为
tree.links
因此,要更改链接目标的位置,我们需要创建一个{ source: { /* source node coordinates */ }, target: { /* target node coords */ }
坐标已更改的新对象。同样,x轴更改应为target
; y轴正常。请注意,尽管-(rectNode.width + circleRadius)
函数切换了diagonal
和x
的值,所以我们需要更改目标的y
值,而不是y
值。因此,我们有:
x
将其添加到我们的代码中:
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d) {
return diagonal({
source: d.source, // this is the same
target: { x: d.target.x, y: d.target.y - (rectNode.width + circleRadius) }
});
});
var rectNode = {
width: 120,
height: 45,
textMargin: 5
};
var root = {
slideId: 100,
children: [{
title: "Node title",
children: [{
type: "end",
items: [],
optionTitle: "Branch 1"
},
{
type: "end",
items: [],
optionTitle: "Branch 2"
}
]
}]
}
var maxLabelLength = 23;
var i = 0;
var duration = 750;
// Define the root
root.x0 = viewerHeight / 2;
root.y0 = 0;
var viewerWidth = 800;
var viewerHeight = 300;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
var baseSvg = d3.select('.tree').append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "tree-container");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function centerNode(source) {
var scale = 1;
var x = 20;
var y = -source.x0;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
}
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
expand(d);
}
return d;
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
if (d.children.length == 1) {
expand(d.children[0])
}
}
}
function click(d) {
if (d._children) {
if (d.type != 'end') {
expandCollapse(d);
}
} else {
expandCollapse(d);
}
}
function expandCollapse(d) {
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25;
var circleRadius = 6;
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 8));
if (d.x < root.x) {
d.x -= (root.x - d.x) * 3;
} else if (d.x > root.x) {
d.x += (d.x - root.x) * 3;
}
});
// Update the nodes…
var node = svgGroup.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);
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp')
.attr('transform', 'translate(' +
-(rectNode.width + circleRadius) + ',' // note the transform is negative
+
-(rectNode.height / 2) + ')');
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('width', rectNode.width)
.attr('height', rectNode.height)
.attr('class', 'node-rect');
rectGrpEnter.append('foreignObject')
.attr('x', rectNode.textMargin)
.attr('y', rectNode.textMargin)
.attr('width', function() {
return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.width - rectNode.textMargin * 2)
})
.attr('height', function() {
return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.height - rectNode.textMargin * 2)
})
.append('xhtml').html(function(d) {
return '<div style="width: ' +
(rectNode.width - rectNode.textMargin * 2) + 'px; height: ' +
(rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' +
'<b>' + d.title + '</b>' +
'</div>';
});
nodeEnter.append("circle")
.attr('class', 'nodeCircle');
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.title;
})
.style("fill-opacity", 0);
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.text(function(d) {
if (d.type != 'end') {
return d.title
} else {
return 'End node'
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
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 "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.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", function(d) {
return diagonal({
source: d.source,
target: { x: d.target.x, y: d.target.y - (rectNode.width + circleRadius) }
});
});
// 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();
// Update the link text
var linktext = svgGroup.selectAll("g.link")
.data(links, function(d) {
return d.target.id;
});
linktext.enter()
.insert("g")
.attr("class", "link")
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.target.optionTitle;
});
// Transition link text to their new positions
linktext.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
})
//Transition exiting link text to the parent's new position.
linktext.exit().transition().remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
var svgGroup = baseSvg.append("g");
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
svgGroup
.append('defs')
.append('pattern')
.attr('id', function(d, i) {
return 'pic_plus';
})
.attr('height', 60)
.attr('width', 60)
.attr('x', 0)
.attr('y', 0)
.append('image')
.attr('xlink:href', function(d, i) {
return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
})
.attr('height', 12)
.attr('width', 12)
.attr('x', 0)
.attr('y', 0);
function getNodeFill(d) {
if (isFinal(d)) {
return '#0f0';
} else if (d._children || (!d._children && !d.children)) {
return 'url(#pic_plus)'
} else {
return '#fff'
}
}
function isFinal(node) {
return node.type == 'end';
}
function isCollapsed(node) {
return d._children || (!d._children && !d.children);
}
body {
background-color: #ddd;
}
.tree-custom,
.tree {
width: 100%;
height: 100%;
background-color: #fff;
}
.holder {
margin: 0 auto;
width: 1000px;
height: 800px;
background-color: #fff;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link text {
font: 10px sans-serif;
fill: #666;
}
.node-rect {
fill: #00f;
}
.node-text {
color: #fff;
}
您可以通过删除<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div class="tree"></div>
</body>
</html>
元素上的填充来检查链接是否在正确的位置终止。
现在还有许多其他修复方法,但这应该已经证明了您的概念证明;如果您不清楚新代码的任何部分,请询问。