You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
.animate method that can handle any type of figure diff #1849
In Dash, I'd like users to be able to toggle on and off an animate property which would either smoothly transition points (if available) or just update the chart.
Right now, Plotly.animate doesn't work with all chart transitions and data transitions. While I could program this myself in the Graph component, it would be great if plotly.js could do this so that everyone could benefit. We could even use this in the workspace to smooth out transitions!
Ideally, I would just replace the .newPlot(id, data, layout) call with a .animate(id, data, layout) call and call it a day.
In particular, here are some of the edge cases:
.animate to work before .plot is called
.animate to work across all chart types (even if it's not a smooth animation)
.animate to autorange (expand axes if the new data was larger than the existing data)
.animate when new traces are added or removed
.animate when chart types have been changed
I'm OK if this is a new method or a new config option or whatever.
.animate to autorange (expand axes if the new data was larger than the existing data)
That's arguably not always the desired behavior. We'll probably need another attribute.
If autosize=true, I think that I'd argue that this is the expected behaviour. If the user wants to restrict the range to a certain view, they can set the range manually.
I 👍 -d @etpinard's comment b/c sometimes expansion (or shrinking) is not the intent - indeed, keeping the range(s) constant is achievable by explicitly setting the range. I was thinking of a boolean stickiness attribute (true: retain the initial autodomain vs. false: expand/shrink as needed) but it may be unnecessary complication as the range can be directly set as you suggest. It's likely I didn't have the same thing in mind as @etpinard, just wanted to add my thought.
Btw. it might occur more often that data is removed, yet one wants to preserve the original full domain (eg. when eliminating points via crossfiltering), than that data is added, yet one would not want to see them.
Even with crossfiltering point elimination, setting an explicit range would work, though it's slightly more convenient to rely on plotly.js for the initial (full) range calculation than doing it in userland (or crossfiltering) code as it'd just duplicate aggregation (extent calculation) logic.
If autosize=true, I think that I'd argue that this is the expected behaviour.
Right, if a user explicitly set autosize to true, I agree. But, autosize defaults to true when the axis ranges aren't valid. In that case, I suspect keeping the first view when animating is more desirable.
keeping the first view when animating is more desirable
Doesn't that mean the rendered plot will be out of sync with the input state? I know we push ranges back to the input state, but autorange: true is supposed to mean that it will recalculate the ranges to fit the given data, and indeed if you copy the figure json and make a new plot with it that will happen. Or if you change the data using restyle for that matter.
Perhaps we need a new autorange setting (autorange: 'once'?) that will run autorange once and then revert to false (pushed back to the input state), to account for all these desires.
FWIW I would favour having an animate flag somewhere such that .react() would animate a transition or something like that, rather than a .animate() method.
Not sure if my question is quite related to this issue, but I want to avoid the axes to be recalculated when the user deselect a particular trace by clicking on the legend. Is this currently possible? Thanks.
The premise of making Plotly.react "call" Plotly.animate is somewhat wrong:
Plotly.react diffs its input with the current state and executes the required subroutines to reach the state as described in the input. Upon completion, the "old" state is lost.
Plotly.animate applies frames -- which work as data/layout patches, not full descriptions -- to a "base" state. This "base" state can be the "current" state or it can combine a baseframe.
gives a scatter trace with red markers but of size 20.
I can't think a use-case for the frames API for Plotly.react users. Frames were added to make animations from layout sliders and updatemenus fully JSON-serializable. Plotly.react users most likely have a more imperative set of interactions (please, correct me if I'm wrong!). In other words, they compute their "frames" on-the-fly.
In plotly.js lingo, this ticket is about making Plotly.reacttransition from "old" to "new" data/layout. More technically, Plotly.react should call Plots.transition when a transition is desired. I'm proposing initiating transitions via Plotly.react with:
Plotly.react(gd,fig,transitionOpts);
which doesn't conflict with the react(gd, data, layout, config) signature and where the transitionOpts are:
'The duration of the transition, in milliseconds. If equal to zero,',
'updates are synchronous.'
].join(' ')
},
easing: {
valType: 'enumerated',
dflt: 'cubic-in-out',
values: [
'linear',
'quad',
'cubic',
'sin',
'exp',
'circle',
'elastic',
'back',
'bounce',
'linear-in',
'quad-in',
'cubic-in',
'sin-in',
'exp-in',
'circle-in',
'elastic-in',
'back-in',
'bounce-in',
'linear-out',
'quad-out',
'cubic-out',
'sin-out',
'exp-out',
'circle-out',
'elastic-out',
'back-out',
'bounce-out',
'linear-in-out',
'quad-in-out',
'cubic-in-out',
'sin-in-out',
'exp-in-out',
'circle-in-out',
'elastic-in-out',
'back-in-out',
'bounce-in-out'
],
role: 'info',
description: 'The easing function used for the transition'
},
}
The other animation attributes in that same file have no meaning when we don't consider frames (with the exception of frame.redraw, which I'm thinking of mapping to the transition attribute container).
So here, we're not adding any config or layout attribute, we're asking Plotly.react users to describe how each update transitions from old to new state.
If performance isn't a concern, resolving this issue essentially sums up to adding a version of Plots.transition that replaces "old" with "new" instead of merging "new" with "old".
While working on this, I'll probably for sure discover animation bugs/regressions which will slow down progress. But, all in all, I should be able to make a PR for this by the end of October.
Just trying to figure out how this will work in practice... if the graph is expected to simply receive a new figure and adapt to it, how will the app know when to provide transitionOpts and when not to? One thing that comes to mind is a declarative spec like "if changes are within this set of attributes, do this transition; if they're in this other set of attributes, do this other transition; anything else, no transition". That could take a form like (in layout or figure):
transitions: [{// scatter -> any scatter trace? or maybe 'data[0].x'?// can't animate name so this would invoke redraw after animating x/yattributes: ['scatter.x','scatter.y','scatter.name'],duration: 1000,easing: 'bounce'},{// some other set, in this case including the first set and moreattributes: ['scatter.x','scatter.y','scatter.name','layout.xaxis.range','layout.yaxis.range'],duration: 1500,easing: 'cubic-in-out'}]
That way if you have an app that has controls like log/linear axes, or changing colors or something, those will just happen with no transition, but when you update the pieces that are expected to transition, they will do so automatically.
We could probably start with transitionOpts and then add something like this later, but it seems like a declarative version would be a lot easier to manage.
with the exception of frame.redraw
Again, ideally we just determine this from the diff, perhaps by marking each schema attribute that does support animation as animatable: true, and if any attribute has a diff and does not describe itself as animatable, we need to redraw.
Just trying to figure out how this will work in practice... if the graph is expected to simply receive a new figure and adapt to it, how will the app know when to provide transitionOpts and when not to?
Yep, pretty much exactly how I felt when I read that :)
@chriddyp this would be problematic for Dash too right?
Ok, I'm starting to think @alexcjohnson 's layout.transitions attribute container as described in #1849 (comment) is what we want here. In similar fashion to plotly/dash-core-components#198, putting new things layout seems to make users of the Plotly.react workflow happy.
Moreover, layout.transitions may become useful to users not using react for example,
restyle(gd, 'marker.size', 20) would transition if gd.layout.transitions includes transition options for marker.size attributes, which maybe more intuition for some than using frames and Plotly.animate
updatemenus and sliders could specify transitioning updates using restyle w/o having to worry about declaring frames
As @alexcjohnson points, it'll become important to list the attributes that do support animations in the schema so that diffData and diffLayout can pick them out. The current _module.animable isn't enough.
Now, supporting multiple transitions options per graph will be nice, but I'm a little worried about update calls that would trigger multiple transitions. Consider,
here in general marker.size changes can also trigger xaxis.range changes (when autorange is turned on). At the moment, Plotly.animate seems to only transition the axis range (see https://codepen.io/etpinard/pen/ReXaKz)
How often will people really want to specify different transitions for different aspects of the chart? Is this a critical thing to be able to support? My feeling is that most people will want to just set some basic transition options once for everything and leave it at that...
Activity
etpinard commentedon Jul 4, 2017
That's arguably not always the desired behavior. We'll probably need another attribute.
Related:
chriddyp commentedon Jul 5, 2017
If
autosize=true, I think that I'd argue that this is the expected behaviour. If the user wants to restrict the range to a certain view, they can set the range manually.monfera commentedon Jul 5, 2017
I 👍 -d @etpinard's comment b/c sometimes expansion (or shrinking) is not the intent - indeed, keeping the range(s) constant is achievable by explicitly setting the range. I was thinking of a boolean
stickinessattribute (true: retain the initial autodomain vs. false: expand/shrink as needed) but it may be unnecessary complication as the range can be directly set as you suggest. It's likely I didn't have the same thing in mind as @etpinard, just wanted to add my thought.Btw. it might occur more often that data is removed, yet one wants to preserve the original full domain (eg. when eliminating points via crossfiltering), than that data is added, yet one would not want to see them.
Even with crossfiltering point elimination, setting an explicit range would work, though it's slightly more convenient to rely on plotly.js for the initial (full) range calculation than doing it in userland (or crossfiltering) code as it'd just duplicate aggregation (extent calculation) logic.
etpinard commentedon Jul 5, 2017
Right, if a user explicitly set
autosizeto true, I agree. But,autosizedefaults to true when the axis ranges aren't valid. In that case, I suspect keeping the first view when animating is more desirable.alexcjohnson commentedon Jul 5, 2017
Doesn't that mean the rendered plot will be out of sync with the input state? I know we push ranges back to the input state, but
autorange: trueis supposed to mean that it will recalculate the ranges to fit the given data, and indeed if you copy the figure json and make a new plot with it that will happen. Or if you change the data usingrestylefor that matter.Perhaps we need a new
autorangesetting (autorange: 'once'?) that will run autorange once and then revert tofalse(pushed back to the input state), to account for all these desires.nicolaskruchten commentedon Sep 6, 2018
FWIW I would favour having an
animateflag somewhere such that.react()would animate a transition or something like that, rather than a.animate()method.vianamp commentedon Sep 18, 2018
Not sure if my question is quite related to this issue, but I want to avoid the axes to be recalculated when the user deselect a particular trace by clicking on the legend. Is this currently possible? Thanks.
nicolaskruchten commentedon Oct 11, 2018
Would it make sense for this new react-with-animate flag to be in
configorlayout?etpinard commentedon Oct 11, 2018
Here are a few thoughts on the topic:
The premise of making
Plotly.react"call"Plotly.animateis somewhat wrong:Plotly.reactdiffs its input with the current state and executes the required subroutines to reach the state as described in the input. Upon completion, the "old" state is lost.Plotly.animateapplies frames -- which work as data/layout patches, not full descriptions -- to a "base" state. This "base" state can be the "current" state or it can combine abaseframe.For example, given
then
gives a scatter trace with red markers of size 6. Whereas,
gives a scatter trace with red markers but of size 20.
I can't think a use-case for the frames API for
Plotly.reactusers. Frames were added to make animations from layout sliders and updatemenus fully JSON-serializable.Plotly.reactusers most likely have a more imperative set of interactions (please, correct me if I'm wrong!). In other words, they compute their "frames" on-the-fly.In plotly.js lingo, this ticket is about making
Plotly.reacttransition from "old" to "new" data/layout. More technically,Plotly.reactshould callPlots.transitionwhen a transition is desired. I'm proposing initiating transitions viaPlotly.reactwith:which doesn't conflict with the
react(gd, data, layout, config)signature and where thetransitionOptsare:plotly.js/src/plots/animation_attributes.js
Lines 66 to 121 in d340150
The other animation attributes in that same file have no meaning when we don't consider frames (with the exception of
frame.redraw, which I'm thinking of mapping to thetransitionattribute container).So here, we're not adding any config or layout attribute, we're asking
Plotly.reactusers to describe how each update transitions from old to new state.If performance isn't a concern, resolving this issue essentially sums up to adding a version of
Plots.transitionthat replaces "old" with "new" instead of merging "new" with "old".While working on this, I'll
probablyfor sure discover animation bugs/regressions which will slow down progress. But, all in all, I should be able to make a PR for this by the end of October.chriddyp commentedon Oct 11, 2018
Looks good to me @etpinard !
alexcjohnson commentedon Oct 11, 2018
Just trying to figure out how this will work in practice... if the graph is expected to simply receive a new figure and adapt to it, how will the app know when to provide
transitionOptsand when not to? One thing that comes to mind is a declarative spec like "if changes are within this set of attributes, do this transition; if they're in this other set of attributes, do this other transition; anything else, no transition". That could take a form like (inlayoutorfigure):That way if you have an app that has controls like log/linear axes, or changing colors or something, those will just happen with no transition, but when you update the pieces that are expected to transition, they will do so automatically.
We could probably start with
transitionOptsand then add something like this later, but it seems like a declarative version would be a lot easier to manage.Again, ideally we just determine this from the diff, perhaps by marking each schema attribute that does support animation as
animatable: true, and if any attribute has a diff and does not describe itself asanimatable, we need toredraw.nicolaskruchten commentedon Oct 12, 2018
From @alexcjohnson:
Yep, pretty much exactly how I felt when I read that :)
@chriddyp this would be problematic for Dash too right?
etpinard commentedon Oct 31, 2018
Ok, I'm starting to think @alexcjohnson 's
layout.transitionsattribute container as described in #1849 (comment) is what we want here. In similar fashion to plotly/dash-core-components#198, putting new thingslayoutseems to make users of thePlotly.reactworkflow happy.Moreover,
layout.transitionsmay become useful to users not using react for example,restyle(gd, 'marker.size', 20)would transition ifgd.layout.transitionsincludes transition options formarker.sizeattributes, which maybe more intuition for some than using frames andPlotly.animaterestylew/o having to worry about declaring framesAs @alexcjohnson points, it'll become important to list the attributes that do support animations in the schema so that
diffDataanddiffLayoutcan pick them out. The current_module.animableisn't enough.Now, supporting multiple transitions options per graph will be nice, but I'm a little worried about update calls that would trigger multiple transitions. Consider,
here in general
marker.sizechanges can also triggerxaxis.rangechanges (when autorange is turned on). At the moment,Plotly.animateseems to only transition the axis range (see https://codepen.io/etpinard/pen/ReXaKz)nicolaskruchten commentedon Nov 1, 2018
Woot!
How often will people really want to specify different transitions for different aspects of the chart? Is this a critical thing to be able to support? My feeling is that most people will want to just set some basic transition options once for everything and leave it at that...