Trying to get the tree layout in D3 to render the child nodes with children closer together. Here is the code:
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,
rectW = 185,
rectH = 45;
var tree = d3.layout.tree()
.nodeSize([200, 40]);
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([0,1]).on("zoom", redraw))
.append("g")
.attr("transform", "translate(" + 30 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
root.x0 = width / 2;
root.y0 = 0;
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function (d) {
d.y = (d.depth * 120);
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
// Add rectangles to nodes
nodeEnter.append("rect")
.attr("width", function (d) {
return rectW;
//return d._children ? "lightsteelblue" : "#fff";
})
.attr("height", rectH)
.attr("class", function (d) {
return "rect-" + d.state;
});
// Add text to nodes
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("class", function (d) {
return "rect-" + d.state;
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// 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", function (d) {
return "link " + d.target.dest;
})
.attr("x", rectW / 2)
.attr("d", function (d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function (d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
}).remove();
// Update the link labels…
var linkLabel = svg.selectAll("text.link-label")
.data(links, function (d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
linkLabel.enter().insert("text", "path")
.text(function (d) {
return (d.target.state !== "open") ? null : "If " + d.target.dest;
})
.attr("class", function (d) {
return "link-label " + d.target.dest;
})
.attr("x", function (d) {
return d.target.x + rectW / 2;
})
.attr("y", function (d) {
return d.target.y + rectH * 2 - 30;
})
.attr('text-anchor', 'middle')
.style("fill-opacity", 0);;
// Transition link labels
linkLabel.transition()
.delay(duration)
.style("fill-opacity", 1);
// 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) {
return false;
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Here is a jsbin of the above code. What I would like to see is the navy-colored "Node 1" and "Node 2" be closer together while preserving the distance between the nodes without children (grey nodes).
Is this possible and how would I do so?
I found the answer. It's the separation method. This got me what I was looking for:
tree.separation(function separation(a, b) {
return a.parent == b.parent ? 1 : 1.5;
});
Related
I am implementing a d3 code in power bi for a collapsible tree. Each node in my tree is a rectangular box. But for some reason the nodes get overlapped if the node size is large. Here is my code:
// ADD: translate function for the data
function toJSON(data) {
var flare = { name: "All Products", children: [] },
levels = ["productcategory","productsubcategory"];
// For each data row, loop through the expected levels traversing the output tree
data.forEach(function(d){
// Keep this as a reference to the current level
var depthCursor = flare.children;
// Go down one level at a time
levels.forEach(function( property, depth ){
// Look to see if a branch has already been created
var index;
depthCursor.forEach(function(child,i){
if ( d[property] == child.name ) index = i;
});
// Add a branch if it isn't there
if ( isNaN(index) ) {
depthCursor.push({ name : d[property], children : []});
index = depthCursor.length - 1;
}
// Now reference the new child array as we go deeper into the tree
depthCursor = depthCursor[index].children;
// This is a leaf, so add the last element to the specified branch
if ( depth === levels.length - 1 ) depthCursor.push({ name : d.product, size : d.revenue, revenue : d.revenue});
});
});
// End of conversion
return flare;
}
// Aggregate the revenue at each level
function aggregateRevenue(node) {
if(node.children) {
node.children.forEach(function(child) {
aggregateRevenue(child);
node.revenue = d3.sum(node.children, function(d) { return d.revenue; });
});
}
}
// Set the margins
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = pbi.width - margin.left - margin.right, // ALTER: Changed fixed width with the 'pbi.width' variable
height = pbi.height - margin.top - margin.bottom; // ALTER: Changed fixed height with the 'pbi.height' variable
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]; });
// Zoom functionality:
var zoom = d3.behavior.zoom()
.scaleExtent([0.1, 10])
.on("zoom", zoomed);
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
// SVG creation
var svg = d3.select("#chart") // ALTER: Select SVG object; no need to create it
.attr("width", width + margin.left + margin.right) // ALTER: Add complete width
.attr("height", height + margin.top + margin.bottom) // ALTER: Add complete height
.call(zoom) // Add zoom behavior here
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ALTER: Replaced the d3.json function with the pbi variant: pbi.dsv
pbi.dsv(function(data) {
var flare = toJSON(data); // ALTER: add extra convertion step to parent/child JSON
root = flare;
aggregateRevenue(flare);
root.x0 = height / 2;
root.y0 = 0;
// collapse the tree
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
});
d3.select(self.frameElement).style("height", height + margin.top + margin.bottom);
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);
// Total revenue ( to calculate individual perncentages )
var totalRevenue = d3.sum(data, function(d) { return d.revenue; });
// Append rectangular node
nodeEnter.append("rect")
.attr("width", 150)
.attr("height", 80)
.attr("x", -75)
.attr("y", -40)
.style("fill", function(d) { return d._children ? pbi.colors[0] : pbi.colors[1]; });
// Append name of the node
nodeEnter.append("text")
.text(function(d) { return d.name; })
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("fill", "white")
.style("font-weight", "bold")
.attr("x", 0)
.attr("y", -15);
// Append the green stacked bar line
nodeEnter.append("rect")
.attr("class", "percentage")
.attr("width", function(d) { return ((d.revenue / totalRevenue) * 100).toFixed(2); })
.attr("height", 10)
.attr("x", -50)
.attr("y", -5)
.attr("fill", "green");
// Append the blue stacked bar line
nodeEnter.append("rect")
.attr("class", "non-percentage")
.attr("width", function(d) { return 100 - ((d.revenue / totalRevenue) * 100).toFixed(2); })
.attr("height", 10)
.attr("x", function(d) { return ((d.revenue / totalRevenue) * 100).toFixed(2) - 50; })
.attr("y", -5)
.attr("fill", "blue");
// Append the text for green bar
nodeEnter.append("text")
.attr("x", -70)
.attr("y", 0)
.attr("text-anchor", "start")
.attr("dominant-baseline", "central")
.text(function(d) { return ((d.revenue / totalRevenue) * 100).toFixed(2) + "%"; })
.style("fill", "white")
.style("font-weight", "bold");
// Append the text for blue bar
nodeEnter.append("text")
.attr("x", 35)
.attr("y", 0)
.attr("text-anchor", "start")
.attr("dominant-baseline", "central")
.text(function(d) { return (100 - ((d.revenue / totalRevenue) * 100)).toFixed(2) + "%"; })
.style("fill", "white")
.style("font-weight", "bold");
// Append node's revenue inside the node
nodeEnter.append("text")
.text(function(d) { return "Revenue = $" + d.revenue; })
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("fill", "white")
.style("font-weight", "bold")
.attr("x", 0)
.attr("y", 15);
// Append % of total share shared by the node
//nodeEnter.append("text")
//.text(function(d) { return "% Total Share = " + ((d.revenue / totalRevenue) * 100).toFixed(2) + "%"; })
//.attr("text-anchor", "middle")
//.attr("dominant-baseline", "central")
//.style("fill", "white")
//.style("font-weight", "bold")
//.attr("x", 0)
//.attr("y", 15);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
// 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();
// 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);
}
I also tried of following code but this does not seem to work also:
var treemap = d3.tree()
.size([10*height, width])
.separation(function separation(a, b) { return a.parent == b.parent ? 2 : 2; });
A help in this regard shall be appreciated.
You need to change the node size to your box(rectangle size) that is 80
var treemap = d3.tree()
.size([80, width])
.separation(function separation(a, b) { return a.parent == b.parent ? 2 : 2; });
I am trying to get the tree nodes to line up next to the lines (paths), I cannot work it out.
This was a top to bottom tree and I have tried to flip into a left to right tree but I think the calculations are off.
Any help would be really appreciated.
Please see screenshot and code in Plunker. http://plnkr.co/edit/9pxJLz?p=preview
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 100 - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 120,
rectH = 30;
var tree = d3.layout.tree().nodeSize([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var accountSvg = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([0.5, 3]).on("zoom", redraw)).on("dblclick.zoom", null)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.left + ")");
d3.json("flare.json", function(error, flare) {
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);
});
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([250, 20]);
d3.select("body").style("height", "455");
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 = accountSvg.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("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;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
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)
.style("fill", '#404080');
// 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("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = accountSvg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d) {
var s = {
x: d.source.x0 + -15,
y: d.source.y0 + 120
}
var t = {
x: d.target.x + 15,
y: d.target.y + 120
}
return diagonal({
source: s,
target: t
})
});
//.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;
});
}
var doubleClickTime = 0;
var threshold = 200;
// Toggle children on click.
function click(d) {
var t0 = new Date();
if (t0 - doubleClickTime > threshold) {
setTimeout(function() {
if (t0 - doubleClickTime > threshold) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
}, threshold);
}
}
// Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
accountSvg.attr("transform",
"translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
You're translating all the links:
.attr("transform", function (d) {
return "translate(" + rectW + "," + rectH / 2 + ")";
})
Don't do that. Instead of that, change the diagonal:
link.transition()
.duration(duration)
.attr("d", function(d) {
var s = {
y: d.source.y + rectW,
x: d.source.x + rectH / 2
};
var t = {
x: d.target.x + rectH / 2,
y: d.target.y
};
return diagonal({
source: s,
target: t
})
});
Here is the updated plunker: http://plnkr.co/edit/LnuoQY7R0tDWR4EnW1Rg?p=preview
I am trying to create a tree layout with d3 V4. This is done by following the example https://jsfiddle.net/augburto/YMa2y/ (this is in v3).
I am trying to implement this within angularjs, so this code is within a directive.
Everything works fine except that the root node gets positioned to 0,0 even though i have applied a transform (translate) on the svg.
Note that, i am using nodeSize on the d3.tree() so that i can have separation between nodes
What is going wrong here?
link: function(scope, element, attrs) {
var margin = { top: 20, right: 120, bottom: 20, left: 120},
width = 1090- margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30;
var svg = d3.select(element[0]).append("svg");
svg.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (width + margin.left + margin.right) / 2 + "," + 0 + ")");
scope.$watch('model', function(newVals, oldVals) {
if (oldVals !== newVals) {
return scope.render(newVals);
}
return {}
}, true);
scope.render = function (data) {
root = d3.hierarchy(data, function (d) { return d.children; });
root.x0 = 0;
root.y0 = height/ 2;
var treemap = d3.tree()
.nodeSize([rectW, rectH])
.separation(function (a, b) {
return a.parent == b.parent ? 1.10 : 2;
});// make separation accessor 1;
// Assigns the x and y position for the nodes
var treeData = treemap(root);
svg.selectAll('*').remove();
function diagonal(source, d) {
return "M" + source.x+ "," + source.y
+ "C" + source.x + "," + (source.y + d.y) / 2
+ " " + d.x+ "," + (source.y + d.y) / 2
+ " " + d.x + "," + d.y;
}
// 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) {
//d3.tree().size([height, width]);
// 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 });
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
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.data.Name;
});
// Transition nodes to their new position.
var nodeUpdate = nodeEnter.merge(node);
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.id;
});
// Enter any new links atet the parent's previous position.
var linkEnter = link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("d", function (d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal(o, o)
}
);
//d3.linkVertical()
// .x(function (d) { return d.y; })
// .y(function (d) { return d.x; }));
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition links to their new position.
linkUpdate.transition()
.duration(duration)
.attr("d", function(d) {
var s = {
x: d.x + rectW / 2,
y: d.y
};
var dest = {
x: d.parent.x + rectW / 2,
y: d.parent.y + rectH
};
return diagonal(s, dest)
});;
//d3.linkHorizontal()
// .x(function (d) { return d.y; })
// .y(function (d) { return d.x; }));
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
//.attr("d", d3.linkVertical()
//.x(function (d) { return d.y; })
//.y(function (d) { return d.x; }))
.attr("d", function (d) {
var o = {
x: source.x,
y: source.y
};
return diagonal(o, 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);
}
}
}
The answer is here https://github.com/d3/d3-hierarchy/blob/master/README.md#tree_separation:
When a node size is specified, the root node is always positioned at ⟨0, 0⟩.
And here Centering of d3 tree changes when specifying nodeSize you can find the answer.
Cannot figure out how to start my tree with data collapsed. Tried all solutions i could find here already. Have included the snipped of javascript.
People have mentioned changing the JSON file readme.json which i don't have.
Have also tried to swap _children and children with no success.
Any help much appreciated.
<script src="d3/d3.min.js"></script>
<script>
var treeData = [
<?php echo $contents ?>
];
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 0, bottom: 0, left: 250},
width = 1800 - margin.right - margin.left,
height = 900 - 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 + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "400px");
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 * 360; });
// 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 + ")"; })
;
nodeEnter.on("click", function (d) {
click(d);
var x = d.jobdescription;
x = x.replace('\\','');
var g = d3.select(this); // The node
d3.select("body").select('div.tooltip').remove();
var div = d3.select("body").append("div")
.attr('pointer-events', 'none')
.attr("class", "tooltip")
.style("opacity", 1)
.html(x)
.style("left", (d.x + 50 + "px"))
.style("top", (d.y +"px"));
});
/*nodeEnter.on("mouseout", function (d) {
d3.select("body").select('div.tooltip').remove();
});*/
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; })
;
nodeEnter.append("text")
.attr("x", function(d) { var i = ((d.the_length) * 7) + 25; var i2 = -(d.the_length + 10); return d.children || d._children ? i2 : i; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "end"; })
.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", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// 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>
I have a D3 collapsible tree, the problem is that a single node can have upto 4000 leaves. I would like to make it scrollable or expandable, i.e. the height should work for both 10 leaf nodes or several 1000.
Currently my height parameter is static, can anyone tell me how I can make it dynamic?
The following is the code:
$(function(){
var m = [20, 120, 20, 120],
w = 1280 - m[1] - m[3],
h = 80000 - m[0] - m[2],
i = 0,
root;
var tree = d3.layout.tree()
.size([h, w]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var vis = d3.select("#modelPatterns").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
d3.json("./static/data/type1Tree.json", function(json) {
root = json;
root.x0 = h / 2;
root.y0 = 0;
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
}
// Initialize the display to show a few nodes.
root.children.forEach(toggleAll);
//toggle(root.children[1]);
//toggle(root.children[1].children[2]);
//toggle(root.children[9]);
//toggle(root.children[9].children[0]);
update(root);
});
In case it helps someone, I used the zoom function which enabled panning and zooming. This may suffice for what you're looking for when trying to make the graph scrollable so to speak.
D3.select('svg')
.call(D3.zoom()
.scaleExtent([-5, 8])
.extent([[0, 0], [300, 300]])
.on('zoom', () => {
D3.selectAll('g')
.attr('transform', D3.event.transform);
if (has('root.children', this) && this.root.children.length > 50) {
this.updateAfterInit(this.root);
} else {
this.update(this.root);
}
// this.centerNode(this.root);
})
.filter(() => {
const foundNode = this.N.findNodeByID(D3.event.srcElement.id.split('_')[1])
if ( !!foundNode && D3.event.type === 'dblclick' && foundNode.data.type === 'SearchRelationspec') {
return false;
} else {
return !D3.event.target.classList.contains('drawarea') && D3.event.type === 'dblclick';
}
})
You can modify your update function like below code as per your need...
function update(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// compute the new height
var levelWidth = [1];
var childCount = function(level, n) {
if(n.children && n.children.length > 0) {
if(levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level+1] += n.children.length;
n.children.forEach(function(d) {`
childCount(level + 1, d);
});
}
};
childCount(0, root);
newHeight = d3.max(levelWidth) * 60; // 20 pixels per line
tree = tree.size([newHeight, width]);
d3.select("svg").remove();//TO REMOVE THE ALREADY SVG CONTENTS AND RELOAD ON EVERY UPDATE
svg = d3.select("body").append("svg");
svg.attr("width", width + margin.right + margin.left)
.attr("height", newHeight + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// 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")
.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", "-.75em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-2);
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("dy", "1.00em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.info1; })
.style("fill-opacity", 1e-2);
// 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", 6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.selectAll("text")
.style("fill-opacity", 4);
// 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.selectAll("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;
});
It will give you a dynamic size tree and serve for any number of nodes in the tree.
I hope this will solve your purpose.