D3 Bubble Chart 'bubble.nodes not a function' - javascript

I'm following Mike Bostock's tutorial here to create a bubble chart... except that I'm using my own dataset and I'm using d3 v4. I'm quite new to d3 and I understand a lot has changed in v4 from v3. I'm having trouble converting the sample code to v4.
For instance, I've converted this code in d3 v3:
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
to:
var bubble = d3.pack(dataset)
.size([diameter, diameter])
.padding(1.5);
Is the above correct? I'm not sure since I'm not having any errors till this point.
But I get an error in the following piece of code:
var node = svg.selectAll(".node")
.data(
bubble.nodes(root)
.filter(function(d) {
return !d.children;
})
)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
with a bubble.nodes is not a function. What is the equivalent in d3 v4?
Fiddle: https://jsfiddle.net/r24e8xd7

Here is your updated fiddle: https://jsfiddle.net/r24e8xd7/9/.
Root node should be constructed using d3.hierarchy:
var nodes = d3.hierarchy(dataset)
.sum(function(d) { return d.responseCount; });
Then pack layout can be called:
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())

Comparing the different docs, v3
# pack(root)
# pack.nodes(root)
Runs the pack layout, returning the array of nodes associated with the
specified root node. The cluster layout is part of D3's family of
hierarchical layouts. These layouts follow the same basic structure:
the input argument to the layout is the root node of the hierarchy,
and the output return value is an array representing the computed
positions of all nodes. Several attributes are populated on each node:
parent - the parent node, or null for the root.
children the array of child nodes, or null for leaf nodes.
value - the node value, as returned by the value accessor.
depth - the depth of the node, starting at 0 for the root.
x - the computed x-coordinate of the node position.
y - the computed y-coordinate of the node position.
r - the computed node radius.
to the newer v4
# pack(root) <>
Lays out the specified root hierarchy, assigning the following
properties on root and its descendants:
node.x - the x-coordinate of the circle’s center
node.y - the y-coordinate of the circle’s center
node.r - the radius of the circle
You must call root.sum before passing the hierarchy to the pack
layout. You probably also want to call root.sort to order the
hierarchy before computing the layout.
it looks like pack() is what you are looking for, but it looks like you might need a change or two before you do.
update
Quick look into different things and there are a few things going on that its not just a simple fix.
your data is entirely different to the example and is flat, which
effects the diagram.
why not use v3? Most of the examples out there are in v3 and like you
said you are new to d3. why make things difficult.
Finally start small. I would suggest trying to find a small bubble
chart first and then make your way up, or substitute your data into
the example code and get that working and then incrementally change
it instead of trying to change multiple things at once.

Related

Side-by-side paths in d3

I'm trying out a way to get paths to display next to each other, such that they'll push each other around (factoring in widths and neighbouring points) and not overlap.
This is my fiddle, mostly pieced together from examples
https://jsfiddle.net/crimsonbinome22/k2xqn24x/
var LineGroup = svg.append("g")
.attr("class","line");
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) { return (d.x); })
.y(function(d) { return (d.y); })
;
LineGroup.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
.attr("d", function(d){ return line(d.p); })
.attr("stroke", function(d){ return d.c; })
.attr("stroke-width", function(d){ return d.w; })
.attr("fill", "none");
And this is what I'm hoping to achieve in this image here, basically:
For all lines landing on the same point, push them left or right of that point so together they center around it.
Factor in line width so they don't overlap, or leave whitespace between.
Be able to handle paths with different numbers of points (max in example is 3 but I want to deal with up to 10)
Note though points that overlap will always have the same index (they won't loop around, but just go outwards like a tree)
Be able to handle different numbers of lines landing on the same point.
Some issues I'm having:
I'm new to d3 and I find functions a bit baffling. Not sure how to even start to apply logic that will move the lines around.
My data structure has some redundant info in it, such as r for the rank (to decide whether to push left or right) and w for the width both of which will always be the same for a particular line.
I have a lot of data so the data structure used here won't work with the csv data I have. Can maybe skip this one for now and I'll open up a new question for that one later.
I've had a search around but can't find any examples of how to do this. In a way it's almost like a chord diagram but a little different, and I can't find much relevant code to reuse. Any help on how to achieve this (either with the approach I've started, or something totally different if I've missed it) would be appreciated.
I would go with the following steps:
compute an array of node objects, i.e. one object for each point visited by a line
compute the tree on this node (that is, for every node, add links to its parent and children)
make sure that children of any node are ordered according to the angle they make with this node
at this point, each line now only depends on its final node
for each node compute an ordered list of lines going through
visit all nodes bottom-up (i.e. starting from the leaves)
the "go-through" list is the concatenation of the lists of the children + all lines that end at the current node
for each node, compute an array of offsets (by summing the successive width
of the lines going through)
finally, for every line and every node in the line, check the array of offsets to know how much the line must be shifted
Edit: running example
https://jsfiddle.net/toh7d9tq/1/
I have used a slightly different approach for the last two steps (computing the offset): I actually create a new p array for each series with a list of pairs {node, offset}. This way it is much easier to access all relevant data in the drawing function.
I needed to add an artificial root to have a nice starting line (and to make it easier for recursion and angles and everything), you can skip it in the drawing phase if you want.
function key(p) {
return p.time+"_"+p.value
}
// a node has fields:
// - time/value (coordinates)
// - series (set of series going through)
// - parent/children (tree structure)
// - direction: angle of the arc coming from the parent
//artificial root
var root={time:200, value:height, series:[], direction:-Math.PI/2};
//set of nodes
var nodes = d3.map([root], key);
//create nodes, link each series to the corresponding leaf
series.forEach(function(s){
s.pWithOffset=[]; //this will be filled later on
var parent=root;
s.p.forEach(function(d) {
var n=nodes.get(key(d));
if (!n) {
//create node at given coordinates if does not exist
n={time:d.time,
value:d.value,
parent:parent,
series:[],
direction:Math.atan2(d.value-parent.value, d.time-parent.time)};
nodes.set(key(n),n);
//add node to the parent's children
if (!parent.children) parent.children=[];
parent.children.push(n);
}
//this node is the parent of the next one
parent=n;
})
//last node is the leaf of this series
s.leafNode=parent;
parent.series.push(s);
})
//sort children by direction
nodes.values().forEach(function(n){
if (n.children)
n.children.sort(function (a,b){
if (a.direction>n.direction)
return a.direction-b.direction;
});
});
//recursively list all series through each node (bottom-up)
function listSeries(n) {
if (!n.children) return;
n.children.forEach(listSeries);
n.series=d3.merge(n.children.map(function(c){return c.series}));
}
listSeries(root);
//compute offsets for each series in each node, and add them as a list to the corresponding series
//in a first time, this is not centered
function listOffsets(n) {
var offset=0;
n.series.forEach(function(s){
s.pWithOffset.push( {node:n, offset:offset+s.w/2})
offset+=s.w;
})
n.totalOffset=offset;
if (n.children)
n.children.forEach(listOffsets);
}
listOffsets(root);
And then in the drawing section:
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) { return (d.node.time-Math.sin(d.node.direction)*(d.offset-d.node.totalOffset/2)); })
.y(function(d) { return (d.node.value+Math.cos(d.node.direction)*(d.offset-d.node.totalOffset/2)); })
;
LineGroup.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
.attr("d", function(d){ return line(d.pWithOffset); })
.attr("stroke", function(d){ return d.c; })
.attr("stroke-width", function(d){ return d.w; })
.attr("fill", "none");

D3 update circle-pack data new nodes overlap existing nodes

I'm following the General Update Pattern but having an issue with regards to layering.
Using a circle-pack layout, I pack the new data, update, enter and exit the circle elements. However, when new elements enter, they overlap the updated circles.
Data key function is based on element name:
.data(nodes, function(d, i) { return d.name; });
So my circle pack has a spot for the updated circle (of the correct location and size) but it's hidden behind its newly entered parent circle.
Is there a way to send these updated nodes to the front or redraw them over the entered circles?
--UPDATE--
As suggested by the person who closed this issue, I've tried implementing the linked to solution using moveToFront.
I added the following code in my update section (which didn't change anything) and then tried adding it after the enter and exit code, which also didn't make any difference.
.each("end", function(d){ d3.select(this).moveToFront(); });
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
For clarity, this is what the selection and update looks like:
// Load data into svg, join new data with old elements, if any.
var nodes = pack.nodes(postData);
node = root = postData;
groupNodes = svg.selectAll("g")
.data(nodes, function(d, i) { return d.name; });
// Update and transition existing elements
groupNodes.select("circle")
.transition()
.duration(duration)
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.attr('r', function(d) { return d.r; })
.each("end", function(d){ d3.select(this).moveToFront(); });
This moveToFront code does not make a difference to my output, and the updated circles remain behind the entered selection circles.
To summarize: the issue seems to be caused by a hierarchy layout (circle-packing) which expects the circles to be drawn in the order of the data's hierarchy. The d3 update pattern (using enter, update and exit selections) causes selected update elements to remain in the svg when the hierarchy is re-drawn, and the new layers are drawn over it. The parents of those nodes are already correctly set, so parentNode.appendChild doesn't do anything in this case, because it's not the cause of the issue.
Here is a fiddle to demonstrate my issue. I've tried putting the moveToFront code in various places, with no visible difference.
When you hit the "Change Data" button, it'll redraw the circles, but any circles whose names overlap between the two data sets are not nested properly in the circle-pack. Children of "Group A" are hidden behind one of the parent circles. You can verify the nodes are there via Inspect Element.
Another pic from the updated fiddle:
D3 provides a way to reorder elements based on the data bound to them with the .sort() function. In your case, the condition to check is the .depth attribute of the elements -- "deeper" elements should appear in front:
svg.selectAll("g")
.sort(function (a, b) {
if (a.depth < b.depth) return -1;
else return 1;
});
Complete demo here.

D3 sankey diagram - enforce node position

Using the D3 Sankey plugin, I'm updating a Sankey diagram with new values (on changing the data, passing new values for the nodes and links -- keeping all of them consistent). Is there functionality like d3.treemap's sticky to maintain node and link orders on the page? If not, is there an approach to building this?
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
.sticky(true)
I'm following the pattern here: http://bost.ocks.org/mike/sankey/
No, there isn't. If you want to dive into the layout, here's where you want to look:
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
initializeNodeDepth();
...
Notice that sortKeys points at d3.ascending. You'd want this to point to some kind of hard-wired value, which you'd need to compute either in the first iteration or in your data preparation. It will still get adjusted when the collision detection function is run, so you might see your nodes pushed out of position but this will give you the best chance to maintain some control.

Jittering geo paths using D3.js

I'm trying to add 'jitter' or add random noise to a D3.js map that contains line features. Note, this is slightly different from this other example because it involves geo paths. Additionally, while I'd like to use a custom transformation to do this, I don't think I can because I need to be able to use a standard transformation (from WGS84 to NY State Plane). I think the jittering function should either be based on a modified path function, or be a separate function which takes a path as input.
var projection = d3.geo.conicConformal()
.parallels([40 + 40 / 60, 41 + 2 / 60])
.rotate([74, -40 - 10 / 60]);
var path = d3.geo.path()
.projection(projection);
Note that I don't really want to modify the input data at all (i.e., the jittering should be on the paths, not the input geodata). Note also that the jittering can be totally random (i.e., it does not have to be the same every time). My initial thought is to wrap the data in a jitter function, or to wrap the path function in a jitter function. Either way, I'm not really sure where to start on this? Any suggestions? Even a link to the relevant API item would be awesome!
svg.selectAll("path")
.data(jitter(lines.features)) // Wrap data in jitter function... or...
.enter().append("path")
.attr("class", "line")
.attr("d", function(d) { return jitter(path(d)); }) // Jitter path directly
A (simplified) jsfiddle is available here for reference.

d3 - sunburst - transition given updated data -- trying to animate, not snap

I am working on a sunburst viz based off of Mike Bostock's Zoomable Sunburst example.
I want to be able to change the underlying data using a whole new JSON (which has the same structure but different 'size' values), and have the sunburst animate a transition to reflect the updated data.
If I change the data of the path elements using .data(), and then attempt to update in the following fashion:
path.data(partition.nodes(transformed_json))
.transition()
.duration(750)
.attrTween("d", arcTween(transformed_json));
(..which is pretty much the exact same code as the click fn)
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
}
..I find that the sunburst does correctly change to reflect the new data, but it snaps into place rather than smoothly transitioning, like it does when you zoom in.
http://jsfiddle.net/jTV2y/ <-- Here is a jsfiddle with the issue isolated (the transition happens one second after you click 'Run')
I'm guessing that I need to create a different arcTween() fn, but my d3 understanding is not there yet. Many thanks!
Your example is quite similar to the sunburst partition example, which also updates data with a transition. The difference is that in this example it's the same underlying data with different value accessors. This means that you can't save the previous value in the data (as that will be different), but need to put it somewhere else (e.g. the DOM element).
The updated tween function looks like this:
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
return function(t) {
var b = i(t);
this.x0 = b.x;
this.dx0 = b.dx;
return arc(b);
};
}
This requires, as in the original example, to save the original x and dx values:
.enter().append("path")
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
Complete example here. This one has a kind of weird transition which is cause by the different order of the data in the layout. You can disable that by calling .sort(null), see here.

Categories