D3 add zoom to circle pack - d3.js

Is it possible to add zooming to a circle pack? Seems like it should be but mine is jumping all around the place when zoom is clicked. I've been attempting to solve this for a few days but with little success.
I've been referencing Mike's Zoomable Circle Packing block (#7607535) and nilanjenator's block (#4950148). Other examples seem to be based on these two. Here's a fiddle of my work in progress: http://jsfiddle.net/cajmcmahon/9weovdm2/5/.
From what I can make out, my layout problems lie in these two functions:
t.selectAll("circle")
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.attr("r", function(d) {
return k * d.r;
});
t.selectAll("text")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y);
})
.style("opacity", function(d) { return k * d.r > 20 ? 1 : 0; });
Also, I can't get the viz to reset (zoom out?) when I click on the background. I believe it's not getting values for 'data'...
//Reset when click on background
d3.select(window).on("click", function(d, i) {
zoom(data)
});
Thanks for any help.

Ok both examples:
Have same output but they are implemented differently.
http://bl.ocks.org/nilanjenator/4950148: This one relies on changing the cx and cy of the circle for moving and updating the radius for zoom effect.
http://bl.ocks.org/mbostock/7607535: This one relies on translate to move the circles.
So in your example: you mixed both of it and thus you got a different flavor of circle packing.
You created circles and moved it into their position using translate but in the zoom section you made use of changing the cx and cy, as a result your circles flew out of the pack on zooming.
I have removed the translate and gave the cx and cy so the zoom function remain the same as what you have written.
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.on("click", function(d) {
zoom(node == d ? root : d);
});
I have changed the fiddle you gave here is a working example:
http://jsfiddle.net/cyril123/khq21pgb/2/
Hope this is helpful!

Related

D3.js : Duplicate 100% working interactive legend for a bubble map

I have created a interactive legend, that works 100% perfectly fine. I just didnt manage to duplicate it for another column of my dataframe.
It starts with bubbles I'm plotting with the code below. Then I draw the actual legend.
1. creating bubbles
selection
.selectAll('circle')
.data(cities)
//.attr('r', 14)
.attr('r', function(d) {
return Math.max(Math.pow(d.population, 0.57) / 40, 7);})
.attr('cx', function(d) { return projection.latLngToLayerPoint([d.latitude, d.longitude]).x;
})
.attr('cy', function(d) { return projection.latLngToLayerPoint([d.latitude, d.longitude]).y;
})
.attr("class", function(d) { return "bubbles " + d[attribute] }) //Important feature for legend
//.attr("class", function(d) { return "bubbles " + d.category })
.attr('stroke', 'white')
.attr('stroke-width', function(d) {
return 1.2 / projection.scale;})
.style("fill", function(d) {
return color(d[attribute])})
.attr("stroke", "#FFFF")
.attr("stroke-width", 1)
.attr("fill-opacity", .9)
.on("mouseover", showTooltip)
.on("mousemove", moveTooltip)
.on("mouseleave", hideTooltip)
2. Drawing actual legend
svg_chorop.selectAll("mydots")
.data(allgroups)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d, i) {
return 60 + i * (size + 10)
}) // 60 is where the first dot appears. 10 is the distance between dots
.attr("width", size)
.attr("height", size)
.style("fill", function(d) {
return mycolor(d)
})
.style("stroke", "#DCDCDC")
.on("mouseover", highlight)
.on("mouseleave", noHighlight)
My goal : make the exact same legend with another hovering option.
Problem : I can't find a way to ADD another class attribute (in my case d.category, for economic sector) in the first bunch of code.
Fiddle link, so it's easier for you to see the code : jsfiddle
I have simply tried to add another .attr("class",..). It doesn't work. And I can find a similar fix for this problem online.
.attr("class", function(d) { return "bubbles " + d[attribute] }) //Important feature for legend
//.attr("class", function(d) { return "bubbles " + d.category })
Here you can see the two legends, left one functionnal, right, the
one to be implemented
Here you can see the dataframe and the category column

.data() binding only first element

I am building an epidemic simulation using D3's force-directed diagram.
When a transmission event occurs, I want to move a circle from the transmitter to the newly infected individual.
PROBLEM: Only the first element is created and moved according to the bound data.
First, I gather the coordinates:
xyCoords = getPathogen_xyCoords(newInfections);
Where xyCoords looks like the following:
{receiverX: newInfections[i].x, receiverY: newInfections[i].y, transmitterX: newInfections[i].infectedBy.x, transmitterY: newInfections[i].infectedBy.y}
Then I create the circles and bind them to xyCoords:
d3.select(".svg").append("circle")
.attr("class", "pathogen")
d3.selectAll(".pathogen")
.data(xyCoords)
.attr("cx", function(d) { return d.transmitterX})
.attr("cy", function(d) { return d.transmitterY})
.attr("r", 4)
.style("fill", "green")
Finally, the circle is moved with a transition:
d3.selectAll(".pathogen")
.transition()
.duration(500)
.attr("cx", function(d) { return d.receiverX} )
.attr("cy", function(d) { return d.receiverY} );
EDIT: The game has been up for a few months now and doing quite well! Check it out at http://vax.herokuapp.com!
I've solved my problem...
At creation of the circles, I was not "entering" new circles to be associated with the newly bound data.
It was displaying only the first element on bind because there was only one circle to begin with.
Creation of the circles now looks like this:
xyCoords = getPathogen_xyCoords(newInfections);
var pathogen = svg.selectAll(".pathogen")
.data(xyCoords)
.enter()
.append("circle")
.attr("class", "pathogen")
.attr("cx", function(d) { return d.transmitterX })
.attr("cy", function(d) { return d.transmitterY })
.attr("r", 4)
.style("fill", "green")

Labels on bilevel D3 partition / sunburst layout

I am trying to add labels to the bilevel sunburst / partition shown here - http://bl.ocks.org/mbostock/5944371:
I have added labels to the first 2 levels and rotated them correctly. But I cant now add the new level's labels during a transition. To get started I have put ...
text = text.data(partition.nodes(root).slice(1), function(d) { return d.key; });
straight after the ...
path = path.data(partition.nodes(root).slice(1), function(d) { return d.key; });
line but it throws the following error ...
Uncaught TypeError: Cannot read property '__data__' of undefined
What am I doing wrong?
I used bilevel partition to add labels via a mouseover, and found that in this version (unlike the other sunburst partition), there are two different sections where "path" is defined and updated. The first is here:
var path = svg.selectAll("path")
and then again below the transition you highlighted in your code:
path.enter().append("path")
When i added my mouseover labels, I needed to reference my text function in both places or it wouldn't work after transition.
If you can post your code to JSFiddle or bl.ocks.org we can try to play with it and see, but that was where I got tripped up at first.
Hope that helps.
*NOTE: Comment didn't post:
I'm sorry I'm not able to help more, but I'm also a newbie at D3. Here's where I got:
copy and paste your svg.selectAll("text") snippet after the second "path.enter().append("path") snippet. This will cause it to appear on subsequent zooms as well.
Problems I see: there's no "g" element so you need separate transitions for text as well or they all pile up. Also, I can't understand why they're positioned at their original partition spot instead of where the arc exists now.
My approach was add labels on mouseOver. I've uploaded that here: https://github.com/johnnymcnugget/d3-bilevelLabels
In this, I'm calling the two functions in both sets of "path" snippets, and the transitions are handled within the single variable of "legend"
Another D3 newbie, but I managed to resolve this. Beggining from the original Bilevel Partition:
Add the text for the first time the chart is drawn:
var path = svg.selectAll("path")
.data(partition.nodes(root).slice(1))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return d.fill; })
.each(function(d) { this._current = updateArc(d); })
.on("click", zoomIn);
var text = svg.selectAll("text")
.data(partition.nodes(root).slice(1))
.enter().append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.html(function(d) { return d.name; });
when zooming in or out you have to redraw labels, add the following in function zoom(root, p)
text.enter().append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
text.exit().transition()
.style("fill-opacity", function(d) { return d.depth === 1 + (root === p) ? 1 : 0; })
.remove();
text.transition()
.style("fill-opacity", 1)
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
Finally modify the computeTextRotation function as:
function computeTextRotation(d) {
return (d.x + (d.dx)/2) * 180 / Math.PI - 90;
}
Hope I haven't missed anything.
Indeed one modified line is missing in "2.". In function zoom(root, p), you should also add a line after path = path.data(partition ... .key; });:
path = path.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});
text = text.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});

D3.js: How to make force directed graph faster

I've been trying to get my force directed graph to go faster/smoother, and it seems commenting this part out of the "tick" function does the trick. Of course, it also makes all the edges disappear although the nodes still move together as if attached by invisible threads.
I have about 2-3 hundred nodes in a network-ish graph and when setting the opacity of each element I also check it's weight and if it's 1 I remove it.I repeat this for all nodes and text labels. (and edges using d.target.weight)
Is it just a number of nodes that weighing down everything? After removing the elements down to 20 or so why is it still so slow? Do I really have to piggyback my removal onto .style("opacity", function(d){//do stuff, return 1;})?
force.on("tick", function() {
// edges.attr("x1", function(d) { return d.source.x; })
// .attr("y1", function(d) { return d.source.y; })
// .attr("x2", function(d) { return d.target.x; })
// .attr("y2", function(d) { return d.target.y; })
// .style("stroke", function(d){
// return d.source.color;
// });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
functions for drawing the svg elements if it helps:
//Create edges as lines
var edges = group.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", function(d,i){
if (d.target.weight === 1)
d3.select(this).remove();
return "#FFFFFF";
})
.style("opacity",0.5)
.style("stroke-width", 2);
//Create nodes as circles
var nodes = group.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.style("opacity",0.8)
.attr("r", function(d,i){
if (d.weight === 1)
d3.select(this).remove();
return nodeScale(d.weight * 2);
})
.style("fill", function(d, i) {
return d.color;
})
.call(force.drag);
var text = group.selectAll("text")
.data(dataset.nodes)
.enter()
.append("text")
.attr("fill","black")
.style("font-size",function(d){
return d.size;
})
.style("text-anchor", "middle")
.text(function(d){
return d.name;
})
.style("opacity",function(d){
if (d.weight === 1)
d3.select(this).remove();
else
return 0.8;
})
.on("mouseover",function(){
d3.select(this)
.style("opacity",1)
.style("font-size", 25);
})
.on("mouseout",function(){
d3.select(this)
.style("font-size", function(d) { return d.size; });
})
.call(force.drag);
Also the initiation function, became quite random after I fiddle around with it a lot:
(I also have a slider for each one that I play with when rendered)
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.charge([-2000])
.friction(0.5)
.gravity(0.5)
.theta(0.5)
.start();
I believe the problem was that removing an element still doesn't affect the actual dataset being used which is composed of hundred of nodes in this case.
Changing the code to removing the element from the dataset and using exit().remove() makes it go faster.

circle radius not updating on transition in d3

I am having trouble with a transtion in d3. This jsfiddle illustrates the problem: http://jsfiddle.net/3bzsE/
When the page loads, dataset01 is used to create a circle for each person in the array. d.name is used as the key.
The blue rectangles below the chart are buttons that update the data on click.
Here is the update funciton:
function updateVis(data) {
var points = svg.selectAll('.nameinfo')
.data(data, function(d) { return d.name;});
var pointsEnter = points
.enter()
.append('g')
.attr('class', 'nameinfo');
pointsEnter
.append('circle')
.attr('cx', function(d) { return 10 + d.position * 100; })
.attr('cy', width/2)
.attr('r', 0)
.style('fill', function(d) { return z(d.position); })
.style('fill-opacity', 0.5)
.style('stroke', '#232323')
.append("title")
.text(function(d) { return d.name; });
pointsEnter
.append('text')
.attr('x', function(d) { return 10 + d.position * 100; })
.attr('y', width/2)
.attr("dy", "0.35em")
.style("text-anchor", "middle")
.style("font-size", "11px")
.text(function(d, i) { return d.name; });
pointsUpdate = points
.selectAll('circle')
.transition().duration(500)
.attr('r', function(d){ return d.value/2;});
var pointsExit = points.exit().transition().duration(500).remove();
pointsExit
.selectAll('circle')
.attr('r', 0);
}
The enter and exits are behaving as expected, but circle radius is not changing for names that are present in the entering and exiting datasets.
An example using the values for Jim:
clicking on button three with button one active:
Joe, Janet and Julie exit
Jane and John enter
But, the radius of Jim does not change (it should shrink because d.value changes from 130 to 50)
Clicking on two with three active causes Jim to exit. Clicking on three from two causes Jim to enter with the proper radius from dataset03.
The same behavior can be see with the other names. In all cases enters and exits work, but if two datasets have an element with the same name, radius is not updated on transition
You might have to specifically select the circles for your transition rather than trying to do it on the outer group element. So instead of this:
pointsUpdate = points
.selectAll('circle')
.transition().duration(500)
.attr('r', function(d){ return d.value/2;});
Do this:
svg.selectAll('.nameinfo circle')
.data(data, function(d) { return d.name;})
.transition().duration(500)
.attr('r', function(d){ return d.value/2;});
UPDATE: Below is another way that fits better with the overall D3 philosophy of reusing the existing data/selection:
points
.select('circle').transition().duration(500)
.attr('r', function(d){ return d.value/2;});

Resources