Avoid overlapping of nodes in tree layout in d3.js - d3.js

I have created a collapsible tree to represent some biological data.
In this tree the size of the node represents the importance of the node. As I have a huge data and also the sizes of the nodes vary,they overlap over each other. I need to specify the distance between the sibling nodes.
I tried tree.separation() method but it didn't work.
Code is as follows :
tree.separation(seperator);
function seperator(a, b)
{
if(a.parent == b.parent)
{
if((a.abundance == (maxAbd)) || (b.abundance == (maxAbd)))
{
return 2;
}
else
{
return 1;
}
}
}
This is giving me error saying:
Unexpected value translate(433.33333333333337,NaN) parsing transform attribute.
I understand that that after adding the separation method it is unable to calculate the x coordinate for the nodes. Can anyone please help me with how to do this?
I also tried modifying the source code as suggested in https://groups.google.com/forum/#!topic/d3-js/7Js0dGrnyek but that did not work either.
Please suggest some solution.

I had the same problem. This is how I solved it. I have width assigned to each node, height for now is the same for all nodes (basically nodes with smaller height than nodeHeight, get centered vertically):
var tree = d3.layout.tree().nodeSize([1, nodeHeight])
.separation(function(a, b) {
var width = a.width + b.width,
distance = width / 2 + 16; // horizontal distance between nodes = 16
return distance;
}),
nodes = tree.nodes(data),
links = tree.links(nodes);
Hope this helps.

SiegS's answer just works fine!
My situation is that: My node is actually some text, which may have various width, which I don't know in advance. So I need to calculate the width of each nodes first.
I have a JSON object json_data as my data.
var tree = d3.layout.tree()
.sort(null)
.size([500,500])
.children( some_function_identify_children );
var nodes = tree.nodes(json_data); //which doesn't consider the node's size;
var links = tree.links(nodes);
// append a svg;
var layoutRoot = d3.select("body")
.append("svg:svg").attr("width","600").attr("height","600")
.append("svg:g")
.attr("class","container");
var nodeGroup = layoutRoot.selectAll("g.node")
.data(nodes)
.enter().append("text").text(function(d){return d.text;});
// now we knows the text of each node.
//calculate each nodes's width by getBBox();
nodeGroup.each(function(d,i){d["width"] = this.getBBox().width;})
//set up a new tree layout which consider the node width.
var newtree = d3.layout.tree()
.size([500,500])
.children(function(d) {return d.children;})
.separation(function(a,b){
return (a.width+b.width)/2+2;
});
//recalcuate the node's x and y by newtree
newtree.nodes(nodes[0]); //nodes[0] is the root
links = newtree.links(nodes);
//redraw the svg using new nodes and links.
...
Hope this will help.

Related

D3 circle packing - swithc color for each branch

I have a d3 circle packing, and I would like to find an elegant way to color each branch differently. I have a depth property, so it is easy to apply a gradient based on it.
To color each branch differently I think it requires to know what color has been attributed to the precedent sibling but I am not sure how to find it.
Any help is appreciated, I am not sure how to get started.
thanks
the coloring is produced by the following that will return a color based on the depth in the tree:
var color = d3.scale
.linear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
function color_for_node(node) {
var out;
//some other operation removed here for simplification
out = node.children ? color(node.depth) : null;
return out;
}
It is then used by the following (simplified) :
circle = svg
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.someOtherStuff()
.style("fill", color_for_node) //<--used here
(I am working on a solution, I will edit this again once it is done. The key in this solution is not really depending on D3, but on the data tree. Luckily I found out that each node references its parent, so I think it is possible to find out of a node belongs to a specific branch which what I am doing now )
Here is a generic solution. Since d3 collection are properly ordered, every time a root branch node is met, it is followed by its childs, until it reaches another root branch node. Therefore we can effectively switch the color from one branch to another without havin gto check the parents of each node, which would be costly computation.
const ranges = [
["hsl(120,0%,90%)", "hsl(120,0%,50%)"], //grey
["hsl(228,80%,80%)", "hsl(300,30%,40%)"], //light blue
["hsl(300,80%,80%)", "hsl(360,30%,40%)"], //light pink to brick
["hsl(152,80%,80%)", "hsl(228,30%,40%)"], //light green-blue
["hsl(80,80%,80%)", "hsl(120,30%,40%)"], //light yellow-green
["hsl(120,80%,80%)", "hsl(150,30%,40%)"], //light green
["hsl(0,80%,80%)", "hsl(40,30%,40%)"], //light pink to brown
["hsl(40,80%,80%)", "hsl(80,30%,40%)"] //sand
];
let rangeIndex = 0;
const color = node => {
let range = ranges[rangeIndex];
if (node.depth === 1) {
rangeIndex = rangeIndex < ranges.length - 1 ? rangeIndex + 1 : 0;
range = ranges[rangeIndex];
}
return d3.scale
.linear()
.domain([-1, 5])
.range(range)
.interpolate(d3.interpolateHcl)(node.depth);
};

D3 v4 (Tree Diagram) - Dynamically calculate the height of the SVG given dynamic number of nodes

I dynamically am changing the data for a Tree Diagram, and don't know what the height of the SVG should be. How can I calculate the height of the Tree Diagram to update the SVG dimensions?
Fiddle example: https://jsfiddle.net/intelligence_ai/guesukv6/
Please view the code on the fiddle, SO requires that I post some code with fiddle links:
var treemap = d3.tree().nodeSize([40, 40]);
root = d3.hierarchy(data, function(d) { return d.children; });
root.x0 = 100;
root.y0 = 0;
function update(source) {
// Assigns the x and y position for the nodes
var data = treemap(root);
// Compute the new tree layout.
var nodes = data.descendants(),
links = data.descendants().slice(1);
// Normalize for fixed-depth.
let connectorLength = 200; // the length of the lines in pixels
nodes.forEach(function(d){ d.y = d.depth * connectorLength});
nodes = nodes.filter(function(d){
return d.depth != 0;
})
...
Since you specified that the nodeSize is [40,40] and you have 129 nodes, the height should be:
var height = 40 * 129;
Since you want to calculate it dynamically, it should be:
var height = treemap.nodeSize()[1] * nodes.length;
Also, when specifying tree.nodeSize the root node will be positioned at 0,0. Therefore, you have to translate the main group:
.attr("transform", "translate("+ margin.left + "," + (height/2) + ")");
Here is the updated fiddle: https://jsfiddle.net/jxw0Ld3c/

How to tune horizontal node position in d3 sankey.js?

I am trying to plot some flow diagrams using d3's sankey.js.
I am stuck at arranging nodes x positions in the diagrams.
t2_a should be in same column as t2_b as they represent quantity from same time period. However by default this is placed at the end which gives wrong interpretation.
I can arrange manually for small number of nodes but its really difficult when number of nodes increase. Any help or suggestion would be highly appreciated.
In sankey.js comment the moveSinksRight call in computeNodeBreadths:
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
nextNodes.push(link.target);
});
});
remainingNodes = nextNodes;
++x;
}
//
// moveSinksRight(x); <-- comment this
scaleNodeBreadths((width - nodeWidth) / (x - 1));
}

Violin plot in d3

I need to build a violin point with discrete data points in d3.
Example:
I am not sure how to align the center for each value on X axis. The default behavior will overlay all the points with same X and Y value, however I would like the points to be offset while being center aligned e.g. 5.1 has 3 values in control group and 4.5 has 2 values, all center aligned. It is easy to do so for either right or left aligned by doing a transformation of each point by a specified amount. However, the center alignment seems to be quite hacky.
A hacky way would be to manually transform the X value by maintaining a couple of arrays to see whether this is the first, even or odd number of element and place it according my specifying the value. Is there a proper way to handle this?
The only example of violin plot in d3 I found was here - which implements a probability distribution rather than the discrete values which I require.
"A hacky way would be to manually transform the X value by maintaining a couple of arrays" - that's pretty much the way most d3 layouts work :-) . Discretise your data set by the y value (weight), keeping a total of the data points in each discrete group and a group index for each datum. Then use those to calculate offsets x-ways and the rounded y-value.
See https://jsfiddle.net/n444k759/4/
// below code assumes a svg and g group element are present (they are in the jsfiddle)
var yscale = d3.scale.linear().domain([0,10]).range([0,390]);
var xscale = d3.scale.linear().domain([0,2]).range ([0,390])
var color = d3.scale.ordinal().domain([0,1]).range(["red", "blue"]);
var data = [];
for (var n = 0; n <100; n++) {
data.push({weight: Math.random() * 10.0, category: Math.floor (Math.random() * 2.0)});
}
var groups = {};
var circleR = 5;
var discreteTo = (circleR * 2) / (yscale.range()[1] / yscale.domain()[1]);
data.forEach (function(datum) {
var g = Math.floor (datum.weight / discreteTo);
var cat = datum.category;
var ref = cat+"-"+g;
if (!groups[ref]) { groups[ref] = 0; }
datum.groupIndex = groups[ref];
datum.discy = yscale (g * discreteTo); // discrete
groups[ref]++;
});
data.forEach (function(datum) {
var cat = datum.category;
var g = Math.floor (datum.weight / discreteTo);
var ref = cat+"-"+g;
datum.offset = datum.groupIndex - ((groups[ref] - 1) / 2);
});
d3.select("svg g").selectAll("circle").data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return 50 + xscale(d.category) + (d.offset * (circleR * 2)); })
.attr("r", circleR)
.attr("cy", function(d) { return 10 + d.discy; })
.style ("fill", function(d) { return color(d.category); })
;
The above example discretes into groups according to the size of the display and the size of the circle to display. You might want to discrete by a given interval and then work out the size of circle from that.
Edit: Updated to show how to differentiate when category is different as in your screenshot above

Add a vertex in the middle of a path with d3js

How can I modify a path according to user actions?
For example: I have a path composed of three points A, B and C. When the user clicks on the path (somewhere other than an existing point) I would like add a new point at that position to the path. How can I insert the new point into the path at the correct location?
Here you can find an example
var nodes = [[30, 130], [250, 250], [400,130]];
var line = d3.svg.line();
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 5000);
var path = svg.append("path")
.datum(nodes)
.attr("class", "line")
.call(update);
path.on("click", insertNode);
function update() {
svg.select("path").attr("d", line);
var circle = svg.selectAll("circle")
.data(nodes, function(d) { return d; });
}
function insertNode(data) {
//create the new node:
var newNode = [];
var newNode = d3.mouse(svg.node());
//find coordinates relative to the plotting region
nodes.push(newNode); //add to your nodes array
update();
}
If you click on the first segment (sx -> dx) a new segment is added from the end of the path to the new node because I add the new node at the end of the nodes array.The right behavior is a new node in the path (nodes array) between the nodes [30, 130] and [250, 250]
Thanks!
aGO!
You can't simply add the new node to the array, you have to determine its position first. One way of doing this is to compute the angle to all points. When the absolute values are the same, you know that you've found your insert position. The only snag is that because of the width of the line, it won't be exactly 180 degrees, so you have to account for that. The following code attempts this and splices the new node into the array.
var idx = 0, prevAngle;
nodes.forEach(function(n, i) {
var angle = Math.abs(Math.atan((n[1] - newNode[1]) / (n[0] - newNode[0])));
if(Math.abs(angle - prevAngle) < 0.05) {
idx = i;
}
prevAngle = angle;
});
tmp = nodes.slice(0, idx);
tmp.push(newNode);
nodes = tmp.concat(nodes.slice(idx));
Complete example here.
The link's data object contains the source and target node data objects, so you can use that information to split the link in two, connected through the new node.
Sample code, assuming linkElements is your d3 selection of the link <path> or <line> elements, and links and nodes are the data arrays corresponding to force.links() and force.nodes():
linkElements.on("click", insertNode);
function insertNode(linkData){ //parameter is the *link's* data object
//create the new node:
var newNode = {};
var clickPoint = d3.mouse(this.parentNode);
//find coordinates relative to the plotting region
newNode.x = xScale.invert(clickPoint[0]);
newNode.y = yScale.invert(clickPoint[1]);
//convert coordinates to data values
nodes.push(newNode); //add to your nodes array
//create a new link for the second half of the old link:
var newLink = {source:newNode, target:linkData.target};
links.push(newLink); //add to your links array
//update the old link to point to the new node:
linkData.target = newNode;
update();
}
This of course only updates the data objects for the nodes and links, your update() function will have to update the force layout and create the entering SVG elements.

Resources