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.
Related
I have the following d3 logic for rendering individual objects:
svg.selectAll("path")
.data(hugePathDataset)
.enter().append("path")
.attr("class", (d) => d.properties.cls )
.attr("id", (d) => d.properties.name )
.each(... canvas render logic ...)
For performance reasons, the svg element above is set to display: none, the real rendering happens on canvas via d3's projection logic. The svg element is still needed, however, for later updates to canvas (such as changing color of each path individually).
My dataset includes over 60,000 paths and above code takes around 30 seconds to run. Testing it in Chrome's profiler I noticed that 90% of this time is spent in reflow. This made no sense to me since canvas doesn't reflow and the SVG with display: none shouldn't reflow the DOM. As I continued looking into it, I realized that the reflow is not triggered by appending elements to invisible SVG, but by setting class and id attributes on these elements. Sure enough, if I remove lines 4 and 5, the reflow slowdown completely disappears. Setting other attributes (i.e. data-something) does not cause a slowdown/reflow.
The problem is that I'm then unable to manipulate these paths individually later, as described above. My questions are:
Why does adding class or id to an element with parent set to display: none trigger a reflow?
How can I work around this? How can I get these properties set without the slowdown?
Reading D3 documentation I realized that selection.append("path") is equivalent to selection.append(() => document.createElement("path")). Since document.createElement does not yet attach the element to the DOM, it should be safe to set properties on it without a reflow. I rewrote the above logic differently and the issue went away:
svg.selectAll("path")
.data(hugePathDataset)
.enter().append((d) => {
let element = document.createElement("path");
element.id = d.properties.name;
element.className = d.properties.cls;
return element;
})
.each(... canvas render logic ...)
I still don't understand why class/id change on invisible element causes a reflow, however, but I'm no longer blocked by this.
I experienced problems with the position of text elements when exporting SVG files and opening it in Corel Draw (some older version). I fixed it by setting every dx/dy attribute to zero and added its value to the corresponding x/y attribute.
I wrote a helper function which is called with .each on every text element I use.
transformDXYtoXY: function(d, i) {
var that = d3.select(this);
var y = that.attr("y") == null ? 0 : parseFloat(that.attr("y"));
var dy = that.attr("dy") == null ? 0 : parseFloat(that.attr("dy"));
that.attr("y", y + dy);
that.attr("dy", 0);
// doing the same with dx/x
...
},
This was working great until I decided to transition axis on input change instead of redraw them:
axis = d3.svg.axis().scale(someScale);
d3.select('.axis')
.transition()
.call(axis)
.selectAll("text")
.each(transformDXYtoXY);
Without the call to transformDXYtoXY() the tick label position is off
The y/dy attributes are not being set, even though when I check for it inside transformDXYtoXY() it seems allright.
Is there a way to tell d3 to avoid using dx/dy? It looks like the problem occurs during transition().
The use of the dx and dy attributes is hardcoded in the source of D3 -- changing it would be a significant effort. However, there's an easy workaround. D3 transitions allow you to set up a listener for the end of the transition. You can leverage this to run your code to fix the attribute values (with minimal changes to your existing code):
d3.select('.axis')
.transition()
.call(axis)
.selectAll("text")
.each("end", transformDXYtoXY);
To clarify, the code that you have at the moment runs the function to fix the attributes immediately after setting up the transition which then runs and overwrites the attribute values. The code above runs the function after the transition is complete, i.e. no further attribute changes will occur.
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 working with this jsfiddle. I expect that when I click the WeekView button it should change the bar colors to reflect the same colors that are in my legend. But for some reason the colors are different.
I don't think
var layer = svg.selectAll(".layer")
.data(stack);
layer.enter()
.append("g")
.attr("class", "layer")
.style("fill", function (d, i) {
return color(i);
});
layer.exit()
.remove();
is being called when I switch to weekview, therefore its not replacing the old bars with the new ones its just reusing the bars from the previous views.
How can I get d3.js to replace the bars with the proper colors?
Indeed, the problem is in that part of the code: Demo
var layer = svg.selectAll(".layer")
.data(stack);
layer.enter()
.append("g")
.attr("class", "layer");
// Set the colors in the `update` cycle, not the `enter` cycle.
layer.style("fill", function (d, i) {
return color(i);
});
layer.exit()
.remove();
There is an interesting history of why this behaves this way. In earlier versions of D3, the enter and update set of elements were kept separate, just like update and exit events are still kept separate, i.e. operations you performed on the update set would not be performed on the exit set and vice-versa.
However, in version 2.0 of D3, it was decided that any element appended in the enter phase would also become a part of the update set. This was done because often the enter set of elements and the update set of elements needed to have the exact same operation performed on them (like in your case). To avoid this effect, you'll need to write the update phase before the enter phase.
Hence, in the enter cycle, elements should be appended and their initial attributes should should be set while their final values (which they should have in static state) should be set in the update cycle.
I see a strange flickering effect after a transition. It is unusual mainly because I do not set the opacity in any way (I want the color to remain the same). Any ideas why this happens?
To have an idea about how the code looks like, here is an example.
var theBars = this.vis.selectAll(".bar" + source.id).data(this.columns);
theBars.enter().insert("svg:rect")
//some attributes
.style("fill", sourceColor)
//some other attributes
theBars.transition()
//.duration(.01)
.attr("y", function(d) {
return this.settings.base - this.getStackedBarHeight(d, source.id);
}.bind(this))
.attr("height", function(d) {
return this.getBarHeight(d.counters[source.id]);
}.bind(this));
As it can be seen only one line sets the color.
I initially tought I made some mistakes at binding, but after checking some posts here and on Google Groups, I discovered that this flickering usually appears when you have transitions that also change the opacity of the object. Unfortunately I don't change any opacity, I just make a transition. This effect appears in all major browsers when executing that transition (theBars.transition).
I try to select a bar from a stacked bar and modify its height.
Best regards!
To fix this I added 2 things:
in the init phase - I added all the bars but with all counters set
on 0;
in the draw phase - I added this code:
var theBars = this.vis.selectAll("#bar_"+index+"_"+currentIndex);
this.settings.sources.each(function(pair) {
theBars
.style("fill", source.color)
.attr("height", this.getBarHeight(source.id)
.attr("y", this.settings.size.baseLine - this.getStackedBarHeight(counters, source.id))
}, this);
The flickering caused by the transitions is gone since we have no transition here. There are still situations in which we need to do a transition, for example when we have several bars with the same word, but there I resolved the flickering by doing it really quick (a .duration(.1) or even less).