d3.js inner bubble chart - javascript

I am working on a bubble chart that needs to look like this - its likely to have just 2 series.
My main concern is what to do - if the bubbles are of the same size or if the situation is reversed.
I thought of using this jsfiddle as a base..
http://jsfiddle.net/NYEaX/1450/
// generate data with calculated layout values
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll('circle')
.data(nodes);
vis.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
})
.attr('class', function(d) {
return d.className;
});
vis
.transition().duration(1000)
vis.exit()
.remove();

Slightly updated your first fiddle http://jsfiddle.net/09fsu0v4/1/
So, lets go through your questions:
a bubble chart that needs to look like this - its likely to have just 2 series.
Bubble charts as part of pack layout rely on data structure - nested JSON with children array. Each node could be either root node (container) or leaf node (node that represents some end value). And its role depends on 'children' value presence. (https://github.com/d3/d3-3.x-api-reference/blob/master/Pack-Layout.md#nodes)
So, to get you chart looking like on picture just re-arrange json structure (or write children accessor function - https://github.com/d3/d3-3.x-api-reference/blob/master/Pack-Layout.md#children). Brief example is in updated fiddle.
if the bubbles are of the same size or if the situation is reversed
Parent nodes (containers) size is sum of all child nodes size. So if you have only one child node parent node will have the same size.
As far as you can't directly change parent node size - situation with oversized child nodes is impossible.

It might be easier if you start with this instead.
You could set the smaller bubble to be a child of the larger one.
As for when the series have the same size, I would either have a single bubble with both series given the same color, or I would separate the two bubbles. There isn't really much you can do when the bubbles need to be the same size.

Related

Append elements into different containers depending on conditions with D3

I have an array, looking as simple as
var labels = ['label 1', 'label2', ..., 'label n']
Now if I want to put them as visual elements in the chart, I can do like this:
var legendItem = legendArea
.selectAll('g')
.data(labels)
.enter()
.append('g')
.attr({class: 'legend-element'});
legendArea becomes a parent for all the labels now. But, I have a more complex scenario, where I need to put labels not in legendArea directly but create a wrapper g element first inside legendArea, which will then contain a set of labels, depending on some criteria that I get from each label.
As a result, I will have a number of g elements with a set of labels inside of them, one can have 5, another can have 8, any number, as they are not spread evenly.
What I see now, is I need to run a loop through all labels array elements, check if current labels conforms to criteria, create a new wrapper element if needed and then append. But this solution seems to be not D3-style, as in most cases it's possible to do functional style code with D3, not for..loop.
I suspect I can do something more custom here, something like:
var legendItem = legendArea
.selectAll('g')
.data(labels)
.enter()
// Do some unknown D3 magic here to create a new wrapper element and append the label to it.
.attr({class: 'legend-element'});
Please advise on how to do it in D3 fashion.
I would get a list of categories, use that as data for the g elements, and then add the rest with filters:
var cats = svg.selectAll("g").data(categories)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(10," + ((i+1) * 30) + ")"; });
cats.selectAll("text")
.data(function(d) { return data.filter(function(e) { return e.category == d; }); })
.enter().append("text")
.text(function(d) { return d.label; });
This works similar to nested selections, except that the nesting isn't in the data itself (although you can modify the data structures for that as well), but by referencing different data structures.
The first block of code simply adds a g element for each category. The second block of code does a nested selection (and is hence able to reference the data bound to the g element just before). In the .data() function, we take all the data and filter out the items that match the current category. The rest is bog-standard D3 again.
Complete demo here.

d3 Force: Making sense of data binding

I can recreate the following 1000 times and have enough of an understanding to do so. But I'm trying to get my head around a few specific bits that I just 'do', rather than understand:
var w = 900,
h = 500;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.attr("style", "border: 1px solid grey;")
.on("mousemove", fn)
var force = d3.layout.force()
.size([w, h])
.on("tick", tick)
.gravity(0)
.charge(0)
.start()
function fn() {
var m = d3.mouse(this);
var point = {x: m[0], y: m[1]};
d3.select("#output").text(force.nodes().length)
var node = svg
.append("circle")
.data([point])
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
.attr("r", 0.1)
.transition().ease(Math.sqrt)
.attr("r", 5)
.transition().delay(1000)
.each("end", function() {
force.nodes().shift()
})
.remove()
force.nodes().push(point)
force.start()
}
function tick() {
svg.selectAll("circle")
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
}
In particular it's the data binding part I'm not sure about.
In function fn() (on mousemove of svg space) we define a new point and we need to do two things with it; push it into force.nodes() so that the x and y coordinates of the point can be manipulated by forces configured in the force layout, and we need to use the coordinates of the point to create and manipulate the visualisation.
So we create the point first off. We then build a circle to represent this point. We push the point into force.nodes() and after a short delay, we remove both the visualisation and the point from the force.nodes() array.
The bit I don't understand is how the visualisation and the point in the array stay "connected"?
Conjecture: The data point is an object which the force layout is constantly updating the x and y properties of. There is a "link" to this object bound to the circle element. The object is therefore easily accessed and used by the circle object, but not without us controlling that process. The circle is defined as having a cx and cy at point of its creation, but we need to keep accessing the underlying data to update its cx and cy?
If that's the case, how is the object "shared" by both force.nodes() and the circle element?
Or am I miles off the mark?
Also I have read a lot of documentation on this but I feel this is something more intrinsic to javascript rather than d3 necessarily, so it's not elaborated on in any literature I've so far read.
The link between the data structures that the force layout updates and the visualization (i.e. the DOM elements) is the tick event handler function. The tick event is generated by the force layout to signify that the force simulation has progressed another step (i.e. tick) and its internal state has changed. This signals that the visualization needs to be updated.
There are two parts to making this link happen. First, the data operated on by the force layout (i.e. the links and nodes) needs to be bound to DOM elements. This is done using the usual .selectAll().data().enter().append() pattern, usually in the initialisation code, sometimes in the tick event handler function. This establishes the link between data and DOM elements.
The second part to this is the code that updates the DOM elements when the force layout changes their positions. This is what happens in the tick event handler function. If you're not adding or removing elements, there's usually no need to rebind data and often you won't see the .selectAll().data() pattern, but only the code that actually updates the positions based on the data already bound to the elements (in your case this works even though you're changing the elements because the data binding happens in the function that updates the data for the force layout as well).
As an experiment, take an arbitrary force layout example and delete the tick event handler function -- you'll see that nothing happens at all even though the force layout is running.

Updating a pie chart with a new data set with more values

I'm relatively new to D3 and have been following a few pie chart tutorials.
Namely the Mike Bostock Tutorials. http://bl.ocks.org/mbostock/1346410
But I have question about a donut chart updating from one data set to another with the 2nd data set having much more values than the first.
I have attempted this numerous times through an update function but no luck, I'll keep it simple and give a hypothetical example , lets say my first data set had 5 values
[1,2,3,4,5]
and my second data set had 10 values
[1,2,3,4,5,6,7,8,9,10]
only 5 values of the new data set would be depicted on the arcs after the dynamic update. It's like the pie is fixed with only 5 arc sections being able to display 5 values of the new dataset.
Any help would be appreciated as its been stumbling around with the idea for awhile!
The key to making it work with data of different size is to handle the .enter() and .exit() selections. This tutorial goes into more detail, but briefly the enter selection represents data for which no DOM elements exist (e.g. in the case where you pass in more data), the update selection (which you're already handling) represents data for which DOM elements exist and the exit selection DOM elements for which no data exists anymore (e.g. when you have more elements to start with).
So in your change function, you would need to do something like this.
function change() {
clearTimeout(timeout);
var path = svg.datum(data).selectAll("path")
.data(pie);
path.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { this._current = d; }); // add the new arcs
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
path.exit().remove(); // remove old arcs
}
This assumes that you're updating your data variable as you suggest above instead of getting a different value from the existing data structure as in the example.
Here I made a simple update that triggers when you click the text above the pie chart: JsFiddle
The main thing happening is all the data is updated when the .on("click") event triggers, so the chart gets updated like so:
d3.select("#update")
.on("click", function (d) {
data = [1,2,3,4,5,6,7,8,9,10];
vis.data([data]);
arc = d3.svg.arc().outerRadius(r);
pie = d3.layout.pie().value(function(d){return d; });
arcs.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice")
.append("svg:path")
.attr("fill", function(d, i){return color(i);}).attr("d", arc);
});

how to draw an array of circles in a straight line, in the bottom of the browser window

I'm trying to draw a straight line of circles using d3 at the bottom of the browser window. I am not sure how this would be accomplished. I know I could create a bunch of circles using the SVG tag, but there's probably a better way using a for loop with an array.
I would like the circles to appear in a straight line at the bottom of the browser window. I would also like the circles to fill the width of the browser window as well. Any help would be greatly appreciated.
d3 has a functional style with the concept of selectors. If you are thinking of using a loop then you are probably using the tool wrong. The functional style allows you to instead concentrate on what you want to do with each item of data instead of how to process the data. There are also a number of helper functions.
Lets take Adam's solution
d3.select('body')
We are using CSS style selectors to select one object from the DOM. In this case it is the body of the document. We can do a number of things with this selection but first we append using
append('svg')
in
d3.select('body').append('svg')
This could be written to differently if we needed to reuse these selections
var body = d3.select('body');
var svg = body.append('svg');
We can the define the attributes of the object just defined
.attr('width', width)
.attr('height', height)
Now comes the interesting bit. D3 operates by binding data to selections so add data we first need a (probably) empty selection.
.selectAll('circle')
Note the use of selectAll not select.
Adam creates an array of data with
d3.range(0, width, width/10)
This uses one of d3's helper functions which behaves like the range function found in many languages with functional support (examples of use in F# & Python)
> d3.range(5)
[0, 1, 2, 3, 4]
> d3.range(0,5)
[0, 1, 2, 3, 4]
> d3.range(4,5)
[4]
// At intervals of 2
>d3.range(0,5,2)
[0, 2, 4]
Anyway we have a list of number which gets bound using
.data()
Which returns a selection. We the define what happens within the life cycle events of this selection. Because we are only dealing with data entering we can just go
.enter()
Anything under this selection will be applied to any datum entering (which in this case will be all the elements from the list). You should be able to understand what is happening until
.attr('cx', function(d){ return d; })
What is happening hear is the attribute cx is dependent on the data from the list we supplied earlier. We can provide a function which will be executed which gets passed the datum and the index of the current item.
Using some more of the helpers D3 brings
Typically you will need to use the scale helper when using D3. This allows us to abstract out the concept of pixels and instead concentrate on a fixed range.
Slightly changing the example given by Adam. Lets say we want to show 5 evenly spaced circles at the end of the document.
We can define the data like
var data = d3.range(0, 5);
And set up a scale like
var x = d3.scale.linear()
.domain([0, data.length])
.range([0,width])
With the domain (that is the input) been 0 to the max number of our data.
.domain([0, d3.max(data)])
and the range (that is what we want to output) as been 0 up to the maximum number of pixals
.range([0,width])
The example code would then look like
var width = window.innerWidth;
var height = 100;
var data = d3.range(0, 5);
var x = d3.scale.linear()
.domain([0, data.length-1])
.range([0,width])
d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.selectAll('circle')
.data(data ).enter()
.append('circle')
.style('fill', 'red')
.attr('r', height/4)
.attr('cy', height/2)
.attr('cx', function(d){ return x(d);})
We can even change that last line to
.attr('cx', function(d, i){ return x(i);})
Whilst in this example the index and the data is the same this allows us to to space out the items whilst keeping the data simple. Say if the array actually was the values which r should be
...
var data = [4,1,20,5,7];
...
.attr('r', function(d){ return d;})
...
.attr('cx', function(d, i){ return x(i+0.5);})
var width = window.innerWidth;
var height = 100;
d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.selectAll('circle')
.data(d3.range(0, width, width/10)).enter()
.append('circle')
.style('fill', 'red')
.attr('r', height/4)
.attr('cy', height/2)
.attr('cx', function(d){ return d; })

Transforming hierarchical JSON to HTML using D3

I am trying to visualize hierarchical JSON using D3 using recursive function. Here is JSFiddle
– http://jsfiddle.net/nBVcs/ and here is relevant part of the code:
function addNode(selection) {
var nodeGroup = selection.selectAll('.child')
.data(function(d) { return d.children })
nodeGroup
.enter()
.append('div')
.attr("class", "child")
nodeGroup
.exit()
.remove()
nodeGroup
.style("padding-left", "2em")
nodeGroup.append("text")
.text(function(d) { return d.name });
nodeGroup.each(function(d) {
if (d.children) {
nodeGroup.call(addNode)
};
});
}
So far, this approach has several problems. First is that leaf nodes are rendered twice.
Another issue with this approach is that adding deeper leaf nodes will lead to error because D3 will try to bind non-existing array (d.children) to the selection.
I would be glad if you could point me to the right direction.
Your recursive call was incorrect, this will work:
nodeGroup.each(function(d) {
if (d.children) {
d3.select(this).call(addNode);
};
});
I also updated your fiddle.
I made another small modification: The <text> node should be appended to the .enter() group, otherwise it will be duplicated when you update the tree.
(this is only relevant if you plan on using this code not only for creating the tree, but also for updating it)
See general update pattern for more information.

Categories

Resources