Skip to content

Animation deletes traces #3753

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

Open
robert-lieck opened this issue Jun 2, 2022 · 9 comments
Open

Animation deletes traces #3753

robert-lieck opened this issue Jun 2, 2022 · 9 comments
Labels
bug something broken P3 backlog

Comments

@robert-lieck
Copy link

Below is a minimal example, where using a slider to show different traces will delete another trace.

The example plots a grid of points and uses a slider to interactively display selected rows, however, the original grid plot disappears as soon as the slider is moved.

This looks like a bug to me. If it’s not, how can I prevent that trace from disappearing?

import numpy as np
import plotly.graph_objects as go
# generate grid
N = 10
x, y = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N))
z = np.zeros((N, N))
# plot
fig = go.Figure(
    # all points (THIS DISAPPEARS!)
    data=[go.Scatter3d(x=x.flatten(), y=y.flatten(), z=z.flatten())],
    # single rows animated via slider
    frames=[go.Frame(data=[go.Scatter3d(x=x[i], y=y[i], z=z[i])], name=f"{i}") for i in range(N)],
    layout_sliders=[dict(steps=[dict(method='animate', args=[[f"{k}"]], label=f"{k}") for k in range(N)])])
fig.write_html('plot.html')
fig.show()

Two other weird things (potential bugs), which might be unrelated:

  • Setting a marker size on the first trace also changes the marker size of the animated traces. Should that be the case?!
  • The interactively opened plot behaves differently from opening the saved html plot (the second one starts playing the animation right away, the first one doesn’t).

I have also posted this in the community forum but without any reactions so far.

@empet
Copy link

empet commented Jun 2, 2022

  • If your frame does not change the marker size, then it inherits the marker size from your fig.data[0].
    To get markers of size different from that in fig.data[0] you should define a frame like this:
go.Frame(go.Scatter3d(x=x[i], y=y[i], z=z[i], marker_size=2)

With a slight modification your animation works.
Namely, I added a dummy trace to keep the scene fixed. Otherwise it moves during the animation and your markers are not visible in the new scene.
I post here two versions: the first one, that displays a single row of markers in each frame, like in your code, and the second one keeps the rows already added:

1.

import numpy as np
import plotly.graph_objects as go
# generate grid
N = 10
x, y = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N))
z = np.zeros((N, N))
# plot
fig = go.Figure(
    
    data=[go.Scatter3d(x=[x.min(),x.max()], y=[y.min(), y.max()], z=[z.min(), z.max()], mode="markers",
                       marker_size=0.05, marker_color="white", showlegend=False),
          go.Scatter3d(x=x.flatten(), y=y.flatten(), z=z.flatten())],
    # single rows animated via slider
    frames=[go.Frame(data=[go.Scatter3d(x=x[i], y=y[i], z=z[i])], 
                     name=i,
                     traces=[1])  for i in range(N)], #traces=[1] tells plotly.js that each frame updates fig.data[1]
    layout_sliders=[dict(steps=[dict(method='animate', args=[[i]], label=f"{i}") for i in range(N)])])
fig.show()

2.

import numpy as np
import plotly.graph_objects as go
# generate grid
N = 10
x, y = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N))
z = np.zeros((N, N))
# plot
fig = go.Figure(
    data=[go.Scatter3d(x=[x.min(),x.max()], y=[y.min(), y.max()], z=[z.min(), z.max()], mode="markers",
                       marker_size=0.05, marker_color="white", showlegend=False),
          go.Scatter3d(x=x.flatten(), y=y.flatten(), z=z.flatten())],
    # single rows animated via slider
    frames=[go.Frame(data=[go.Scatter3d(x=x[:i, :].flatten(), y=y[:i, :].flatten(), z=z[:i, :].flatten())], 
                     name=i,
                     traces=[1])  for i in range(N)],
    layout_sliders=[dict(steps=[dict(method='animate', args=[[i]], label=f"{i}") for i in range(N)])])
fig.show()

@robert-lieck
Copy link
Author

Thanks @empet, this is really helpful!

So the behaviour for the marker size is probably intended. I wasn't aware of that inheritance, but it makes sense.

Adding auto_play=False is a good fix for the second minor issue, even though I would argue that the default behaviour should be the same in both cases, but that is definitely a minor issue.

Concerning the main issue of vanishing traces, your suggestion works, but it has to be slightly extended in more complex cases, where the animation involves multiple traces. Essentially, it seems that each animated trace "eats" one existing trace, starting at the beginning. So if you have n animated traces, you have to add n dummy traces before adding any other traces.

Still a bug, but with an acceptable workaround for the moment 🙂 Thanks again!

@empet
Copy link

empet commented Jun 2, 2022

No, if you have n traces, you have to add just one dummy trace, defined exactly I defined it in your case, but the drawback is that you must know the min and max coordinates among all points to be plotted in those n traces.

@nicolaskruchten
Copy link
Contributor

The current behaviour is annoying, but is basically as designed and documented in the "current limitations and caveats" here https://plotly.com/python/animations/#current-animation-limitations-and-caveats ... Re-reading this now I should probably add a bullet explaining more explicitly that you need "the same trace" in each frame, even if some of them are "dummy" traces.

@robert-lieck
Copy link
Author

@empet my issue was not the moving scene, but the disappearing traces, which is still happening in your example. But you brought up the idea of adding dummy traces. So here is my solution to the original problem (extended to animations with multiple traces):

import numpy as np
import plotly.graph_objects as go
# generate grid
N = 10
x, y = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N))
z = np.zeros((N, N))
# plot
fig = go.Figure(
    data=[
        # dummy trace 1
        go.Scatter3d(x=[], y=[], z=[], showlegend=False),
        # dummy trace 2
        go.Scatter3d(x=[], y=[], z=[], showlegend=False),
        # real trace 1
        go.Scatter3d(x=x.flatten(), y=y.flatten(), z=z.flatten(),
                     mode="markers", marker=dict(size=4, color="rgb(0,200,0)"), name="Trace 1"),
        # real trace 2
        go.Scatter3d(x=x.flatten(), y=y.flatten(), z=z.flatten() + 1,
                     mode="markers", marker=dict(size=4, color="rgb(0,0,200)"), name="Trace 2")
    ],
    # animation with multiple traces in each frame, which will "eat" the dummy traces
    frames=[go.Frame(data=[go.Scatter3d(x=x[i], y=y[i], z=z[i] + j,
                                        mode="markers", marker=dict(size=6, color="rgb(200,0,0)")) for j in [0, 1]],
                     name=f"{i}") for i in range(N)],
    layout_sliders=[dict(steps=[dict(method='animate', args=[[f"{k}"]], label=f"{k}") for k in range(N)])])
fig.show()

Important: You need as many dummy traces as you have traces in the frames, otherwise, the real traces will be eaten.

@robert-lieck
Copy link
Author

@nicolaskruchten thanks for your comment. I am not familiar with the internals, but the frame traces seem to "hijack" the other traces (e.g. they take their place in the legend, as can be seen when omitting showlegend=False above, etc.). So far, adding a sufficient number of dummy traces looks like a fairly robust patch, which also works with legend groups.

@empet
Copy link

empet commented Jun 2, 2022

@robert-lieck When I ran your initial code no marker was displayed from a slider step to another. I corrected that issue,
and with my code each frame displays the added row.They do not disappear, as you confirmed in the first answer after
my post. It wasn't quite clear from your post what are your expectations,
i.e. to keep the initial markers within the plane z=0 and just recolor each line. No other marker_color has been set in your go.Frame definition. You changed the marker color in each row only after I
stressed that each frame inherits the properties of the base figure. But it's important that now you get the right animation.

@empet
Copy link

empet commented Jun 3, 2022

@nicolaskruchten
The same trick, in the case of a disapearing trace, has been pointed out in #2423.
Then, such a case was inserted in a wishlist. Although no special example has been created since, now I realised that my first plotly animation has been exactly of this kind. It's this example https://plotly.com/python/animations/#moving-point-on-a-curve. The idea of inserting the base trace twice was suggested to me by Ricky Reusser. @robert-lieck's example illustrates that it is sufficiently to add an empty trace.

As a conclusion, when a frame does not update a property for all points in a scatter plot, but only for a subset of them, we must add in fig.data an empty trace of the same type as the base trace, to prevent disappearance of the points in the base trace during the animation.

@robert-lieck
Copy link
Author

robert-lieck commented Jun 3, 2022

@empet Ah, interesting, so my initial example shows a different behaviour on your machine and on mine (on mine the grid disappears, but the frames display correctly – albeit with moving scene), good to know. Yes, your comments were very helpful and solved all issues except for the disappearing traces. Thanks for that!

Also #2423 indeed seems to be about the same issue. @nicolaskruchten your comment there about frames replacing other traces is also helpful. Maybe a bit more context and the minimal examples / workarounds from these two issues could be added to the documentation for clarification.

@gvwilson gvwilson self-assigned this Jul 4, 2024
@gvwilson gvwilson removed their assignment Aug 2, 2024
@gvwilson gvwilson added P3 backlog bug something broken labels Aug 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken P3 backlog
Projects
None yet
Development

No branches or pull requests

4 participants