Transitions in D3 - Duplicate bubbles instead of transitioning bubbles - d3.js

I'm trying to create something similar to this example: Wealth and Health of Nations:
My data comes from a JSON file, just like the example, but when I add the transitions, I'm getting duplicate bubbles. Instead of the bubble transitioning from point A to point B I'm getting 2 bubbles (one for point A, one for point B). Generally speaking, the transition is not able to differentiate between 2 data points for the same bubble or 2 separate bubbles.
Looking at the example, I'm missing the interpolate and bisect functions. I haven't been able to grasp how they work and what exactly i'm doing wrong. Is this what's causing the problem in my graph?
Also, can someone give me an example on how bisectors and interpolate works in d3?
Code:
g = d3.select("#animation")
.append("svg")
.attr("width", width)
.attr("height", height);
x_extent = [0, 100];
x_scale = d3.scale.linear().domain(x_extent).range([margin + 20, width - 30]);
y_extent = [0, 60];
y_scale = d3.scale.linear().domain(y_extent).range([height - margin, margin]);
r_scale = d3.scale.linear().domain([0, d3.max(jsondata, function (d) { return d.MSVMMboe; })]).range([2, 30]);
g.selectAll("circle").data(jsondata, function (d) { return d.EffectiveDate; }).enter().append("circle")
.attr("cx", function (d) { return x_scale(d.PercentageComplete * 100) })
.attr("cy", function (d) { return y_scale(d.GPoS * 100) })
.attr("r", function (d) { return r_scale(d.MSVMMboe) })
.attr("stroke", "blue")
.attr("stroke-width", 1)
.attr("opacity", 0.6)
.attr("fill", "red");
//add transition
g.selectAll("circle").data(jsondata, function (d) { return d.EffectiveDate; })
.transition()
.duration(1000);

You haven't told the transition what you want to change. You need to add some attribute changes for example. Have a look at the d3 website for examples and tutorials.

Related

Choropleth map scale and legend

Let me preface this by saying I am brand new to D3.js and coding in general. I am an infographic artist and I've been using QGIS to generate maps, but am trying to use D3.js to generate a choropleth map for a story about Opioid deaths. Basically I am trying to recreate this map.
map from the Economist
I have tried to start by using this map by Mike Bostock and changing some of the parameters but am getting stuck with the color range and scale. The measurement is 1 per 100,000 population. I have a domain that starts at 1.543385761 and ends at 131.0814217.
The code I'm struggling with is around the scale input and output:
var x = d3.scaleLinear()
.domain([0, 132])
.rangeRound([600, 860]);
var color = d3.scaleThreshold()
.domain(d3.range(2, 10))
.range(d3.schemeBlues[9]);
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(0, 40)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); });
I can see that I need some bit of code that will define everything 25 and over as the darkest color. Not even sure I want that to be my final legend but I'd love to know how to reproduce that. I am shocked I was able to get this far but feel a bit lost right now. thank you in advance!
Let's examine your scale:
var color = d3.scaleThreshold()
.domain(d3.range(2, 10))
.range(d3.schemeBlues[9]);
Your domain is an array of created like so:
d3.range(2,10) // [2,3,4,5,6,7,8,9]
These are your thresholds, colors will be mapped based on values that are less than or equal to 2, more than two up to three, more than three and up to four .... and over 9. This domain is mapped to nine values defined in the range:
d3.schemeBlues[9] // ["#f7fbff", "#deebf7", "#c6dbef", "#9ecae1", #6baed6", #4292c6", "#2171b5", "#08519c", "#08306b"]
To set the thresholds for those colors so that values over 25 are one color, define the domain with array that has the appropriate threshold(s):
.domain([2,3,4,5,6,7,8,25]);
In the snippet below, this domain is applied. Rectangles have colors dependent on their location, all rectangles after the 25th (count left to right then line by line) one will be of one color.
var color = d3.scaleThreshold()
.domain([2,3,4,5,6,7,8,25])
.range(d3.schemeBlues[9]);
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",500);
var rects = svg.selectAll("rect")
.data(d3.range(100))
.enter()
.append("rect")
.attr("width",15)
.attr("height", 15)
.attr("y", function(d,i) { return Math.floor(i / 10) * 20 + 10 })
.attr("x", function(d,i) { return i % 10 * 20 })
.attr("fill", function(d) { return color(d); })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>

D3.js get data index of shape on mouseover event

I am attempting to access the data index of a shape on mouseover so that I can control the behavior of the shape based on the index.
Lets say that this block of code lays out 5 rect in a vertical line based on some data.
var g_box = svg
.selectAll("g")
.data(controls)
.enter()
.append("g")
.attr("transform", function (d,i){
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+ 5))+")"
})
.attr("class", "controls");
g_box
.append("rect")
.attr("class", "control")
.attr("width", 15)
.attr("height", 15)
.style("stroke", "black")
.style("fill", "#b8b9bc");
When we mouseover rect 3, it transitions to double size.
g_box.selectAll("rect")
.on("mouseover", function(d){
d3.select(this)
.transition()
.attr("width", controlBoxSize*2)
.attr("height", controlBoxSize*2);
var additionalOffset = controlBoxSize*2;
g_box
.attr("transform", function (d,i){
if( i > this.index) { // want to do something like this, what to use for "this.index"?
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+5)+additionalOffset)+")"
} else {
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+5))+")"
}
})
})
What I want to do is move rect 4 and 5 on mouseover so they slide out of the way and do not overlap rect 3 which is expanding.
So is there a way to detect the data index "i" of "this" rect in my mouseover event so that I could implement some logic to adjust the translate() of the other rect accordingly?
You can easily get the index of any selection with the second argument of the anonymous function.
The problem here, however, is that you're trying to get the index in an anonymous function which is itself inside the event handler, and this won't work.
Thus, get the index in the event handler...
selection.on("mouseover", function(d, i) {
//index here ---------------------^
... and, inside the inner anonymous function, get the index again, using different parameter name, comparing them:
innerSelection.attr("transform", function(e, j) {
//index here, with a different name -----^
This is a simple demo (full of magic numbers), just to show you how to do it:
var svg = d3.select("svg");
var data = d3.range(5);
var groups = svg.selectAll("foo")
.data(data)
.enter()
.append("g");
var rects = groups.append("rect")
.attr("y", 10)
.attr("x", function(d) {
return 10 + d * 20
})
.attr("width", 10)
.attr("height", 100)
.attr("fill", "teal");
groups.on("mouseover", function(d, i) {
d3.select(this).select("rect").transition()
.attr("width", 50);
groups.transition()
.attr("transform", function(e, j) {
if (i < j) {
return "translate(40,0)"
}
})
}).on("mouseout", function() {
groups.transition().attr("transform", "translate(0,0)");
rects.transition().attr("width", 10);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: don't do...
g_box.selectAll("rect").on("mouseover", function(d, i){
... because you won't get the correct index that way (which explain your comment). Instead of that, attach the event to the groups, and get the rectangle inside it.
I'm pretty sure d3 passes in the index as well as the data in the event listener.
So try
.on("mouseover", function(d,i)
where i is the index.
Also you can take a look at a fiddle i made a couple months ago, which is related to what you're asking.
https://jsfiddle.net/guanzo/h1hdet8d/1/
You can find the index usign indexOf(). The second argument in the event mouseover it doesn't show the index in numbers, it shows the data info you're working, well, you can pass this info inside indexOf() to find the number of the index that you need.
.on("mouseover", (event, i) => {
let index = data.indexOf(i);
console.log(index); // will show the index number
})

How do I pass bound data to a function in d3?

I am trying to make a small multiples chart by following Mike Bostock's example.
This example uses enter().append("svg") to create a new SVG for each data point. In each SVG you would then create the chart.
I have data that is in a CSV file that looks like this:
count, radius
15, 5
10, 3
With this data I'd like to create two SVGs (one for each data point), with the first one containing 15 circles each with a radius of 5, and the second svg containg 10, each with a radius of 3. I have a function drawCircles that I wish to use to draw the circles based on my dataset, however I'm having trouble passing the data through to my function.
Here's my code:
d3.csv("nations.csv", function(data) {
var svg = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
drawCircles(function (d) {return +d.count;}, function (d) {return +d.radius;})
I can't seem to pass d.count and d.radius through as arguments to my drawCircles function. Can anyone please help?
Here's a very d3ish way of doing what you are after:
d3.csv("some.csv", function(d){
// coerce your data to numbers
return {
count: +d.count,
radius: +d.radius
}
},
function(data){
// create your svg for each row of data
var s = d3.selectAll("svg")
.data(data)
.enter()
.append("svg")
.attr("width", 600)
.attr("height", 100);
// use a sub-selection to create a circle for each count
s.selectAll('circle')
.data(function(d){
// the bound data will simply be an array with repeating radius
return d3.range(d.count).map(function(_){ return d.radius });
})
.enter()
.append('circle')
// radius is the same for each circle
.attr('r', function(d){
return d;
})
// space the circles so they look good;
.attr('cx', function(d,i,j){
return ((d + 2) * 2) * i + 10;
})
.attr('cy', 50)
.style('fill', 'steelblue');
});
Example here.

Animation with Data Joins d3

I'm trying to animate a data join. I can get the state to change and update but there isn't any seamless animation between states.
I tried to refer to this to answer my question:
d3.js trying to animate data, but changed data always treated as new
This data is created upon json load...and then, on a button click, i'm just simply adding 20 ( for testing purposes ) to my y values...it updates but there is no transition. What am i selecting incorrectly?
The variable bar is already defined globally.
function btnClick(){
//bar.exit().remove();
bar = chart.selectAll("g")
.data(mydata.players)
console.log(bar.data(mydata.players))
bar.exit().remove()
bar.enter().append("g")
.transition()
.duration(1650)
.attr("transform", function (d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr('width', x.rangeBand())
.attr("y", function (d) {
return y(d.money+20)
})
.attr("height", function (d) {
return height - y(d.money+20);
})
Trying to move the transition before the height change:
chart.selectAll("g")
.data(mydata.players);
bar.exit().remove();
bar.attr("transform",
function(d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr('width', x.rangeBand())
.transition()
.attr("y", function(d) {
return y(d.money + 20)
})
.attr("height", function(d) {
return height - y(d.money + 20);
})
I think i'm just not grabbing the right selection for bar in btnClick(). Maybe I'm not supposed to grab all the 'g' elements? And the bar is defined on json load earlier so I'm not certain I should even have to define it again by selecting all the g elements....if it's already been built. I have stored it and should be able to manipulate it: but if I don't define it, then for some reason the exit.remove() doesn't work.
Ok, I made the small necessary changes to your code to have this working. So, two things to note:
you want to select your rectangles for update:
bar.select("rect")
.attr('width', x.rangeBand())
.transition().duration(750).ease("linear")
.attr("y", function (d) {
return y(d.money + 20)
})
.attr("height", function (d) {
return height - y(d.money + 20);
})
you want to re-set your y domain to accommodate for the new data values (you really don't have a new set of data, you are just adding 20...so, do the same here):
y.domain([0, d3.max(mydata.players, function (d) {
return d.money + 20;
})])
NOTE: your tallest bar will not move since its value puts it at the top of the old and new scales (the domain is based on the data values). To see all bars rise, you can inflate the domain of your original data (say, + 20). I placed a comment where you can do this.
Here is the complete FIDDLE.

Aggregate data via d3 to form y axis and y values

I had to reformat this JSON file from what I originally had and am having a hard time creating a summary graph with it. I have the x axis working the way I want but am not having the same luck with the y axis and y values. I need a total of all open, all responded, etc.
y.domain([0, d3.max(color.domain(), function (d) { return d; })]);
and this is the part that plots the rectangles, as I mentioned, the x is correct, just the y needs some TLC.
svg.selectAll(".bar")
.data(color.domain())
.enter().append("rect")
.attr("class", "bar")
.style("fill", color)
//.attr("height", function (d) { return height - y(d.stati) })
.attr("x", function (d) { return x(d); })
.attr("width", 60);
here is a jsfiddle with a link to an example data set:
http://jsfiddle.net/ba5m8/
dataset:
https://dl.dropboxusercontent.com/u/23726217/queryStatusBar.json

Resources