d3.js radial tree layout variable node length / text wrap node description - javascript

I am trying to create a radial hierarchy on d3.js based on mbostock's reingold-tilford tree example.
The block on bl.ocks.org is here: http://bl.ocks.org/ratnakarv/43087fb7f373338bf62c
Considering root node is 'Level 0', the text at some nodes at level 2 and some nodes at level 3 is encroaching on child node's text.
I am possible looking at two solutions:
1. Increasing the length of edges as the levels increase (as text length increases with levels in hierarchy)
2. including a word wrap function to give a neater look
or a combination of two.
Appreciate if some one can point me to ideas/code examples to incorporate these
[Note: I have a basic idea of d3.js. I am not a data visualizer and primarily constructing these visualizations to describe my main work]

D3's tree layout populates the x and y attributes on the data when you call tree.nodes(). Now it's just a matter of manipulating the y values to "spread out" the tree so that there's enough space between each level for your text.
A straightforward method of doing this is to simply scale each node's y value based on its depth. After the tree layout is created, do:
nodes.forEach(function(d) { d.y = d.depth * 180; });
Working fiddle here: http://jsfiddle.net/sk7krdrp/1/
You may even want to make the scaling a function of the maximum node text length.
This makes the tree a lot bigger, but it looks like that's what you want, as long as the texts don't overlap. If you want to keep the tree smaller and wrap text so that longer text appears in two or more lines, you could do that by wrapping each "line" in a tspan (example here). Of course, you will have to move around the neighboring nodes so that the wrapped text of a node doesn't overlap its neighbors.

Related

Is there any d3 function like scalePoint but for two dimensions?

I want to place clusters like in this block:
https://bl.ocks.org/SpaceActuary/d6b5ca8e5fb17842d652d0de21e88a05
However, I want the clusters to be on a 2d grid rather than in a line and specify range in each dimension so that I can have e.g. a short but wide lattice.
I tried playing with scalePoint range but couldn't get it to return an array.
I could manually compute positions, but would be a lot cleaner if there's already a built-in way to do this in d3.

Make two instances of d3.forceCollide() play nice together

I want two instances of d3.forceCollide(). In one, every node is pushed away from one another to prevent overlap. In the second, only a subset of nodes are pushed away from one another, with a much bigger radius.
To accomplish the second force, I tweak the initialize method to filter the incoming nodes, like so:
function selective(force,filter){
var init = force.initialize;
force.initialize = function(_){return init(_.filter(filter));};
return force;
}
var dpi = 90; // approximate pixels per inch in SVG
var size = dpi * (1/4); // quarter-inch unit size
var universally_applied =
d3.forceCollide()
.radius(size)
.strength(1);
var selectively_applied =
selective(
d3.forceCollide(),
function(d){return d.id === color;}
)
.radius(size*5)
.strength(1);
}
Now, this ALMOST works. I created a fiddle to see it in action: https://jsfiddle.net/jarrowwx/0dax43ue/38/ - every colored circle is supposed to repel every other circle of the same color, from a distance. Every other color, it just bumps into and pushes it out of the way.
If I do not change the order in which things are defined, then the selectively applied force is ONLY applied to the first color (red). If I shuffle the data array before applying forces, it is difficult to define exactly what happens, but the force is applied to some circles and not most of the others, even among the same color.
Any ideas what is going on here, or how to fix it?
The D3 team decided that this behavior was a bug (Isolating forces to a subset of nodes? #72), and fixed it. The fix was included in version 1.0.4 of d3-force, which is available as part of the full D3 build as of version 4.4.0.
The problem is resolved using the solution suggested by "Partial forces on nodes in D3.js", and the code works as intended now.

d3.js Respect weights for each treemap node

I'm currently working on a project using a modified Zoomable Treemap (http://bost.ocks.org/mike/treemap/) in d3.js but I have run into some difficulty in implementing this specific behavior that I'm looking for.
Ideally, each node in the tree would be sized according to its specified weight rather than the sum of the weights of its children. The only relevant question I could find (d3.js - Treemap where parent's value is greater than sum of its children) isn't quite what I'm looking for, though it could potentially be used to create a similar effect. However, attempting to use the filtered dummy node method to keep the parent's size constant when its children are modified results in ugly transitioning artifacts. I assume this due to the higher level being aware of the dummy node, but the lower level not being aware (as it is filtered, so it is not visible).
How might I go about implementing this behavior?
As it turns out, the implementation that I was using already had a function (accumulate(d)) that was responsible for this behavior. I solved this issue by rewriting it to always return the node's value directly like so:
function accumulate(d) {
var result = (d._children = d.children)
? d.value
: d.value;
if(d.children){
d.children.forEach(accumulate);
}
return result;
}
Now the nodes scale independently of their children as desired.
Hopefully this saves someone else some confusion in the future.

Scale in Bilevel Partition in D3

I am trying to make the Bilevel Partition in D3 work with log scale.
However, it doesn't seem to be working properly.
I have specified a log scale for angles:
var angle1 = d3.scale.log()
.base(2.0)
.domain([0, 2 * Math.PI])
.range([root.x, root.x + root.dx]);
However, this applies only when the angles are recalculated on zoom.
I have tried to modify the original partition scale, but with no success.
Any hints appreciated. See example code at the link below.
http://bl.ocks.org/mbostock/5944371
The partition layout in D3 sums up the values of the leaf nodes in order to calculate layout of the the elements with children. By definition of layout it should word like that. Therefore, only leaf nodes values are taken into account when calling layout.nodes(). Hence, only leaf nodes can be scaled (for example as log(count + 1)). However, non-leaf nodes will be represented sums of underlying values. The only option for all nodes to scale logarithmically would be to write a new layout, that would take into account a value at each node, and not sum up the values of the children.

D3.js patterns for managing multiple elements bound to same data

I'm learning D3 and my toy application is a visualizer for a 2-dimensional gravity simulation using Symplectic Velocity Verlet Integration.
I have had quite some success animating and drawing a path using bound structured data, but it seems to me that D3 isn't really designed for attaching data to more than one element. It sort of assumes that for any given data that there is a clear and simple SVG element that should own it, and this is evident in the direct storage of data within the __data__ property inside the DOM.
It's not clear, though, the proper way to represent a datum with more than one SVG element. For instance, I'd really prefer to draw a path and a circle for each planet, the path traces its past position (and can have a bunch of clever line-length and color interpolation applied), and the circle plots its current position.
I can even come up with a few more elements I might want to draw: A vector-arrow for velocity... A vector-arrow for acceleration...
In my case, my master data structure is constructed like this, and is dynamically maintained in this structure:
var data = [];
function makeParticle(x, y, vx, vy) {
// state vector plus goodies
return [
x, y,
vx, vy,
0, 0,
[] // path
];
}
data.push(makeParticle(400, 100, -0.5, 1));
data.push(makeParticle(300, -100, 0.5, 2)); // and so on
Each element of data is a planet, which contains its current state vector (position, velocity, and cached acceleration (needed for the integrator)) as well as its path history which is an array of positions which is kept truncated to some reasonably large length.
Currently I am updating the path history like this:
var paths = d3.select('svg').selectAll("path").data(data);
paths.enter().append('path'); // put cool transitions here
paths.exit().remove();
paths.attr("stroke", "red")
.attr("d", function(d){
return lineDrawer(d[6]);
})
This works fine, each path tracks its own copy of its own planet's path.
It's not immediately clear to me how to extend this elegantly to include my circle at the head of each path. I certainly do not want to duplicate the entire datum into the circle's DOM element (as the path data is simply not necessary for it).
Edit in light of my self-answer:
I am hoping that someone can help to elucidate a way to use "groups" to make data-driven the set of things to draw for each datum. e.g. [position, velocity, force, path] as data where they are visualized using, respectively, a circle, an arrow closed path, an arrow closed path, and an open path. It is also possible that this is completely overthinking it because these properties are sort of fixed.
I guess in the process of thinking the problem through, it's become clear to me that all I have to do is filter out the data, so that I only attach the position state data to selectAll('circle.planet') rather than the full datum value which includes the massive path history array. This way it gets exactly the data it is responsible for displaying.
It seems like I have to do some more reading about subselections (I'm mostly puzzled by why (or if) the subselections are limited to two dimensions of hierarchy), but this seems completely reasonable. It seems logical, if I want to draw 4 items for each datum, I just have to somehow "assign" the correct subsets of my datum's structure to each SVG visualizer element.

Categories