Transforming hierarchical JSON to HTML using D3 - javascript

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.

Related

d3.js inner bubble chart

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.

Basic d3: why can you select things that don't exist yet?

I've been learning about d3, and I'm a bit confused about selecting. Consider the following example:
http://bl.ocks.org/mbostock/1021841
Specifically, let's look at this line:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8)
.style("fill", function(d, i) { return fill(i & 3); })
.style("stroke", function(d, i) { return d3.rgb(fill(i & 3)).darker(2); })
.call(force.drag)
.on("mousedown", function() { d3.event.stopPropagation(); });
In the documentation it says, "A selection is an array of elements pulled from the current document." I interpret this to mean that svg.selectAll(.node) creates an array of elements of class .node pulled from the current document, but as far as I can tell there are no such elements! Unless I'm confused - and I'm almost certain that I am - the only place in the document where something is given the class "node" is after the selection has already occurred (when we write .attr("class", "node")).
So what is going on here? What does svg.selectAll(".node") actually select?
Although, at first sight, this may look like a simple and silly question, the answer to it is probably the most important one for everyone trying to do some serious work with D3.js. Always keep in mind, that D3.js is all about binding data to some DOM structure and providing the means of keeping your data and the document in sync.
Your statement does exactly that:
Select all elements having class node. This may very well return an empty selection, as it is in your case, but it will still be a d3.selection.
Bind data to this selection. Based on the above mentioned selection this will, on a per-element basis, compute a join checking if the new data is a) not yet bound to this selection, b) has been bound before, or c) was bound before but is not included in the new data any more. Depending on the result of this check the selection will be divided into an enter, an update, or an exit selection, respectively.
Because your selection was empty in the first place. All data will end up in the enter selection which is retrieved by calling selection.enter().
You are now able to append your new elements corresponding to the newly bound data by calling selection.append() on the enter selection.
Have a look at the excellent article Thinking with Joins by Mike Bostock for a more in-depth explanation of what is going on.

D3 Select <g> Element and Modify Fill

I have used D3 to make a map of the US and filled in the colors
var map = d3.select("#map").append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
d3.json("us.json", function (error, us) {
map.append("g")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style("stroke", function (d) { return "#000000"; } )
.style("fill", function (d) { return gradient(Math.random()); } )
};
Now, I want to change the color of each state but rather than removing the map and then re-adding it, I would like to transition the colors.
I have tried:
d3.selectAll("#map").selectAll("g").selectAll("path")
But then trying to loop through the elements of this array does not help.
What am I doing wrong?
EDIT:
The code I am using to try and change the colors of each state (each path variable) is...
d3.select(this).transition().style("fill", gradient(Math.random()));
I do not believe the problem has to do with the code above - it's the code I am trying to use to select the paths/states that is giving me trouble.
I have also tried
d3.selectAll("path").attr("fill", function (d) { ... });
But, that too, did not do anything. :(
As Lars said in the comments, since I used "style" before, I have to do it again [instead of using attr as I was before].
Selecting the data as I did (d3.selectAll("path")) is the correct way to select the states.

Map does not render completely in d3.js

I am creating a globe on which I plot a number of locations. Each location is identified with a small circle and provides information via a tooltip when the cursor hovers over the circle.
My problem is the global map renders incompletely most of the time. That is various countries do not show up and the behavior of the code changes completely at this point. I say most of the time because about every 5th time i refresh the browser it does render completely. I feel like I either have a hole in my code or the JSON file has a syntax problem that confuses the browser.
btw: I have the same problem is FF, Safari, and Chrome. I am using v3 of D3.js
Here is the rendering code:
d3.json("d/world-countries.json", function (error, collection) {
map.selectAll("path")
.data(collection.features)
.enter()
.append("svg:path")
.attr("class", "country")
.attr("d", path)
.append("svg:title")
.text( function(d) {
return d.properties.name; });
});
track = "countries";
d3.json("d/quakes.json", function (error, collection) {
map.selectAll("quakes")
.data(collection.features)
.enter()
.append("svg:path")
.attr("r", function (d) {
return impactSize(d.properties.mag);
})
.attr("cx", function (d) {
return projection(d.geometry.coordinates)[0];
})
.attr("cy", function (d) {
return projection(d.geometry.coordinates)[1];
})
.attr("class", "quake")
.on("mouseover", nodehi)
.on("mouseout", nodelo)
.attr("d", path)
.append("svg:title")
.text( function(d) {
var tip = d.properties.description + " long "+ (d.geometry.coordinates)[0] + " lat " + (d.geometry.coordinates)[1];
return tip
});
});
Any thoughts would be appreciated...
The reason you're seeing this behaviour is that you're doing two asynchronous calls (to d3.json) that are not independent of each other because of the way you're selecting elements and binding data to them. By the nature of asynchronous calls, you can't tell which one will finish first, and depending on which one does, you see either the correct or incorrect behaviour.
In both handler functions, you're appending path elements. In the first one (for the world file), you're also selecting path elements to bind data to them. If the other call finished first, there will be path elements on the page. These will be matched to the data that you pass to .data(), and hence the .enter() selection won't contain all the elements you're expecting. This is not a problem if the calls finish the other way because you're selecting quake elements in the other handler.
There are several ways to fix this. You could either assign identifying classes to all your paths (which you're doing already) and change the selectors accordingly -- in the first handler, do .selectAll("path.country") and in the second .selectAll("path.quake").
Alternatively, you could nest the two calls to d3.json such that the second one is only made once the first one is finished. I would do both of those to be sure when elements are drawn, but for performance reasons you may still want to make the two calls at the same time.

d3: confusion about selectAll() when creating new elements

I am new to d3 and am using 'Interactive Data Visualization for the Web' by Scott Murray (which is great btw) to get me started. Now everything I saw so far works as described but something got me confused when looking at the procedure to create a new element. Simple example (from Scott Murray):
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");
The name "circle" is used for the selectAll which returns an empty selection (which is ok as I learned). Then circles are appended by putting the same name into the .append. Great!
Now what got me confused was what happens when you want to do the same thing again. So you have a second dataset and want to generate new circles in the same way. Using the same code just replacing the dataset will obviously not work as the selectAll("circle") will not return an empty selection anymore. So I played around and found out that I can use any name in the selectAll and even leave it empty like this: selectAll()
Scott Murrays examples always just use one type (circle, text, etc.) per dataset. Finally I found in the official examples something like
svg.selectAll("line.left")
.data(dataset)
.enter()
.append("line")
.attr ...
svg.selectAll("line.right")
.data(dataset)
.enter()
.append("line");
.attr ...
Now my question: How is this entry in selectAll("ENTRY") really used? Can it be utilized later to again reference those elements in any way or is it really just a dummy name which can be chosen in any way and just needs to return an empty selection? I could not find this entry anywhere in the resulting DOM or object structure anymore.
Thank you for de-confusing me.
What you put in the selectAll() call before the call to .data() really only matters if you're changing/updating what's displayed. Imagine that you have a number of circles already and you want to change their positions. The coordinates are determined by the data, so initially you would do something like
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d; })
.attr("cy", function(d) { return d; });
Now your new data has the same number of elements, but different coordinates. To update the circle positions, all you need to do is
svg.selectAll("circle")
.data(newData)
.attr("cx", function(d) { return d; })
.attr("cy", function(d) { return d; });
What happens is that D3 matches the elements in newData to the existing circles (what you selected in selectAll). This way you don't need to append the circles again (they are there already after all), but only update their coordinates.
Note that in the first call, you didn't technically need to select circles. It is good practice to do so however just to make clear what you're trying to do and to avoid issues with accidentally selecting other elements.
You can find more on this update pattern here for example.

Categories

Resources