Add Tooltips to Interactive D3 Diagram in D3 - d3.js

I am new to D3 and trying to combine two examples from bl.ocks.org to add tooltips to the nodes of an interactive tree diagram. You can see the original code here: http://bl.ocks.org/d3noob/8375092 & http://bl.ocks.org/d3noob/a22c42db65eb00d4e369 .
I believe my issue stems from not knowing how to attach the .on("mouseover") portion to the nodes div. The current code does not render any output. Any help is greatly appreciated.
<!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;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var treeData =
{
"name": "Total: 300",
"children": [
{
"name": "ABC: 150",
"children": [
{ "name": "BCD: 150" },
{ "name": "CDE: 50" }
]
},
{ "name": "DEF: 25" }
]
};
// 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 + ")");
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
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); });
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html("1" + "<br/>" + "2")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
// 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.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "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('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {x: source.x0, y: source.y0}
return diagonal(o, o)
});
// UPDATE
var 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
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.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>

Two issues! one is misplaced ; and one is missing }); Following should work
<!DOCTYPE html>
<html>
<head>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var treeData =
{
"name": "Total: 300",
"children": [
{
"name": "ABC: 150",
"children": [
{ "name": "BCD: 150" },
{ "name": "CDE: 50" }
]
},
{ "name": "DEF: 25" }
]
};
// 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 + ")");
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
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); })
.on("mouseover", function (d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("1" + "<br/>" + "2")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function (d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// 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.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function (d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "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('path.link')
.data(links, function (d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function (d) {
var o = { x: source.x0, y: source.y0 }
return diagonal(o, o)
});
// UPDATE
var 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
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function (d) {
var o = { x: source.x, y: source.y }
return diagonal(o, o)
})
.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>
</html>

Related

Make a dynamic tree with d3 V7

I want to make a tree with d3 v7, which allows me to add nodes on each side. I found a code on Stack Overflow (Tree with children towards multiple side in d3.js (similar to family tree)) that uses V3 but I'd like to adapt it to V7 (something I can't do properly because I don't know enough about this library and I have trouble understanding all the docs)
So I tried to modify the code to work with V7 like the d3.layout.tree() etc but nothing does it I must be doing it wrong. Does anyone know where the code goes wrong? If it is possible or if I have to start from the beginning?
Here is mine
import { hierarchy, tree } from "https://cdn.skypack.dev/d3-hierarchy#3.1.2"
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function() {
console.log("=====> READY");
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 treeLayout = d3.tree()
.size([height, width]);
var diagonal = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
makeRightTree();
makeLeftTree();
});
function makeRightTree() {
// ************** 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 treeLayout = d3.tree()
.size([height, width]);
var diagonal = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
var svg = d3.select("svg").append("g")
.attr("transform", "translate(600,0)");
root = treeData[0];
var oldrx = root.x0 = height / 2;
var oldry = root.y0 = 0;
update(root);
function update(source) {
// create a hierarchy from the root
const treeRoot = d3.hierarchy(root)
tree(treeRoot)
// nodes
const nodes = treeRoot.descendants()
// links
const links = treeRoot.links()
// 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("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
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");
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");
// 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("circle")
.attr("r", 10)
.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.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("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
//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;
});
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') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
console.log(tree(newChild[0]));
var newnodes = d3.hierarchy(newChild[0], d => d.children);
d.children = newnodes;
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);
}
$("#child-info").hide();
});
});;
}
// Toggle children on click.
function click(d) {
// console.log(d);
// if (d.children) {
// d._children = d.children;
// d.children = null;
// } else {
// d.children = d._children;
// d._children = null;
// }
// update(d);
}
}
function makeLeftTree() {
// ************** 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 treeLayout = d3.tree()
.size([height, width]);
var diagonal = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
var svg = d3.select("svg").append("g")
.attr("transform", "translate(-421,0)");
root = treeData2[0];
var oldlx = root.x0 = height / 2;
var oldly = root.y0 = width;
update(root);
function update(source) {
// create a hierarchy from the root
const treeRoot = d3.hierarchy(root)
tree(treeRoot)
// nodes
const nodes = treeRoot.descendants()
// links
const links = treeRoot.links()
// 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("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
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");
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");
// 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("circle")
.attr("r", 10)
.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.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("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
//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') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
console.log(tree(newChild[0]));
var newnodes = d3.hierarchy(newChild[0], d => d.children);
d.children = newnodes;
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);
}
$("#child-info").hide();
});
});;
}
// Toggle children on click.
function click(d) {
// console.log(d);
// if (d.children) {
// d._children = d.children;
// d.children = null;
// } else {
// d.children = d._children;
// d._children = null;
// }
// update(d);
}
}
#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 circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.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;
}
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</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>

D3 Tree with Collapsing Boxes using D3 Version 4

We were able to use this example here(https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd) and achieve the Collapsible tree diagram. We are looking to have boxes in here instead of circles with text aligned inside it.
Below is the diagram which we are trying to achieve using Version4.
Please let know what change needs to be done in the above code using d3 version 4 to achieve this
I have made necessary code changes to accommodate your requirement.
Hope it helps you.
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
.node rect {
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
fill: #fff;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var treeData =
{
"name": "NODE NAME 1",
"subname": "CODE N1",
"fill":"orange",
"children": [
{
"name": "NODE NAME 2.1",
"subname": "CODE N1",
"fill":"blue"
},
{ "name": "NODE NAME 2.2","subname": "CODE N1" ,"fill":"blue" },
{ "name": "NODE NAME 2.3","subname": "CODE N1","fill":"blue",
"children": [
{ "name": "NODE NAME 3.3","fill":"blue","subname": "CODE N1",
"children": [{
"name":"NODE NAME 4.1","subname": "CODE N1",
"fill":"#d281d2"
}
] },
{ "name": "NODE NAME 3.4","fill":"blue", "subname": "CODE N1" ,
"children": [{
"name":"NODE NAME 4.2", "subname": "CODE N1" ,
"fill":"#d281d2"
}
]
}
]
}
]
};
// 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);
var rectHeight = 60, rectWidth = 120;
nodeEnter.append('rect')
.attr('class', 'node')
.attr("width", rectWidth)
.attr("height", rectHeight)
.attr("x", 0)
.attr("y", (rectHeight/2)*-1)
.attr("rx","5")
.style("fill", function(d) {
return d.data.fill;
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", "-.35em")
.attr("x", function(d) {
return 13;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) { return d.data.name; })
.append("tspan")
.attr("dy", "1.75em")
.attr("x", function(d) {
return 13;
})
.text(function(d) { return d.data.subname; });
// 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('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {x: source.x0, y: source.y0}
return diagonal(o, o)
});
// UPDATE
var 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
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.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>

Collapse d3js tree to a specified depth

I'm new to d3js and javascript in general. I am trying to create a tree of more than 8000 nodes, and used the basic tree examples available and modified to display vertically.
Example data is hard coded, however, on my local server, I read the data from an external JSON file.
I would like to be able to specify the depth level to start with (collapse all node deeper than 2) and allow the user to further expand or collapse nodes.
I tried modifying the "collapse" function to check for the depth level, however, the logic is not correct.
Appreciate any help in this regard.
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//------------------
var data =[{"sid":"1","parent":"null","name_ar":"Hashim"},{"sid":"2","parent":"1","name_ar":"wahb"},{"sid":"3","parent":"1","name_ar":"Abdulmuttaleb"},{"sid":"4","parent":"2","name_ar":"Amina"},{"sid":"5","parent":"3","name_ar":"Abutaleb"},{"sid":"6","parent":"3","name_ar":"Abdulla"},{"sid":"7","parent":"3","name_ar":"Abbas"},{"sid":"8","parent":"3","name_ar":"Hamza"},{"sid":"9","parent":"6","name_ar":"Mohammed (Prophet)"},{"sid":"10","parent":"9","name_ar":"Alqassim"},{"sid":"11","parent":"9","name_ar":"Um Kalthoum"},{"sid":"12","parent":"9","name_ar":"Zainab"},{"sid":"13","parent":"9","name_ar":"Ruqaya"},{"sid":"14","parent":"9","name_ar":"Fatima"},{"sid":"15","parent":"9","name_ar":"Ibrahim"},{"sid":"16","parent":"9","name_ar":"Abdulla"},{"sid":"17","parent":"9","name_ar":"Muhsen"},{"sid":"18","parent":"5","name_ar":"Ali"},{"sid":"19","parent":"18","name_ar":"Hassan"},{"sid":"20","parent":"18","name_ar":"Hussain"},{"sid":"21","parent":"20","name_ar":"Ali Zain Alabbideen"},{"sid":"22","parent":"21","name_ar":"Mohammed Baqer"},{"sid":"23","parent":"22","name_ar":"Jafar Sadeq"},{"sid":"24","parent":"23","name_ar":"Mousa Kadim"},{"sid":"25","parent":"24","name_ar":"Ali AlAreed"},{"sid":"26","parent":"24","name_ar":"Ibrahim Murtada"},{"sid":"27","parent":"26","name_ar":"Mousa (the second)"},{"sid":"28","parent":"27","name_ar":"Ahmed"},{"sid":"29","parent":"28","name_ar":"Hussain"},{"sid":"30","parent":"29","name_ar":"Abu Alqassim Mohammed"},{"sid":"31","parent":"30","name_ar":"Najm Aldeen Mahdi"}];
//need to find a way to dynamically set the "Width" as the tree is very deep
var margin = {top: 25, right: 120, bottom: 20, left: 120},
width = 10000 - margin.right - margin.left,
height = 5000 - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30,
root;
// zoom functionality
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var tree = d3.layout.tree()
.nodeSize([110, 50]); // increased to 110 to avoid node overlap
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", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.call(zoom) // added to call to zoom to enable zooming; it works :}
.on("wheel.zoom", null) // disable zooming on mouse wheel scroll
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//create a name-based map for the nodes
//which starts with an empty object and iterates over the data array, adding an entry for each node
//for the flat array
var dataMap = data.reduce(function(map, node) {
map[node.sid] = node;
return map;
}, {});
//iteratively add each child to its parents, or to the root array if no parent is found
//for the flat array
var treeData = [];
data.forEach(function(node) {
// add to parent
var parent = dataMap[node.parent];
if (parent) {
// create child array if it doesn't exist
(parent.children || (parent.children = []))
// add node to child array
.push(node);
} else {
// parent is null or missing
treeData.push(node);
}
});
root = treeData[0];
root.x0 = height / 2; // should this be width/2 for the vertical?
root.y0 = 0;
//testing using depth to open at a specified level
var nodes = tree.nodes(root);
function collapseLevel(d) {
console.log("sid "+d.sid+" depth "+d.depth);
if (d.children && d.depth > 2) { // doesn't work as it exits at parent depth
d._children = d.children;
d._children.forEach(collapseLevel);
d.children = null;
}
}
root.children.forEach(collapseLevel);//iterate each node and collapse excluding node zero
update(root);
d3.select(self.frameElement).style("height", "500px");
//zoom (drag the tree around !)
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
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.
//vertical tree by swaping y0 and x0
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
.on("click", click)
.on("mouseover", function(d) {
var g = d3.select(this); // The node
var info = g.append('text')
.classed('info', true)
.attr('x', 20)
.attr('y', 10)
.text(function(d) { return d.name_ar + " " + d.sid });
})
.on("mouseout", function() {
// Remove the info text on mouse out.
d3.select(this).select('text.info').remove()
});
;
//rectagular nodes
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name_ar; })
.style("fill-opacity", 1);
// Transition nodes to their new position.
//vertical
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.
//vertical
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("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")
.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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 18px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
</head>
You have to provide an else if condition, in case the if condition is falsy:
function collapseLevel(d) {
if (d.children && d.depth > 1) {
d._children = d.children;
d._children.forEach(collapseLevel);
d.children = null;
} else if (d.children) {
d.children.forEach(collapseLevel);
}
}
Here is your code with that change:
var data =[{"sid":"1","parent":"null","name_ar":"Hashim"},{"sid":"2","parent":"1","name_ar":"wahb"},{"sid":"3","parent":"1","name_ar":"Abdulmuttaleb"},{"sid":"4","parent":"2","name_ar":"Amina"},{"sid":"5","parent":"3","name_ar":"Abutaleb"},{"sid":"6","parent":"3","name_ar":"Abdulla"},{"sid":"7","parent":"3","name_ar":"Abbas"},{"sid":"8","parent":"3","name_ar":"Hamza"},{"sid":"9","parent":"6","name_ar":"Mohammed (Prophet)"},{"sid":"10","parent":"9","name_ar":"Alqassim"},{"sid":"11","parent":"9","name_ar":"Um Kalthoum"},{"sid":"12","parent":"9","name_ar":"Zainab"},{"sid":"13","parent":"9","name_ar":"Ruqaya"},{"sid":"14","parent":"9","name_ar":"Fatima"},{"sid":"15","parent":"9","name_ar":"Ibrahim"},{"sid":"16","parent":"9","name_ar":"Abdulla"},{"sid":"17","parent":"9","name_ar":"Muhsen"},{"sid":"18","parent":"5","name_ar":"Ali"},{"sid":"19","parent":"18","name_ar":"Hassan"},{"sid":"20","parent":"18","name_ar":"Hussain"},{"sid":"21","parent":"20","name_ar":"Ali Zain Alabbideen"},{"sid":"22","parent":"21","name_ar":"Mohammed Baqer"},{"sid":"23","parent":"22","name_ar":"Jafar Sadeq"},{"sid":"24","parent":"23","name_ar":"Mousa Kadim"},{"sid":"25","parent":"24","name_ar":"Ali AlAreed"},{"sid":"26","parent":"24","name_ar":"Ibrahim Murtada"},{"sid":"27","parent":"26","name_ar":"Mousa (the second)"},{"sid":"28","parent":"27","name_ar":"Ahmed"},{"sid":"29","parent":"28","name_ar":"Hussain"},{"sid":"30","parent":"29","name_ar":"Abu Alqassim Mohammed"},{"sid":"31","parent":"30","name_ar":"Najm Aldeen Mahdi"}];
//need to find a way to dynamically set the "Width" as the tree is very deep
var margin = {top: 25, right: 120, bottom: 20, left: 120},
width = 10000 - margin.right - margin.left,
height = 5000 - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30,
root;
// zoom functionality
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var tree = d3.layout.tree()
.nodeSize([110, 50]); // increased to 110 to avoid node overlap
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", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.call(zoom) // added to call to zoom to enable zooming; it works :}
.on("wheel.zoom", null) // disable zooming on mouse wheel scroll
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//create a name-based map for the nodes
//which starts with an empty object and iterates over the data array, adding an entry for each node
//for the flat array
var dataMap = data.reduce(function(map, node) {
map[node.sid] = node;
return map;
}, {});
//iteratively add each child to its parents, or to the root array if no parent is found
//for the flat array
var treeData = [];
data.forEach(function(node) {
// add to parent
var parent = dataMap[node.parent];
if (parent) {
// create child array if it doesn't exist
(parent.children || (parent.children = []))
// add node to child array
.push(node);
} else {
// parent is null or missing
treeData.push(node);
}
});
root = treeData[0];
root.x0 = height / 2; // should this be width/2 for the vertical?
root.y0 = 0;
//testing using depth to open at a specified level
var nodes = tree.nodes(root);
function collapseLevel(d) {
if (d.children && d.depth > 1) {
d._children = d.children;
d._children.forEach(collapseLevel);
d.children = null;
} else if (d.children){
d.children.forEach(collapseLevel);
}
}
root.children.forEach(collapseLevel);//iterate each node and collapse excluding node zero
update(root);
d3.select(self.frameElement).style("height", "500px");
//zoom (drag the tree around !)
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
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.
//vertical tree by swaping y0 and x0
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
.on("click", click)
.on("mouseover", function(d) {
var g = d3.select(this); // The node
var info = g.append('text')
.classed('info', true)
.attr('x', 20)
.attr('y', 10)
.text(function(d) { return d.name_ar + " " + d.sid });
})
.on("mouseout", function() {
// Remove the info text on mouse out.
d3.select(this).select('text.info').remove()
});
;
//rectagular nodes
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name_ar; })
.style("fill-opacity", 1);
// Transition nodes to their new position.
//vertical
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.
//vertical
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("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")
.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();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 18px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
PS: I changed to d.depth > 1 because it seems to me that you want to show 2 depths by default, not 3. If I'm wrong, just change that number accordingly.

D3 Tree layout: how to highlight a whole path

I want to highlight the parent links when clicking on a node. The parent links means all the links between the clicked node and the root.
I stuck on the procedure to select the links when clicking on a node. How to achieve this?
the html file:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 30px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 7px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width = 1200 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
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 + ")");
d3.json("parse_tree.json", function(error, parse_tree) {
if (error) throw error;
root = parse_tree;
root.x0 = height / 2;
root.y0 = 0;
tree.size([width, height]);
update(root);
});
function update(source1) {
var originalConsole = console;
var nodes = tree.nodes(root);
var links = tree.links(nodes);
nodes.forEach(function(d) { d.y = d.depth * 100; });
// 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(" + source1.y0 + "," + source1.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -20 : 20; })
.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);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeUpdate.select("circle")
.attr("r", 10)
.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(" + source1.y + "," + source1.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("d", function(d) {
var o = {x: source1.x0, y: source1.y0};
return diagonal({source: o, target: o});
})
.style("stroke-width", "3px")
.style("stroke", "green");
// 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: source1.x, y: source1.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;
});
for(var k = 0; k < 1000; k++)
flag = 0;
}
// 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>
the json file:
{"name":"VP",
"size":"89",
"children": [
{"name":"VBP",
"size":"15",
"children":[{"name":"are", "size":"38"}]
},
{"name":"NP",
"size":"83",
"children": [
{"name":"DT",
"size":"29",
"children":[{"name":"a", "size":"53"}]
},
{"name":"NN",
"size":"50",
"children":[{"name":"boy", "size":"99"}]
}
]
}
]
}
The key here is links which is an array of objects, each with two attributes: source (the parent node) and target (the child node). See here for more details.
Within your existing click handler (function click(d)...) you'll want to loop through links checking for cases where links[i].target is your currently clicked on node and styling each of those links as you see fit.

Resources