Open robert-lieck opened 2 years ago
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()
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 :slightly_smiling_face: Thanks again!
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.
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.
@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.
@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.
@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.
@nicolaskruchten The same trick, in the case of a disapearing trace, has been pointed out in https://github.com/plotly/plotly.py/issues/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.
@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.
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?
Two other weird things (potential bugs), which might be unrelated:
I have also posted this in the community forum but without any reactions so far.