D3 tree with boxes - d3.js

With my limited D3 knowledge, I've combined some tree layout examples to make a collapsible tree with branch labels. Here is a functional extract:
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>
In my code, the nodes are circles with the node labels next to them:
When I collapse a node, a plussign shows up.
Now I'm trying to put the node labels in boxes as it's shown in this example code.
I know I have to change the circles by foreignObjectsas in the example code but when I do it, paths aren't adjusted to the boxes.
How could I change the circles by foreignObjects and maintain the same functionality expand/collapse/plus?

The changes that you need to make to your current layout are:
move the blue boxes to be vertically aligned with the circles and with the right edge adjacent to the left side of each circle;
alter the paths to terminate at the left side of each blue box
The first can be done using a transform to alter the position of the blue boxes, and the second by altering the coordinates for the target point of each line.
Looking at the sample you linked to, there are rect elements behind the foreignObject elements that provide the colour, and the foreignObject is offset from the position of the rect elements. I have taken the liberty of adding the rect elements to your code and grouping the rect and foreignObject elements together so they can be moved in a single transform:
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>';
});
If you look at the tree this produces, the rect/foreignObject group needs to be translated the length of the rect element + the circle radius along the x axis, and by half the height of the rect element along the y axis. So, first let's add a variable to represent the circle radius and replace the hard-coded number with that variable:
var circleRadius = 6;
// a bit further on
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
Now write the transform:
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) + ')' );
Check the resulting tree:
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>
The next task is to alter the links to terminate at the edge of the rect boxes. If you check the code that covers the link positions and transitions, the enter and exit selections both use the position of the source node. The code we are interested in is this:
link.transition()
.duration(duration)
.attr("d", diagonal);
where the diagonal function is
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
d3.svg.diagonal() takes an object of the form
{ source: { x: 10, y: 10 }, target: { x: 20, y: 50 } }
and if you look at each item in the tree.links array, you'll see it is in the form
{ source: { /* source node coordinates */ }, target: { /* target node coords */ }
so to alter the position of the link target, we need to create a new object with the target coordinates altered. Once again, the x axis alteration should be -(rectNode.width + circleRadius); the y axis is OK. Note that the diagonal function switches over the x and y values, though, so we need to alter the target's y value, not the x value. Thus, we have:
// 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) }
});
});
Add that to our code:
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>
You can check that the links are terminating in the correct place by removing the fill on the rect elements.
There are now a number of other fixes to be made, but that should have proved your proof of concept; if you are unclear about any parts of the new code, please just ask.

Related

put line between two elements that are inside a <g> that has zoom events?

I have this code, and I have an algorithm to put the line between two nodes. I want this line to join the #nodo4 with the #nodo6, the rectangles are the nodes and each one has the same name as its id.
The code is a bit long but the important part to achieve this is here:
setTimeout(() => {
let source = d3.select("#node4");
let target = d3.select("#node6");
source.datum(source.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
target.datum(target.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
d3.select("#g_main").append("line")
.style("stroke", "black") // colour the line
.attr("x1", source.attr('nodeX')) // x position of the first end of the line
.attr("y1", source.attr('nodeY')) // y position of the first end of the line
.attr("x2", target.attr('nodeX')) // x position of the second end of the line
.attr("y2", target.attr('nodeY')); // y position of the second end of the line
}, 5000)
but I have problems getting the line to appear in the place where it should be and considering that I can constantly zoom and pan.
I want a dynamic solution because in the future I want to put a line that connects to other nodes and I would like to do this dynamic calculation.
what am I doing wrong?
var width = 960,
height = 800;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30;
var tree = d3.layout.tree().nodeSize([220, 40]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000).style("border", "1px solid red")
.call(zm = d3.behavior.zoom().scaleExtent([0.3, 3]).on("zoom", redraw)).append("g").attr("id", "g_main")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
var root = {
"name": "node6",
"children": [{
"name": "node5",
"respuesta": "SI",
"children": [{
"name": "node4",
"children": [{
"name": "node3"
}]
}]
}, {
"name": "node2",
"respuesta": "NO"
},
{
"name": "node1",
"respuesta": "SI"
}
]
}
root.x0 = 0;
root.y0 = height / 2;
root.children.forEach(collapse);
update(root);
root.x0 = 0;
root.y0 = height / 2;
setTimeout(() => {
let source = d3.select("#node4");
let target = d3.select("#node6");
source.datum(source.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
target.datum(target.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
d3.select("#g_main").append("line")
.style("stroke", "black") // colour the line
.attr("x1", source.attr('nodeX')) // x position of the first end of the line
.attr("y1", source.attr('nodeY')) // y position of the first end of the line
.attr("x2", target.attr('nodeX')) // x position of the second end of the line
.attr("y2", target.attr('nodeY')); // y position of the second end of the line
}, 5000)
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
// d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
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.x0 + "," + source.y0 + ")";
})
nodeEnter.append("rect")
.attr("id", function(d) {
return "node" + d.id
})
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "white")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("image").attr("href", "plus-flat.png").attr("width", (d) => {
let length = 0;
if (d.children) {
length = d.children.length;
} else {
length = 0;
}
if (d.name == "INICIO" && length == 0) {
return 0;
} else if (d.name == "INICIO" && length != 0) {
return 0;
} else {
return 25;
}
}).style("transform", "translate(65px, -10px)")
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.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 "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// 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")
.style("stroke", (d) => {
let respuesta = d.target.respuesta;
if (respuesta == "SI") {
return "#2a8841";
} else if (respuesta == "NO") {
return "#d44646";
} else {
return "#b7b7b7";
}
})
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.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();
link.append("text").text("otros")
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
.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;
}
body {
overflow: hidden;
}
<script src="https://d3js.org/d3.v3.js"></script>
<div id="body"></div>
result expected,something like this:
Don't override the datum of source and target. That means you will not be able to redraw the tree, because you override all the values you need for it.
Also, don't use getBoundingClientRect(). What if you zoom, or pan? It distorts and the line will never be at the correct place.
Instead, rely on the data you already gave the nodes, you have it already, and you can access it by calling .datum() with no arguments! Then, it's no problem to calculate the positions the nodes will have, and also no problem to add a line exactly where you want it, regardless of zooming or scrolling.
var width = 960,
height = 800;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30;
var tree = d3.layout.tree().nodeSize([220, 40]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000).style("border", "1px solid red")
.call(zm = d3.behavior.zoom().scaleExtent([0.3, 3]).on("zoom", redraw)).append("g").attr("id", "g_main")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
var root = {
"name": "node6",
"children": [{
"name": "node5",
"respuesta": "SI",
"children": [{
"name": "node4",
"children": [{
"name": "node3"
}]
}]
}, {
"name": "node2",
"respuesta": "NO"
},
{
"name": "node1",
"respuesta": "SI"
}
]
}
root.x0 = 0;
root.y0 = height / 2;
root.children.forEach(collapse);
update(root);
root.x0 = 0;
root.y0 = height / 2;
setTimeout(() => {
let source = d3.select("#node4").datum();
let target = d3.select("#node6").datum();
d3.select("#g_main").append("line")
.style("stroke", "black") // colour the line
.attr("x1", source.x0 + rectW / 2) // x position of the first end of the line
.attr("y1", source.y0) // y position of the first end of the line
.attr("x2", target.x0 + rectW / 2) // x position of the second end of the line
.attr("y2", target.y0 + rectH); // y position of the second end of the line
}, 1000)
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
// d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
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.x0 + "," + source.y0 + ")";
})
nodeEnter.append("rect")
.attr("id", function(d) {
return "node" + d.id
})
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "white")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("image").attr("href", "plus-flat.png").attr("width", (d) => {
let length = 0;
if (d.children) {
length = d.children.length;
} else {
length = 0;
}
if (d.name == "INICIO" && length == 0) {
return 0;
} else if (d.name == "INICIO" && length != 0) {
return 0;
} else {
return 25;
}
}).style("transform", "translate(65px, -10px)")
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.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 "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// 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")
.style("stroke", (d) => {
let respuesta = d.target.respuesta;
if (respuesta == "SI") {
return "#2a8841";
} else if (respuesta == "NO") {
return "#d44646";
} else {
return "#b7b7b7";
}
})
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.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();
link.append("text").text("otros")
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
.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;
}
body {
overflow: hidden;
}
<script src="https://d3js.org/d3.v3.js"></script>
<div id="body"></div>

Adding Labels to Links in Tree D3.js

Hi i need to add Labels for Tree D3.js on the links without a rectangle box on mouseover. And on the rectangle I need a tooltip displaying text inside a rectangle on mouse over and also dispaly the same toolTip when mouseover on link.
Below is the d3 js of version4 I am using. Please help me with this.
I want to achieve an img like this.
let treeData,
path;
treeData =JSON.parse(this.traceableData);
const treeHeight=(treeData.children.length*50);
this.imgHeight=treeHeight;
let children=treeData.children;
let imghtIncrement=0;
for(let i=0;i<children.length;i++)
{
if(children[i].name.length>0)
{
imghtIncrement=imghtIncrement+40;
}
}
let rectNode = {
width: 120,
height: 17,
textMargin: 5
};
// Set the dimensions and margins of the diagram
let margin = {top: 20, right: 120, bottom: 30, left: 160},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
let svg = d3.select(this.template.querySelector('svg.d3'))
.attr("width", width + margin.right + margin.left+200)
.attr("height", this.imgHeight+imghtIncrement+100)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
let i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
let treemap = d3.tree().size([this.imgHeight+imghtIncrement, width+200]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
//Collapse the Node and all its Children
function collapse(d) {
if (d.children) {
d.all_children = d.children;
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
d.hidden = true;
}
}
root.hidden = false;
update(root);
d3.select(self.frameElement).style("height", "800px");
function update(source) {
let nodeHeight=15;
// Assigns the x and y position for the nodes
treeData = treemap(root);
// Compute the new tree layout.
let nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d){ d.y = d.depth * 200});
// ****************** Nodes section ***************************
// Update the nodes...
let node = svg.selectAll('g.node')
.data(nodes, function(d) {return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
let nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
console.log(d);
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click)
.on("mouseover", function(d) {
let g = d3.select(this); // The node
// The class is used to remove the additional text later
g.append('text')
.classed('info', true)
.attr('x', -80)
.attr('y', -30)
.text(d.data.objectType);
})
.on("mouseout", function() {
// Remove the info text on mouse out.
d3.select(this).select('text.info').remove()
});
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d.children ? "lightsteelblue" : "#fff";
});
let rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp');
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('x',-120)
.attr('y',-20)
.attr('width', rectNode.width)
.attr('height', function(d){
let rectVariedHeight;
if(Math.ceil(d.data.name.length/nodeHeight<1.01)){
rectVariedHeight=40;
}
else
{
rectVariedHeight=rectNode.height*Math.ceil(d.data.name.length/nodeHeight);
}
return rectVariedHeight; })
.attr('fill', "white")
.style("stroke-width", 1)
.style("stroke", "black")
.attr('class', 'node-rect');
rectGrpEnter.append("text")
.attr("dx", function(d) {
let x=0;
if(Math.ceil(d.data.name.length<11)){
x=-160;
}
else
{
x=-125;
}
return d.children ? x : -220;
})
.attr("dy", "-5")
.attr('cursor', 'pointer')
.on("mouseover", function() {d3.select(this).style("fill", "#0000FF");})
.on("mouseout", function() {d3.select(this).style("fill", "black");})
.on("click", function(d) { window.open(d.data.url);})
.each(function(d) {
let noofTextParts;
if(d.children){
noofTextParts=Math.ceil(d.data.name.length/nodeHeight);
}
else{
noofTextParts=Math.ceil(d.data.name.length/nodeHeight);
}
// eslint-disable-next-line no-shadow
for(let i=0;i<noofTextParts;i++)
{
d3.select(this).append("tspan")
.attr("dy", i ? "1.2em" : -5)
// eslint-disable-next-line no-unused-vars
.attr("x", function(d2) {
let len;
if(i!==0)
{
if(d2.children)
{
len= -10;
}
else
{
len=-115;
}
}
else
{
len= 105;
}
return len;
}
)
.attr("class", "tspan" + i)
.style("text-anchor", function(d3) { return d3.children ? "end" : "start"; })
.text(function(d1) {
let nodeName;
if(d1.children)
{
nodeName=d1.data.name.substring(i*nodeHeight,(i+1)*nodeHeight);}
else
{
nodeName= d1.data.name.substring(i*nodeHeight,(i+1)*nodeHeight);
}
return nodeName;
});
}
});
// UPDATE
let nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
let circleRadius = 0;
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', circleRadius)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
let nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
console.log(d);
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
let link = svg.selectAll('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
let linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
console.log(d);
let o = {x: source.x0, y: source.y0}
return diagonal(o, o)
});
// UPDATE
let linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d){ return diagonal(d, d.parent) });
// Remove any exiting links
let linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
console.log(d);
let o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.remove();
console.log(linkExit);
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y- (rectNode.width + circleRadius) } ${s.x}
C ${(s.y- (rectNode.width + circleRadius) + d.y ) / 2} ${s.x},
${(s.y- (rectNode.width + circleRadius) + d.y ) / 2} ${d.x},
${d.y } ${d.x}`
return path
}
}
if(root.hasOwnProperty("children"))
//Only children will be collpased
{
root.children.forEach(collapse);
}
// To collapse all to root element
collapse(root);
//Changes added for collapsing ends
update(root);
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
if (d._children) {
d._children.forEach(function(n) { n.hidden = true; });
if (d.parent) {
d.parent.children = d.parent.all_children;
d.parent.children.forEach(function(n) {
n.hidden = false;
});
}
}
} else {
d.children = d._children;
d._children = null;
if (d.children) {
d.children.forEach(function(n) { n.hidden = false; });
if (d.parent) {
d.parent.children = [d,];
d.parent.children.filter(function(n) { return n !== d; }).forEach(function(n) {
n.hidden = true;
});
}
}
}
update(d);
}
I have added labels to the links.
Hope it helps you.
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
.link path {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
.link text {
font: 12px sans-serif;
stroke: #333;
stroke-width: 1;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var treeData =
{
"name": "Top Level",
"linkname": "null",
"children": [
{
"name": "Level 2: A",
"linkname": "Link_1",
"children": [
{ "name": "Son of A", "linkname": "Link_2.1" },
{ "name": "Daughter of A", "linkname": "Link_2.2" }
]
},
{ "name": "Level 2: B", "linkname": "Link_3", }
]
};
// Set the dimensions and margins of the diagram
var margin = { top: 20, right: 90, bottom: 30, left: 90 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function (d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function (d) {
d.y = d.depth * 180
});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new modes 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);
// Add Circle for the nodes
nodeEnter.filter(function (d) {
return (!d.data.type || d.data.type !== 'data');
}).append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.filter(function (d) {
return (d.data.type && d.data.type === 'data');
}).append('rect')
.attr('class', 'node')
.attr('width', 20)
.attr('height', 20)
.attr('y', -10)
.attr('x', -10)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", "2em")
.attr("x", function (d) {
return d.children || d._children ? 13 : 13;
})
.attr("text-anchor", function (d) {
return d.children || d._children ? "start" : "start";
})
.text(function (d) {
return d.data.name;
});
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('g.link')
.data(links, function (d) {
return d.id;
});
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('g', 'g')
.attr("class", "link");
linkEnter.append('text')
.attr("class","linkLabels")
.text(function (d, i) {
if (d.parent && d.parent.children.length > 1) {
if (!d.parent.index) d.parent.index = 0;
return d.data.linkname;
}
})
.attr("opacity",0)
.attr('dy', "-1em");
linkEnter.append('path')
.attr('d', function (d) {
var o = {
x: source.x0,
y: source.y0
}
return diagonal(o, o)
})
.on("mouseover", function(){
d3.select(this.parentNode).select("text").attr("opacity",1);
})
.on("mouseleave", function(){
d3.select(this.parentNode).select("text").attr("opacity",0);
})
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.select('path').transition()
.duration(duration)
.attr('d', function (d) {
return diagonal(d, d.parent)
});
linkUpdate.select('text').transition()
.duration(duration)
.attr('transform', function (d) {
if (d.parent) {
return 'translate(' + ((d.parent.y + d.y) / 2) + ',' + ((d.parent.x + d.x) / 2) + ')'
}
})
// Remove any exiting links
link.exit().each(function (d) {
d.parent.index = 0;
})
var linkExit = link.exit()
.transition()
.duration(duration);
linkExit.select('path')
.attr('d', function (d) {
var o = {
x: source.x,
y: source.y
}
return diagonal(o, o)
})
linkExit.select('text')
.style('opacity', 0);
linkExit.remove();
// Store the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// 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>

Wrapping multi line labels

I'm building a responsive zoomable treemap based on this project. The problem is that the labels I've are longer than the original visualization and end up not showing:
function text(text) {
text.selectAll("tspan")
.attr("x", function(d) { return x(d.x) + 6; })
text.attr("x", function(d) { return x(d.x) + 6; })
.attr("y", function(d) { return y(d.y) + 6; })
.style("opacity", function(d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; });
}
The problem I see is that the text needs to be showed completely in the first line, but I'd like to show it in multiple lines (inside the rect) instead.
There's a code by Mike Bostock which seems to solve this issue but I don't know how to apply it to the treemap.
Here's a quick a modification which wraps the parent text in that example:
<!DOCTYPE html>
<!--
Generic treemap, based on http://bost.ocks.org/mike/treemap/
-->
<html>
<head>
<meta charset="utf-8">
<title>Zoomable treemap template</title>
<style>
#chart {
background: #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.title {
font-weight: bold;
font-size: 24px;
text-align: center;
margin-top: 6px;
margin-bottom: 6px;
}
text {
pointer-events: none;
}
.grandparent text {
font-weight: bold;
}
rect {
fill: none;
stroke: #fff;
}
rect.parent,
.grandparent rect {
stroke-width: 2px;
}
rect.parent {
pointer-events: none;
}
.grandparent rect {
fill: orange;
}
.grandparent:hover rect {
fill: #ee9700;
}
.children rect.parent,
.grandparent rect {
cursor: pointer;
}
.children rect.parent {
fill: #bbb;
fill-opacity: .5;
}
.children:hover rect.child {
fill: #bbb;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
window.addEventListener('message', function(e) {
var opts = e.data.opts,
data = e.data.data;
return main(opts, data);
});
var defaults = {
margin: {
top: 24,
right: 0,
bottom: 0,
left: 0
},
rootname: "TOP",
format: ",d",
title: "",
width: 960,
height: 500
};
function main(o, data) {
var root,
opts = $.extend(true, {}, defaults, o),
formatNumber = d3.format(opts.format),
rname = opts.rootname,
margin = opts.margin,
theight = 36 + 16;
$('#chart').width(opts.width).height(opts.height);
var width = opts.width - margin.left - margin.right,
height = opts.height - margin.top - margin.bottom - theight,
transitioning;
var color = d3.scale.category20c();
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var treemap = d3.layout.treemap()
.children(function(d, depth) {
return depth ? null : d._children;
})
.sort(function(a, b) {
return a.value - b.value;
})
.ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
.round(false);
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
var grandparent = svg.append("g")
.attr("class", "grandparent");
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top);
grandparent.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
if (opts.title) {
$("#chart").prepend("<p class='title'>" + opts.title + "</p>");
}
if (data instanceof Array) {
root = {
key: rname,
values: data
};
} else {
root = data;
}
initialize(root);
accumulate(root);
layout(root);
console.log(root);
display(root);
if (window.parent !== window) {
var myheight = document.documentElement.scrollHeight || document.body.scrollHeight;
window.parent.postMessage({
height: myheight
}, '*');
}
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
}
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
function accumulate(d) {
return (d._children = d.values) ? d.value = d.values.reduce(function(p, v) {
return p + accumulate(v);
}, 0) : d.value;
}
// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1×1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
// object is created for the parent node for each group of siblings so that
// the parent’s dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1×1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
function layout(d) {
if (d._children) {
treemap.nodes({
_children: d._children
});
d._children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
function display(d) {
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function(d) {
return d._children;
})
.classed("children", true)
.on("click", transition);
var children = g.selectAll(".child")
.data(function(d) {
return d._children || [d];
})
.enter().append("g");
children.append("rect")
.attr("class", "child")
.call(rect)
.append("title")
.text(function(d) {
return d.key + " (" + formatNumber(d.value) + ")";
});
children.append("text")
.attr("class", "ctext")
.text(function(d) {
return d.key;
})
.call(text2);
g.append("rect")
.attr("class", "parent")
.call(rect);
var t = g.append("text")
.attr("class", "ptext")
.attr("dy", ".75em")
t.append("tspan")
.text(function(d) {
return d.key;
});
t.append("tspan")
.attr("dy", "1.0em")
.text(function(d) {
return formatNumber(d.value);
});
t.call(text);
g.selectAll("rect")
.style("fill", function(d) {
return color(d.key);
});
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function(a, b) {
return a.depth - b.depth;
});
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll(".ptext").call(text).style("fill-opacity", 0);
t1.selectAll(".ctext").call(text2).style("fill-opacity", 0);
t2.selectAll(".ptext").call(text).style("fill-opacity", 1);
t2.selectAll(".ctext").call(text2).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function() {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
function text(text) {
text.selectAll("tspan")
.attr("x", function(d) {
return x(d.x) + 6;
})
text.attr("x", function(d) {
return x(d.x) + 6;
})
.attr("y", function(d) {
return y(d.y) + 6;
})
.each(function(d) {
var tspan = this.childNodes[0];
var w = x(d.x + d.dx) - x(d.x);
wrap(tspan, w, x(d.x) + 6);
})
}
function text2(text) {
text.attr("x", function(d) {
return x(d.x + d.dx) - this.getComputedTextLength() - 6;
})
.attr("y", function(d) {
return y(d.y + d.dy) - 6;
})
.style("opacity", function(d) {
return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0;
});
}
function rect(rect) {
rect.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y);
})
.attr("width", function(d) {
return x(d.x + d.dx) - x(d.x);
})
.attr("height", function(d) {
return y(d.y + d.dy) - y(d.y);
});
}
function name(d) {
return d.parent ? name(d.parent) + " / " + d.key + " (" + formatNumber(d.value) + ")" : d.key + " (" + formatNumber(d.value) + ")";
}
}
if (window.location.hash === "") {
d3.json("https://jsonblob.com/api/7c30e101-da91-11e6-90ab-11c211a4b3d5", function(err, res) {
if (!err) {
console.log(res);
var data = d3.nest().key(function(d) {
return d.region;
}).key(function(d) {
return d.subregion;
}).entries(res);
main({
title: "World Population"
}, {
key: "World",
values: data
});
}
});
}
function wrap(tspan, width, x) {
var text = d3.select(tspan),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
y = text.attr("y"),
dy = parseFloat(text.attr("dy")) || 0.4,
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", "0.75em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", "1em").text(word);
}
}
}
</script>
</body>
</html>

Placing label in the middle of a link in d3 js tree layout

I'm trying to create a labelled double tree using d3js tree layout. Functionality is working fine so far , but the problem I face is , i'm unable to place the label in the middle of the link.
Right now it appears like this ( once you have added enough nodes )
This is the part of the code where I add the edge and label inside a g element. I'm trying add the label in the midpoint of the link , but it's not working. ( the code shown is for the right side of the double tree )
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 25);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
Here is the working code so far -
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
svg.append("g").attr("id", "right-edges")
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
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", function(d) {
console.log(d);
if (d.parent == "null") {
if (d.children)
{
return "node rightparent collapsed" //since its root its parent is null
} else {
return "node rightparent"
}
} else {
if (d.children) {
return "node rightchild collapsed" //all nodes with parent will have this class
} else {
return "node rightchild" //all nodes with parent will have this class
}
}
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
addRightChild.append("rect")
.attr("x", "90")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addRightChild.append("line")
.attr("x1", 95)
.attr("y1", 1)
.attr("x2", 105)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addRightChild.append("line")
.attr("x1", 100)
.attr("y1", -4)
.attr("x2", 100)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
// adding the right chevron
var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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("text")
.style("fill-opacity", 1e-6);
// Update the links…
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 25);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// d3.selectAll(".right-tree-container .remove-child").remove();
// var removeButton = edge.selectAll(".remove-child")
// .data(links, function(d) {
// return d.target.id + d.source.id;
// });
// removeButton.enter().insert("rect", "edge-container")
// .attr("class", "remove-child")
// .attr("x", function(d) {
// var x = (d.source.y + d.target.y) / 2
// return parseInt(x + 45);
// })
// .attr("y", function(d) {
// var y = (d.source.x + d.target.x) / 2
// return y;
// })
// .attr("width","20")
// .attr("height","20")
// .attr("rx","10")
// .attr("ry","10")
// .style("fill","white")
// .style("stroke","#444")
// .style("stroke-width","2");
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to the child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
addRightChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// 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);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
svg.append("g").attr("id", "left-edges")
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (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", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
var edge = d3.select("#left-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().append("text")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 45);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
var link = edge.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Update the links…
// Enter any new links at the parent's previous position.
link.enter().insert("path", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to teh child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
z-index: 100;
}
.asset-title {
padding: 15px;
float: left;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
.left-chevron,
.right-chevron {
display: none;
}
.collapsed .left-chevron,
.collapsed .right-chevron {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
I've added to your code. Below is the result. The main part was this :
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2;
d.thisText = "test-label";
return x - d.thisText.length/2 + 5; //added 5 to make up for node width. This needs to be worked out properly
})
It's not exactly center as I haven't implemented the different between the center of the node and the label. But changing the above piece of code will get the result you want :) Hope this helps.
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
svg.append("g").attr("id", "right-edges")
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
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", function(d) {
console.log(d);
if (d.parent == "null") {
if (d.children)
{
return "node rightparent collapsed" //since its root its parent is null
} else {
return "node rightparent"
}
} else {
if (d.children) {
return "node rightchild collapsed" //all nodes with parent will have this class
} else {
return "node rightchild" //all nodes with parent will have this class
}
}
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? 0 : 0;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
addRightChild.append("rect")
.attr("x", "90")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addRightChild.append("line")
.attr("x1", 95)
.attr("y1", 1)
.attr("x2", 105)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addRightChild.append("line")
.attr("x1", 100)
.attr("y1", -4)
.attr("x2", 100)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
// adding the right chevron
var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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("text")
.style("fill-opacity", 1e-6);
// Update the links…
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.target.y + d.source.y) / 2
return x - 50;
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// d3.selectAll(".right-tree-container .remove-child").remove();
// var removeButton = edge.selectAll(".remove-child")
// .data(links, function(d) {
// return d.target.id + d.source.id;
// });
// removeButton.enter().insert("rect", "edge-container")
// .attr("class", "remove-child")
// .attr("x", function(d) {
// var x = (d.source.y + d.target.y) / 2
// return parseInt(x + 45);
// })
// .attr("y", function(d) {
// var y = (d.source.x + d.target.x) / 2
// return y;
// })
// .attr("width","20")
// .attr("height","20")
// .attr("rx","10")
// .attr("ry","10")
// .style("fill","white")
// .style("stroke","#444")
// .style("stroke-width","2");
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to the child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
addRightChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// 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);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
svg.append("g").attr("id", "left-edges")
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (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", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
var edge = d3.select("#left-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().append("text")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2;
d.thisText = "test-label";
return x - d.thisText.length/2 + 5;
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text(function(d){return d.thisText})
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
var link = edge.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Update the links…
// Enter any new links at the parent's previous position.
link.enter().insert("path", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to teh child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
z-index: 100;
}
.asset-title {
padding: 15px;
float: left;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
.left-chevron,
.right-chevron {
display: none;
}
.collapsed .left-chevron,
.collapsed .right-chevron {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
You would need to do the same with the Y. But instead of taking away the textLength/2, just take away the textHeight/2 if you want it exactly central

Place label beside links on collapsible d3 tree layout

I'm trying to create a double tree plugin using d3 tree layout where i can add children dynamically on clicking a parent. I'm trying to add a label over each link or path.
What i have tried to do is add a g element and then add both the text and path element inside it. But the problem is that i'm unable to place the text exactly over the link. How can i apply transition to the text so that it is placed beside the link connecting two elements .
Following is the relevant code -
var edge = d3.select(".right-tree-container")
.append("g")
.attr("class","edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
edge.append("text")
.text("test-label");
Here is my code. Sorry for the long version , could not make it shorter - for convenience i have commented the part of the code which is important for the above juncture.
For demo purpose code has only been added on the right hand side tree.
Use:- click on the node , click the + symbol to add nodes. Click on the path to remove a node and it's connected children.
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
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", function(d) {
if (d.parent == "null") {
return "node rightparent" //since its root its parent is null
} else
return "node rightchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
addRightChild.append("rect")
.attr("x", "90")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addRightChild.append("line")
.attr("x1", 95)
.attr("y1", 1)
.attr("x2", 105)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addRightChild.append("line")
.attr("x1", 100)
.attr("y1", -4)
.attr("x2", 100)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
// adding the right chevron
var rightChevron = nodeEnter.append("g");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
console.log("chevron-right");
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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("text")
.style("fill-opacity", 1e-6);
// Update the links…
/***************************************
required code , where group edge-container edge is added
************************/
var edge = d3.select(".right-tree-container")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
edge.append("text")
.text("test-label");
/***** end of relevant code *******/
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to the child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
addRightChild.on("click", function(d) {
console.log(d._children);
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// 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);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (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", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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", 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("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to teh child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
// 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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
console.log($(this).children("addLeftChild"));
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
}
#btn-add-child {} .control-bar {
min-height: 50px;
padding: 10px 0px;
background: #f1f1f1;
border-bottom: 1px solid #ccc;
box-shadow: 0 3px 2px -2px rgba(200, 200, 200, 0.2);
color: #666;
}
.asset-title {
padding: 15px;
float: left;
}
.control-buttons {
float: right;
padding: 10px;
}
.control-buttons button {
border: none;
border-radius: 0px;
background: #fff;
color: #666;
height: 30px;
width: 30px;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
Seems you are not adding x and y to the label for link.
var text = edge.selectAll(".text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
})
text.enter().append("text")
.attr("class", "text-link")
.attr("x",function(d){
var x = (d.source.y +d.target.y)/2
return x;
})
.attr("y",function(d){
var y = (d.source.x +d.target.x)/2
return y;
})
.text("test-label");
also need to delete the existing links , else multiple links will get added. So added this line too
d3.selectAll(".text-link").remove();

Resources