-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Animating layout and data simultaneously #1687
Comments
One concern I have is the implicit dependence between data and layout through
|
Could that be controlled via an animation config flag so that the default result is consistent and you have the option to augment it with fancier behavior? |
cc @antoinerg |
I would really like to understand more about what's blocking us from currently doing this :) @rreusser the general feeling on the team today is something like "Ricky looked at this for a while and thought it was really hard to do right so we're stuck" but I find this a bit unsatisfying... do you happen to recall anything about what the sticking points were? Were there some pathological edge cases? I'm not sure I understand this:
|
(I've added this to the 1.52.0 milestone not as a "we have to completely implement this in 1.52.0" but during this dev cycle I'd like us to gain clarity on what can/can't work and what would be needed) |
@nicolaskruchten If I recall correctly, there were a couple issues. The first issue was simply state management since plotly generally is eager to tear things down and redraw to ensure consistency. If I recall correctly, I found it very challenging to manage the axes objects and figure out which was current or merely an outdated reference. Regarding the specific details of animation, perhaps negligible is that the intermediate states are tweened but not strictly mathematically valid since it transitions in screen-space rather than in data-space. For example, imagine transitioning the axes and the data at the same time. The axis transition can be accomplished via a transform which would take the data with it (rather than tearing down the plot and redrawing on every frame), but the data is transitioned in screen space. The result might be that the start and end states are correct and things in between are smooth but that the data doesn't track correctly with the axes between the two states. That's not all that undesirable since it looks smooth, but here's where it got very difficult: When you drag the plot, it doesn't redraw things on every mouse event. If I recall correctly, it applies a transform and then when you mouseup it tears down the plot and redraws. This, I think, was the fundamental source of difficulty since the task is state management combined with two animation mechanisms: one mechanism is a requestAnimationFrame loop that updates an ephemeral axis-dragging sort of transform, and the other is browser svg transitions. I could definitely be convinced that perhaps a more promising pathway than the request animation frame loop is to use a matrix with browser transitions to transform the axes. That's a few thoughts based on my recollections, but I'm glad to clarify and discuss further! I really tried to get it working and felt like it was very close, but I never quite was able to overcome the state management concerns and get things working well. |
Thanks @rreusser for the extra context! A concrete example of screen-space vs data-space transitions: Let's say you have a transition that both doubles the size of a bar, and halves the span of the axis it's drawn on. And let's further simplify to say the transition is linear. As a result the bar should end up 4x bigger than it started. If we do the entire transition in screen space, the bar will linearly expand, so halfway through it will be 2.5x its original size. But done in data space, the bar would grow linearly while the axis is zooming linearly, so there would be a quadratic component to the bar's size trajectory. Halfway through, it would be 1.5x * 1.5x = 2.25x its original size. I'm sure there are cases where the distinction is bigger than that; but seems to me in general even if it's not 100% accurate, as long as all the pieces make the same choice we should be fine with screen-space transitions. Bars that are supposed to be connected to each other will be connected to each other throughout the transition; points that are supposed to switch positions will still switch positions, just perhaps not at the same fraction of the overall transition. |
One other thing to consider is log axes, which I think are similarly tolerable in screen space and as long as it's not require that data tracks the axes. And just for the sake of argument, I don't know anything about the particular product requirements here, but I will note that the OpenGL approach is generally to use a model matrix and (of course) completely clear and redraw the entire plot on every frame. I don't know if it's possible or reasonable or feasible, but there is a possibility that this would actually be a comparatively easy thing to accomplish in WebGL as opposed to SVG which prefers the statefulness and persistence which make this difficult. |
Ah, now that I think about it, layout is easy in WebGL, but d3's management of state is what makes data transitions easy. Sigh. |
I don’t really follow this screen/data argument... “during a transition” the state of a vis is never “accurate” in the sense that if on my way from 2 to 3 (data) I always travel “through” 2.5 even though that’s not data per se. |
With respect to dragging, @rreusser are you describing problems that would occur when dragging a plot during a transition? |
Is the screen/data thing just that a “linear” transition only applies separately to the axis and the bar but not their relationship @alexcjohnson ? |
In particular I mean that data is subject to the layout, but SVG transitions mean that data really just transitions in screen space rather than transitioning under the layout, which itself is transitioning. I'm not sure if that's clear yet. Instead, the structure leads to layout and the data transitioning independently to correct start and end states, but they're not truly consistent in intermediate states. In my head, I'm picturing a hierarchy in which data should live under the layout and move accordingly, but instead it's more of a situation where data and layout transition independently, like siblings—to correct states, of course, but still independently.
Edit: Sorry, yes that is a thing and that's why dragging was disabled during transitions, but that's basically the issue. By piggybacking on the same machinery, it's hard to use it for both at the same time, though probably very possible and not fundamentally difficult. |
Ok thanks for the clarifications! I am totally fine with data and layout independently transitioning to their final destinations and their relationships therefore being ambiguous/wrong in between :) I’m also totally fine with disallowing interaction during transitions :) Under those relaxed assumptions, are there any known blockers or pitfalls we should look out for? |
I think the biggest challenges were simply managing the corner cases, async state. Some of that was definitely my shortcomings. I think single thing that caused me the most trouble was axes instances before and after supply defaults since, as mentioned, I had a lot of trouble figuring out which things were current and which were appropriate to hold onto and use at which time asynchronously. The other big thing was accidental double-transforms since it tends to fall into one of these:
I would love to see this all working smoothly, so I'm glad to continue offering thoughts/experiences or just helping work through difficult bits of my code. It was pretty challenging so it's likely there are some bits that might need explaining. Don't hesitate to ask. |
Thanks very much for being willing to help out so long after the fact 🙏 In the case where both get applied and the data gets double-transformed, this is when both layout and data get to their final destinations "in the own way" right? Or do they just not make it to the right place, or take a weird path or...? I'm having trouble concretizing what you're saying into a case of "when I do X I get Y outcome which is bad because of Z" :) Like in a case where I have a scatter plot transition from state A to state B and in state B the markers are meant to be in a place that's not currently in range, is the problem that:
Put another way: are we having trouble doing what other libraries (i.e. Highcharts) do, or are we trying to do something "better" in some way and stuck at that point? |
Sorry this is vague and hand-wavey. I think I'm having trouble remembering the precise state the corner cases ended up in because I eventually had to pull the plug and cut out some feature interactions. The double-transform issue was a case where things did not end up in the right state and were unacceptably buggy. To clarify, what I wanted was:
To help explain the double-transition issue, consider the following:
If you combine the strategies, you have to be very careful. If you truly handle data and layout transitions as completely separate, then perhaps you actually want to animate the data from Or maybe you animate from If I'm being honest, I think the challenge is perhaps path-dependent development where piggybacking on the machinery already in place for static traces and mouse interactions was performant but not designed from the ground up to minimize feature interactions. If I recall correctly, I think my requestAnimationFrame loop for animating layout does two things: it animates the axes and also applies a transform to the data. Perhaps it shouldn't apply that transform to the data at all in order to move it with the axes and it should instead replot all the traces with d3 transitions in order to trigger SVG transitions and get things into their correct state. As mentioned, then downside is that the data won't track the axes perfectly along the way, though it's probably much less complicated. I believe this is what highcharts does. |
To try to boil that down more, transforming traces with a transform matrix gets them to follow the axes perfectly, performantly, and correctly. But if you transition the data along the way, you have to consider that you're transitioning data in the coordinate system of where things started since the axis transition is applied at the end. So in that sense, the first option is that you could transition the data all relative to the original axes, then separate the changing axes into a matrix handled at the end. Restating this, it doesn't seem as bad? This option, written as functions:
The other option (the highcharts option, possibly?) is not to apply that matrix to the trace to get it to track the axes correctly but to instead to transition from the first state in screen-space to the final state, also in screen-space. This option, written as functions:
I think I had double transforms because I had trouble tracking the axis objects and combined the two incorrectly:
|
Ahh, I see. I feel like doing things in the "possibly highcharts" option is conceptually easier and nice to look at, if less theoretically pure, let's say. Doing things in two disjoint transition steps would be better than the status quo, which is "transition one, snap the other, choose the order", but clearly having some way of simultaneously transitioning them would be nice. |
@alexcjohnson I'm having trouble understanding the test case you're outlining with "Let's say you have a transition that both doubles the size of a bar, and halves the span of the axis it's drawn on." Is this like transitioning from:
In screen coordinates, linearly growing the bar to its destination means that halfway through the transition, the bar is at 75px, and the y_range is now mapping [0-3], meaning if you froze the frame and read off the bar-height it would be at 3*75/120=1.875 "incorrect data units" (it should be 1.5!) If you did this in data coordinates, linearly growing the bar to its destination would mean that halfway through, it would be at 1.5 data-units, and therefore its height would be halfway along the axis which now maps [0-3] to 120px, so it should be at 60px. Is this what you're saying? If so, should we really consider this a blocker? |
As I said:
not a blocker |
OK cool. I couldn't replicate your "2.25" from above so I was just checking to make sure I wasn't subtly misunderstanding. So given that, is it just a matter of "doing both the things we currently do simultaneously" and therefore is an easy lift to start or... ? |
PS I’m looking forward to being one step closer to Plotly.js being able to make bar chart races :P https://flourish.studio/2019/03/21/bar-chart-race/ |
For @archmoj , a good way to "get into" the transition/animation code would be to look at some recent PRs and the
|
(Other then that, I don't have much to add on top of what @rreusser has written down) |
This issue has been tagged with A community PR for this feature would certainly be welcome, but our experience is deeper features like this are difficult to complete without the Plotly maintainers leading the effort. Sponsorship range: $30k-$35k What Sponsorship includes:
Please include the link to this issue when contacting us to discuss. |
@nicolaskruchten, do you know if it is already possible to create bar chart race and export to video? |
Hi - this issue has been sitting for a while, so as part of our effort to tidy up our public repositories I'm going to close it. If it's still a concern, we'd be grateful if you could open a new issue (with a short reproducible example if appropriate) so that we can add it to our stack. Cheers - @gvwilson |
One feature missing in animation is simultaneous data and layout transitions. The docs note that this is explicitly disabled. Closely related is that I'm not actually aware of a library that gets things strictly correct. Highcharts, for example, will transition both simultaneously, but in screen space, not data space. That means the data doesn't match the axis as it transitions. On top of that, to get this working correctly, instantaneous updates and finite transitions and ui interactions basically all have to be carefully coordinated to get this right.
Opening this issue to collect examples and track progress on improving the robustness.
The text was updated successfully, but these errors were encountered: