I am using d3js to visualize my data. Everything works fine, when I have my "normal" code running. But now when I add animations for the insertion and deletion of elements and I insert/delete data elements too fast, then the animations of the first inserted element is not completed before the next element being inserted triggers the animations once again. Is there a way to wait for the animations of d3 to be completed?
Example code für an animation:
layoutRoot.selectAll('g')
.data(nodes, function (d) { return d.path })
.exit()
.transition()
.duration(400)
.style('opacity', 0)
.remove()
This would be a comment but I don't have the necessary reputation.
See this d3 issue on github. Is this what you're looking for?
It's in the 3.3 milestone and the current version is 3.2.8. So, hopefully it won't be too much longer.
Related
Note: I asked this question about interrupting transitions during a scroll, but am trying a different technique now that is resulting in a similar issue that doesn't get resolved with the accepted (and working) answer.
This time, rather than initializing all the graphs with 0 opacity and having a separate function to change the opacity that gets called on each step, I'd like to use selection.remove() in each drawing function. I want to do this so that out-of-view graphs don't get in the way of any mouseover interactions that I might want on the current graph.
For example, I have functions that clear the existing graphs and then draw the current one with some transition:
var makeCircle0 = function() {
d3.selectAll(".nancygraphs").interrupt().remove()
g.append("circle")
.attr("cx", 50)
.attr("cy", 100)
.attr("r", 20)
.attr("fill", "red")
.attr("id", "red")
.attr("opacity", 0)
.transition()
.duration(1000)
.attr("opacity", 1)
.attr("class", "nancygraphs")
}
These functions are put in a list
var activateFunctions = [];
activateFunctions[0] = makeCircle0;
activateFunctions[1] = makeCircle1;
activateFunctions[2] = makeCircle2;
activateFunctions[3] = makeCircle3;
And depending on the step, the function gets called to draw the correct graph
function handleStepEnter(response) {
console.log(response)
step.classed('is-active', function(d, i) {
return i === response.index;
})
figure.select('p').text(response.index);
figure.call(activateFunctions[response.index]) // HERE!
}
Here is a jsfiddle to illustrate. Basically, if you scroll back-and-forth quickly then old graphs don't get cleared and you'll notice several graphics in view simultaneously. Why isn't d3.selectAll(".nancygraphs").interrupt().remove() doing the job?
Three observations regarding your approach:
First, according to the d3 manual on
transitions:
remove: remove the selected elements when the transition ends.
The remove will not interrupt already running transitions - it only removes when all transitions have stopped. More specifically it seems to act when __transition__.count of an element reaches 0. You could consider using a non-d3 remove implementation here, e. g. jQuery.
Second, from the same manual:
Interrupting a transition on an element has no effect on any transitions on any descendant elements. (...) you must therefore interrupt the descendants: selection.selectAll("*")
You should call interrupt on both by doing d3.selectAll(".nancygraphs").interrupt().selectAll("*").interrupt().
Third, it is never a good idea to directly couple mouse or scroll input to your logic (when you directly couple input events to e. g. attaching a transition, you might be doing so many thousands of times): did you use a debounce function? The lodash implementation is highly recommended.
After trying these modifications I would assume your current problem is solved. If it is not, a further way of debugging would be to log / overwrite the __transition__.count attribute of your elements.
I am moving a rectangle from point a to point i in a picture, i want to mark a stop delay of 5s for each point (there are 8 points). the transitions work fine in the code below (the delay works only for point b).The problem is that i can't add more delays for my other transitions.
Is there any way to do it ?
Thank you all in advance.
function TRANSITION(access,dur=10000,Delay=5000,b=390.5,c=523,d=632.5,e=810.8,f=942.5,g=1063,h=1196,i=1334.5)
{
access.transition().duration(dur).attr('x',b)
.transition().delay(Delay).duration(dur).attr('x',c)
.transition().duration(dur).attr('x',d)
.transition().duration(dur).attr('x',e)
.transition().duration(dur).attr('x',f)
.transition().duration(dur).attr('x',g)
.transition().duration(dur).attr('x',h)
.transition().duration(dur).attr('x',i)
}
You could add a transition that changes no attributes but still has a duration:
.transition()
.duration(dur)
.attr('x',d)
.transition() // don't transition anything
.duration(5000) // but take five seconds doing it
.transition()
.duration(dur)
.attr('x',e)
I've only tested this in version 4, so it is possible that this might not work in version 3. Alternatively, you could add the .attr line in the delaying transition if you were to leave some attribute the same.
I have a d3 visualisation on a web page that shows a collection of plot points on a chart.
It's all very simple really with just two axis and sometimes maybe 30 points to plot. As I render these plots I perform a transition on the r attributes of the SVG circles. The code that does this is:
g.append("svg:circle")
.attr("class", "plot-circle")
.attr("cx", xCo)
.attr("cy", yCo)
.attr("r", 0)
.transition()
.delay(function() { return delayInput * 100; })
.duration(plotExplosionDuration)
.ease(Math.sqrt)
.attr("r", 6);
All that occurs is that the circles are initially set to r=0 which means they aren't rendered at all. Then I start a transition on the appended circle to take this radius up to 6.
The problem is that it appears on some machines these transitions don't stop at r=6 and some plots end up being much bigger than the value set after the transition.
I simply cannot duplicate this on my main development machine (PC), my iPad nor my MacBook Pro which leads me to think it might be performance or machine load causing this?
has anyone got any ideas on how to ensure the transition stops at the defined final r value?
Here is the structure of my HTML
svg
g id=invisibleG
g
circle
g
circle
g
circle
So I want something like this on hover of any particular circle
svg
g id=invisibleG
g
circle --> radius is increased on hover.....decreased on hoverout
text
g
circle
g
circle
here is the code
.on("mouseover",function(){
var r=d3.select(this).attr("r");
d3.select(this).style('fill','tan')
.style('fill-opacity', '1')
.transition()
.duration(1000)
.attr("r",50);
d3.select(this).attr("stroke","blue")
.attr("stroke-width",4);
})
.on("mouseout",function(){
var r=d3.select(this).attr("prevRadius");
d3.select(this).attr("r",r)
.attr("stroke-width",0)
.style('fill-opacity','0');
});
Now the problem is that when I hover over a circle and immediately hover out of it the transition which is started in mouseover doesn't stop immediately.It completes its transition and the size of radius is increased despite being the fact that mouseout event should be called.And whatever the transition was going should stop.
Please let me know the problem and its solution .
You just need to use transitions in both cases. From the documentation:
If a newer transition runs on a given element, it implicitly cancels any older transitions, including any that were scheduled but not yet run. This allows new transitions, such as those in response to a new user event, to supersede older transitions even if those older transitions are staged or have staggered delays.
So your code would need to be something like this.
.on("mouseover", function() {
this.prevRadius = d3.select(this).attr("r");
d3.select(this)
.style('fill','tan')
.style('fill-opacity', '1')
.transition()
.duration(1000)
.attr("r",50)
d3.select(this)
.attr("stroke","blue")
.attr("stroke-width",4);
}).on("mouseout", function() {
d3.select(this)
.transition()
.attr("r", this.prevRadius)
.attr("stroke-width",0)
.style('fill-opacity','0');
});
Demo here.
If your d3 version is mature enough (3.3+), they seemed to have added selection.interrupt
So you could perhaps try:
.on("mouseout",function(){
d3.select(this).interrupt();
// if interrupt isn't supported in your version use below
//d3.select(this).transition().duration(0);
})
Otherwise, newer transitions on the same selection will cancel the old active transitions. So you could run a new transition on mouseout where you transition to the reset values again. If you want to just freeze the transitions, just run a dummy transition to cancel the old one.
FURTHER INSIGHTS
If your goal is to stop the only transition of the r (radius) dead in its tracks see this fiddle which uses interrupt.
If your goal is to reset the r value or the non-transition changes you've made during the mouseover, see this fiddle
I'm fairly sure if I can find an example showing what I'm trying to do, I can reverse engineer/reimplement it. Has anyone seen an example showing a smooth/animated transition between a linear and log scale in D3JS?
I have both scales working independently, but I have to reload the page to change the scale.
My Google skills have failed me!
Thanks so much.
Here's a proof of concept jsfiddle. You simply reselect the data points and redraw them with the new scale. For the axis labels, the transition is even simpler -- you just need to call the axis function again. Relevant excerpt below.
// change to log scale...
yScale = d3.scale.log().domain([1, 100]).range([dim-padding,padding]);
svg.selectAll("circle").data(data)
.transition().delay(1000).duration(1000)
.attr("cx", function(d) { return xScale(d); })
.attr("cy", function(d) { return yScale(d); });
svg.selectAll(".y")
.transition().delay(1000).duration(1000)
.call(yAxis.scale(yScale));
You might need to play around with how the labels are generated to make it look "nice", but in principle d3 will take care of the entire transition.