javascript d3.js - Multiple transitions of points on a scatter plot - javascript

My objective is to take a set of points, and move them (.transition.duration()) a few times, in series-like fashion.
Example of code:
d3.csv("X.csv", function(csv) {
// initialize circles at random positions
svg.selectAll("circle")
.data(csv)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(80*Math.random());
})
.attr("cy", function(d) {
return y(500*Math.random());
})
.attr("r", function(d) {
return r(Math.sqrt(10*Math.random()));
})
.style("fill", function(d) {
return color(d.A);
})
.style("opacity", 1.0)
.style("stroke-opacity", 1)
.style("stroke-width", 3)
.style("stroke", function(d) {
return stroke(d.B)
});
// Move #1: moving the marks to their position
svg.selectAll("circle")
.transition().duration(2000)
.attr("cx",function(d) {
return x(+d.C);
})
.attr("cy",function(d) {
return y(+d.D);
})
.attr("r",function(d) {
return r(Math.sqrt(+d.E));
})
.style("opacity", 0.8);
//Move #2: move again to highlight
svg.selectAll("circle")
.transition().duration(2000)
.style("opacity", function(d) {
if (d["A"] == "male") {
return 0.1;
} else if (d["A"] == "female") {
return 0.8;
}
});
}
Problem: Running as is, Move #1 is skipped over.
Failed Attempts: If I comment out Move #2 section, then Move #1 works. If I comment out Move #1 section, then Move #2 works.
Ideas considered: I have Googled .delay, setTimeout(), and other options with .exit() and further data bind steps, but I believe there should be something simpler that exists. I have also tried to follow this SO post, but have a hard time following the "General Update Pattern" examples of the first answer.
Question: How do I get Move #1 and Move #2 to work in succession (with possible further Moves #3, #4, etc.)?

Excellent tutorial here
Idea is delay the second transition by the duration of first transition.
So if you have 3 transitions each of duration 1 sec then, delay the second by 1 sec and third by 2 sec, because we have to wait for both first and second transitions to complete. Hope you get the idea.
var canvas = d3.select('body')
.append("svg")
.attr("width",500)
.attr("height",500);
var addcircle = canvas.append("circle")
.attr("cx",50)
.attr("cy",50)
.attr("r",25);
var circles = d3.select('circle');
// first transition
circles.transition().duration(1000)
.attr("cx",250);
// 2nd
circles.transition().delay(1000)
.duration(1000)
.attr("cy",250)
// 3rd
circles.transition().delay(2000)
.duration(1000)
.attr("cx",50)
// 4th
circles.transition().delay(3000)
.duration(1000)
.attr("cy",50);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

In D3 chart, On Click is not working on circle as expected

In My D3 chart, OnClick is not working as expected.
Any suggestions ?
It seems because of focus line and focus circle it is creating the issue.
Expected behaviour should be on click circle it should pop up alert.
PS: .on("click" works if i try to click on circle when focus line and circle is not present.
My Code sandbox here
Code -
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d.startTime);
})
.attr("cy", function(d) {
return yScale(d.magnitude);
})
.attr("r", function(d) {
if (d.startupRunningStatus === "OUT_OF_SYNC") {
return 5;
}
})
.on("click", function(d) {
alert("on click" + d.magnitude);
})
.attr("class", "circle");
It is a stack issue. As you see in the screenshot, the rect overlays the circles. So you need to append the elements in order.
The quickets and easiest fix is to append circle after appending the rect.
https://codesandbox.io/s/nifty-brattain-vi5g4

Simple circle animation is only displayed the first time

I'm trying to put circles on a map every second. This animation consists of 4 circles that are shown once a point is added on the map.
After the first time the animation is not repeated again. I do not know why this happens. When new points are added, the animation does not happen again.
https://plnkr.co/edit/benkcHIINN9DCjvIvtEn?p=preview
var aNumCircles=[1,2,4,5];
function addpoints(){
//add circle on map
var coordenadas=map.latLngToLayerPoint([coordinates[cont].lat,coordinates[cont].long]);
svg.append('circle').attr("cx",coordenadas.x)
.attr("cy", coordenadas.y)
.attr("r", 1)
.style("fill",'red')
.attr("class",'circulo_mapa')
//add animation circles on map
var circle = svg.selectAll("circle").data(aNumCircles).enter().append('circle')
.attr("cx",coordenadas.x)
.attr("cy", coordenadas.y)
.attr("id", cont)
.attr("r", 0)
.style("stroke-width", function(d,i){ return 5 / (i+1) })
.attr("class", 'animation_explosion')
.transition()
.delay(function(d,i){ return Math.pow((i+1), 2.5) * 50 })
.duration(2000)
.ease('quad-in')
.attr("r", 25)
.style("stroke-opacity", 0)
.each("end", function (d,i) {
d3.select(this).remove();
});
cont++;
}
var interval = setInterval(function(){
addpoints();
if(cont==5){
clearInterval(interval);
}
},1000);
The problem is just the first line in this selection:
var circle = svg.selectAll("circle")
.data(aNumCircles)
.enter()
.append("circle")
//etc...
Since there are already circles in the SVG at the second time addpoints() runs, your "enter" selection will be empty.
Instead of that, it should be:
var circle = svg.selectAll(null)
.data(aNumCircles)
.enter()
.append("circle")
//etc...
By using selectAll(null) you can be completely sure that your "enter" selection has all the elements in your data array.
Here is the updated plunker: https://plnkr.co/edit/3u0er01thuj5P8e0XqO6?p=preview

How to make circle to appear one after another using d3 transition?

I am following the circle example:
I created the circle below, and I wish to make the opacity transition be that as the data set updates, the circle will start appearing one after another. For example, if the data length is 5, then circle 1 appears, then circle 2, ... finally circle 5. And if the data is updated so its length is 2, then circle 1 appears, then circle 2 appears. How do I do this effect? So far, the transition() works on the data set uniformly.
circle.enter().append("circle")
.attr("class", "dot");
// Update (set the dynamic properties of the elements)
circle
.attr("r", 5)
.attr("cy", 20)
.attr("cx", function(d,i){return i*50;})
.attr("fill", "red");
svg.selectAll("circle")
.style("opacity", 0)
.transition()
.duration(1000)
.style("opacity", 1);
Problem:
Setting a delay for each element in a "transition" selection.
Solution:
Use delay() with function(d, i)
Instructions:
You have to add this after transition():
.delay(function(d,i){ return i * someNumber })
Where someNumber is the delay, in milliseconds, for each element.

How to update selection with new data in D3?

I'm trying to edit the data of created circles in D3. Below my code is pasted of me creating a lot of circles based on some data from graphData.
Supposed I'd want to re-arrange my circles Y position with a new dataset, by transitioning them to their new destinations. How would perform this task? I've tried using attr.("cy", function(d){return yScale(parseFloat(d))} ) to update my Y-coordinates by adding data(graphData[i], function(d){return d;}) with my new data, but this does not work.
You can take a look at my JSFiddle: http://jsfiddle.net/RBr8h/1/
Instead of the for-loop in the following code I've created circles on 2 ticks of my X-axis. I have 3 sets of data and I've used to of them in the example in the fiddle. I'd like to able to use the 3rd dataset instead of the 2 first ones on both circles.
var circle;
for(var i = 0;i < graphData.length;i++){
circle = SVGbody
.selectAll("circle")
.data(graphData[i], function(d){return d;})
.enter()
.append("circle")
.attr("cx",xScale(0))
.attr("cy", yScale(minAxisY))
.attr("r",4)
.style('opacity', 0)
.transition()
.duration(1000)
.attr("cx", function(d){
return spreadCircles(i);
})
//.attr("cy", function (d, i){ return yScale(i); })
.style('opacity', 1)
.transition()
.duration(1500)
.attr("cy", function(d){return yScale(parseFloat(d))} );
Thank you for your help in advance!
To put some flesh on Lars comment, here is a FIDDLE leveraging the enter/update/exit pattern to help you out. I have altered and simplified your code (and data) just enough to demonstrate the principle.
function updateCircles(dataset,color) {
var circle = SVGbody
.selectAll("circle")
.data(dataset, function(d) { return d; });
circle
.exit()
.transition().duration(750)
.attr("r", 0)
.remove();
circle
.enter()
.append("circle");
circle
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",0)
.transition().duration(1500)
.attr("r",5)
.style("fill", color);
};
Update fiddle with data keyed off by index...so, circles just have their position updated.

Interactive Legend onclick or mouseover - D3js

I've been looking for a way to have my legend control my chart animation (similar to NVD3 examples). I've run into a problem though - nested selections.
var legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(70,10)")
;
var legendRect = legend.selectAll('rect').data(colors);
legendRect.enter()
.append("rect")
.attr("x", w - 65)
.attr("width", 10)
.attr("height", 10)
.attr("y", function(d, i) {
return i * 20;
})
.style("stroke", function(d) {
return d[1];
})
.style("fill", function(d) {
return d[1];
});
I'm using a bit of a hack to do my animation. Basically setting style to display: none.
I want to be able to click on the rectangles and call the function. But putting a mouseover or onclick within legendRect doesn't work. The bars to animate are not children of the legend. How can I call the function, or chain my function to my legend?
function updateBars(opts) {
var gbars = svg.selectAll("rect.global");
var lbars = svg.selectAll("rect.local");
if (opts === "global") {
gbars.style("display", "block") ;
lbars.style("display", "none");
gbars.transition()
.duration(500)
.attr("width", xScale.rangeBand());
};
if (opts === "local") {
lbars.style("display", "block")
;
gbars.style("display", "none");
lbars.transition()
.duration(500)
.attr("x", 1 / -xScale.rangeBand())
.attr("width", xScale.rangeBand());
};
}
My other obstacle is changing the fill color on click. I want it to almost imitate a checkbox, so clicking (to deselect) would turn the fill white. I tried something similar as .on("click",(".style" ("fill", "white"))). But that is incorrect.
Here is my fiddle. For some reason, the function isn't updating things on Fiddle. It works on my localhost though. Not sure the problem with that.
I'm not completely sure I understand you correctly, but if your first question is how to change element X when clicking on element Y, you need something along the lines of:
legendRect.on("click", function() {
gbars.transition()
.duration(500)
.style("display", "block")
// etc...
}
As for changing the fill on click, try:
gbars.on("click", function() {
d3.select(this)
.attr("fill", "white");
}

Categories

Resources