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.
Related
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>
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>
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>
I am trying to find specific nodes on the tree layout. I found this example on plunkr:
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(function(d1){d1.parent = d; collapse(d1);});
d.children = null;
}}
function find(d, name) {
if (d.name == name){
while(d.parent){
d = d.parent;
click(d)
}
return;
}
if (d.children) {
d.children.forEach(function(d){find(d, name)});
} else if(d._children){
d._children.forEach(function(d){find(d, name)});
}}
[root].forEach(collapse);
var name = "layout"
find (root, name)
update(root);
http://plnkr.co/edit/Ce5Ub8PWXblSMcrYlkHa?p=preview which finds a specif node given a name. I adopted it to find a node given an ID.I have two problems with the implementation:
1- The code generates an error in the console "Error: attribute transform: Expected number, "translate(undefined,undefi…". Which I think is due to the x and y coordinates becoming null, but I cannot identify the exact condition. This error appears in the original example as well.
2- If I call the find routine once (by clicking the find button which calls find_name() and commenting out the second call to the find function) , it works, however if I call it more than once (by un-commenting and enabling the second call to find), the tree collapses to the root node.
Any help in understanding this behavior is much appreciated.
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// ************** Generate the tree diagram *****************
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"}];
//------------------
//the find function is global
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(function(d1){d1.parent = d; collapse(d1);});
d.children = null;
}
}
function find(d, sid) {
if (d.sid == sid){
while(d.parent){
d = d.parent;
click(d)
}
return;
}
if (d.children) {
d.children.forEach(function(d){find(d, sid)});
} else if(d._children){
d._children.forEach(function(d){find(d, sid)});
}
}
function find_name(){
[root].forEach(collapse); //iterate each node and collapse
find(root,25);
find(root,31);
update(root);
}
//------------------
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;
var enteredId = 7402;
//zoom functionality
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var tree = d3.layout.tree()
.nodeSize([110, 50]);
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)
.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;
root.y0 = 0;
//------------------------------------------------------
var nodes = tree.nodes(root);
function collapseLevel(d) {
if (d.children && d.depth > 23) {
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");
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
// The class is used to remove the additional text later
var info = g.append('text')
.classed('info', true)
.attr('x', 20)
.attr('y', 10)
.text(function(d) { return d.name_ar + " " + d.sid }); // need to put good background for tooltip
})
.on("mouseout", function() {
// Remove the info text on mouse out.
d3.select(this).select('text.info').remove()
});
;
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);
//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>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 14px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
<header>
<button onclick="find_name()">find</button>
</header>
</head>
The reason for the error in the first question "The code generates an error in the console "Error: attribute transform: Expected number, "translate(undefined,undefi" is due to the SID being character. I converted it to integer and no error is generated.
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.