move text on click d3js collapsible trees - d3.js

I am using the collapsible tree in d3js and want to create the functionality that the text of the children will always be on the right hand side of the node and the text the parent will be on the left hand side.
And this updates every time a node is clicked.
the text attr are set in the chunk below.
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.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);

Two things are required. You modify the x position and anchor based just on children being available (and not hidden children):
.attr("text-anchor", function(d) {
return d.children ? "end" : "start";
})
You also need to move the position to the update selection so it'll do it on redraw:
nodeUpdate.select("text")
.style("fill-opacity", 1)
.attr("text-anchor", function(d) {
return d.children ? "end" : "start";
})
.attr("x", function(d) {
return d.children ? -10 : 10;
});
Here's some full running code.
Stack snippet:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("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("flare.json", function(error, flare) {
var flare = {"name":"flare","children":[{"name":"analytics","children":[{"name":"cluster","children":[{"name":"AgglomerativeCluster","size":3938},{"name":"CommunityStructure","size":3812},{"name":"HierarchicalCluster","size":6714},{"name":"MergeEdge","size":743}]},{"name":"graph","children":[{"name":"BetweennessCentrality","size":3534},{"name":"LinkDistance","size":5731},{"name":"MaxFlowMinCut","size":7840},{"name":"ShortestPaths","size":5914},{"name":"SpanningTree","size":3416}]},{"name":"optimization","children":[{"name":"AspectRatioBanker","size":7074}]}]},{"name":"animate","children":[{"name":"Easing","size":17010},{"name":"FunctionSequence","size":5842},{"name":"interpolate","children":[{"name":"ArrayInterpolator","size":1983},{"name":"ColorInterpolator","size":2047},{"name":"DateInterpolator","size":1375},{"name":"Interpolator","size":8746},{"name":"MatrixInterpolator","size":2202},{"name":"NumberInterpolator","size":1382},{"name":"ObjectInterpolator","size":1629},{"name":"PointInterpolator","size":1675},{"name":"RectangleInterpolator","size":2042}]},{"name":"ISchedulable","size":1041},{"name":"Parallel","size":5176},{"name":"Pause","size":449},{"name":"Scheduler","size":5593},{"name":"Sequence","size":5534},{"name":"Transition","size":9201},{"name":"Transitioner","size":19975},{"name":"TransitionEvent","size":1116},{"name":"Tween","size":6006}]},{"name":"data","children":[{"name":"converters","children":[{"name":"Converters","size":721},{"name":"DelimitedTextConverter","size":4294},{"name":"GraphMLConverter","size":9800},{"name":"IDataConverter","size":1314},{"name":"JSONConverter","size":2220}]},{"name":"DataField","size":1759},{"name":"DataSchema","size":2165},{"name":"DataSet","size":586},{"name":"DataSource","size":3331},{"name":"DataTable","size":772},{"name":"DataUtil","size":3322}]},{"name":"display","children":[{"name":"DirtySprite","size":8833},{"name":"LineSprite","size":1732},{"name":"RectSprite","size":3623},{"name":"TextSprite","size":10066}]},{"name":"flex","children":[{"name":"FlareVis","size":4116}]},{"name":"physics","children":[{"name":"DragForce","size":1082},{"name":"GravityForce","size":1336},{"name":"IForce","size":319},{"name":"NBodyForce","size":10498},{"name":"Particle","size":2822},{"name":"Simulation","size":9983},{"name":"Spring","size":2213},{"name":"SpringForce","size":1681}]},{"name":"query","children":[{"name":"AggregateExpression","size":1616},{"name":"And","size":1027},{"name":"Arithmetic","size":3891},{"name":"Average","size":891},{"name":"BinaryExpression","size":2893},{"name":"Comparison","size":5103},{"name":"CompositeExpression","size":3677},{"name":"Count","size":781},{"name":"DateUtil","size":4141},{"name":"Distinct","size":933},{"name":"Expression","size":5130},{"name":"ExpressionIterator","size":3617},{"name":"Fn","size":3240},{"name":"If","size":2732},{"name":"IsA","size":2039},{"name":"Literal","size":1214},{"name":"Match","size":3748},{"name":"Maximum","size":843},{"name":"methods","children":[{"name":"add","size":593},{"name":"and","size":330},{"name":"average","size":287},{"name":"count","size":277},{"name":"distinct","size":292},{"name":"div","size":595},{"name":"eq","size":594},{"name":"fn","size":460},{"name":"gt","size":603},{"name":"gte","size":625},{"name":"iff","size":748},{"name":"isa","size":461},{"name":"lt","size":597},{"name":"lte","size":619},{"name":"max","size":283},{"name":"min","size":283},{"name":"mod","size":591},{"name":"mul","size":603},{"name":"neq","size":599},{"name":"not","size":386},{"name":"or","size":323},{"name":"orderby","size":307},{"name":"range","size":772},{"name":"select","size":296},{"name":"stddev","size":363},{"name":"sub","size":600},{"name":"sum","size":280},{"name":"update","size":307},{"name":"variance","size":335},{"name":"where","size":299},{"name":"xor","size":354},{"name":"_","size":264}]},{"name":"Minimum","size":843},{"name":"Not","size":1554},{"name":"Or","size":970},{"name":"Query","size":13896},{"name":"Range","size":1594},{"name":"StringUtil","size":4130},{"name":"Sum","size":791},{"name":"Variable","size":1124},{"name":"Variance","size":1876},{"name":"Xor","size":1101}]},{"name":"scale","children":[{"name":"IScaleMap","size":2105},{"name":"LinearScale","size":1316},{"name":"LogScale","size":3151},{"name":"OrdinalScale","size":3770},{"name":"QuantileScale","size":2435},{"name":"QuantitativeScale","size":4839},{"name":"RootScale","size":1756},{"name":"Scale","size":4268},{"name":"ScaleType","size":1821},{"name":"TimeScale","size":5833}]},{"name":"util","children":[{"name":"Arrays","size":8258},{"name":"Colors","size":10001},{"name":"Dates","size":8217},{"name":"Displays","size":12555},{"name":"Filter","size":2324},{"name":"Geometry","size":10993},{"name":"heap","children":[{"name":"FibonacciHeap","size":9354},{"name":"HeapNode","size":1233}]},{"name":"IEvaluable","size":335},{"name":"IPredicate","size":383},{"name":"IValueProxy","size":874},{"name":"math","children":[{"name":"DenseMatrix","size":3165},{"name":"IMatrix","size":2815},{"name":"SparseMatrix","size":3366}]},{"name":"Maths","size":17705},{"name":"Orientation","size":1486},{"name":"palette","children":[{"name":"ColorPalette","size":6367},{"name":"Palette","size":1229},{"name":"ShapePalette","size":2059},{"name":"SizePalette","size":2291}]},{"name":"Property","size":5559},{"name":"Shapes","size":19118},{"name":"Sort","size":6887},{"name":"Stats","size":6557},{"name":"Strings","size":22026}]},{"name":"vis","children":[{"name":"axis","children":[{"name":"Axes","size":1302},{"name":"Axis","size":24593},{"name":"AxisGridLine","size":652},{"name":"AxisLabel","size":636},{"name":"CartesianAxes","size":6703}]},{"name":"controls","children":[{"name":"AnchorControl","size":2138},{"name":"ClickControl","size":3824},{"name":"Control","size":1353},{"name":"ControlList","size":4665},{"name":"DragControl","size":2649},{"name":"ExpandControl","size":2832},{"name":"HoverControl","size":4896},{"name":"IControl","size":763},{"name":"PanZoomControl","size":5222},{"name":"SelectionControl","size":7862},{"name":"TooltipControl","size":8435}]},{"name":"data","children":[{"name":"Data","size":20544},{"name":"DataList","size":19788},{"name":"DataSprite","size":10349},{"name":"EdgeSprite","size":3301},{"name":"NodeSprite","size":19382},{"name":"render","children":[{"name":"ArrowType","size":698},{"name":"EdgeRenderer","size":5569},{"name":"IRenderer","size":353},{"name":"ShapeRenderer","size":2247}]},{"name":"ScaleBinding","size":11275},{"name":"Tree","size":7147},{"name":"TreeBuilder","size":9930}]},{"name":"events","children":[{"name":"DataEvent","size":2313},{"name":"SelectionEvent","size":1880},{"name":"TooltipEvent","size":1701},{"name":"VisualizationEvent","size":1117}]},{"name":"legend","children":[{"name":"Legend","size":20859},{"name":"LegendItem","size":4614},{"name":"LegendRange","size":10530}]},{"name":"operator","children":[{"name":"distortion","children":[{"name":"BifocalDistortion","size":4461},{"name":"Distortion","size":6314},{"name":"FisheyeDistortion","size":3444}]},{"name":"encoder","children":[{"name":"ColorEncoder","size":3179},{"name":"Encoder","size":4060},{"name":"PropertyEncoder","size":4138},{"name":"ShapeEncoder","size":1690},{"name":"SizeEncoder","size":1830}]},{"name":"filter","children":[{"name":"FisheyeTreeFilter","size":5219},{"name":"GraphDistanceFilter","size":3165},{"name":"VisibilityFilter","size":3509}]},{"name":"IOperator","size":1286},{"name":"label","children":[{"name":"Labeler","size":9956},{"name":"RadialLabeler","size":3899},{"name":"StackedAreaLabeler","size":3202}]},{"name":"layout","children":[{"name":"AxisLayout","size":6725},{"name":"BundledEdgeRouter","size":3727},{"name":"CircleLayout","size":9317},{"name":"CirclePackingLayout","size":12003},{"name":"DendrogramLayout","size":4853},{"name":"ForceDirectedLayout","size":8411},{"name":"IcicleTreeLayout","size":4864},{"name":"IndentedTreeLayout","size":3174},{"name":"Layout","size":7881},{"name":"NodeLinkTreeLayout","size":12870},{"name":"PieLayout","size":2728},{"name":"RadialTreeLayout","size":12348},{"name":"RandomLayout","size":870},{"name":"StackedAreaLayout","size":9121},{"name":"TreeMapLayout","size":9191}]},{"name":"Operator","size":2490},{"name":"OperatorList","size":5248},{"name":"OperatorSequence","size":4190},{"name":"OperatorSwitch","size":2581},{"name":"SortOperator","size":2023}]},{"name":"Visualization","size":16540}]}]};
// if (error) throw error;
root = flare;
root.x0 = height / 2;
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
//});
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("dy", ".35em")
.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.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1)
.attr("text-anchor", function(d) {
return d.children ? "end" : "start";
})
.attr("x", function(d) {
return d.children ? -10 : 10;
});
// 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});
});
// 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>

Related

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>

find specific nodes in d3js tree

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.

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.

How do you push a new node onto an existing node in D3 in a tree layout?

I have tried this this, and it works but it replaces my whole tree because it is at the zero index of the nodes.
var newguys= jQuery.parseJSON(getLevel(2,'123456'));
var newnodes = tree.nodes(newguys).reverse();
d.children = newnodes[0];
update(d);
So I tried push,
// Toggle children on click.
function click(d) {
var newguys= jQuery.parseJSON(getLevel(2,'123456'));
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
nodes.children.push(newguys);
update(d);
}
Update is the standard update function of d3 (Let us assume I have the json already read). I cannot get the new data on the end of the parent element (or anywhere else.)
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 1800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("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 + ")");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.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 ? -10 : 10; })
.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.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.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});
});
// 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;
});
}
I do not know if this is the correct way, but here is what I did to finally insert a new node, code modified from all my travels. I can't find this anywhere, so I hope it helps someone:
// Toggle children.
function toggle(d) {
//works.
var a;
if(d.children)
{
a={"name": "No More Records"};
d.children.push(a);
} else {
d3.xhr(url)
.header("X-Requested-With", "XMLHttpRequest")
.header("Content-Type", "application/x-www-form-urlencoded")
.post("level=1&emp="+d.empid, function (error, request) {
if (error) return console.warn(error.responseText);
var folks = jQuery.parseJSON(request.responseText);
d.children = [folks];
update_new(d);
});
}
}
I have update_new because I did some things to make the added child nodes look different, but updating is essentially the same as the stock update(d) function.

Resources