How to merge Isosurface and Scatter3d in plotly frame?

Using frames, I create an animation of the solar system with impending photon clusters. There was a problem when capturing objects on frames.

I create frames as a list (plotly.graph_objects.Frame), passing two objects as the data parameter: Scatter3d and Isosurface. The problem is that for each frame, only the first transferred object is displayed in the data field of the Frame object.

frame = Frame(name='some name', data=[Scatter3d(...), Isosurface(...)], traces=[0])

In this case, only the Scatter3d object will be displayed on the frame. If you swap data objects, then Isosurface will be displayed, without Scatter3d.

Also, it turned out to add Isosurface separately from frames via fig.add_surface, but this does not suit me, since each such object changes in time.

Can you suggest which way to look to solve the problem? If you need more information, I will provide. Thank you in advance.

Hi @spataphore welcome to the forums. I created a small sample figure based on the examples here:

import plotly.graph_objects as go
import numpy as np

colors = ['aggrnyl', 'agsunset', 'algae', 'amp', 'armyrose', 'balance',
             'blackbody', 'bluered', 'blues', 'blugrn', 'bluyl', 'brbg',
             'brwnyl', 'bugn', 'bupu', 'burg', 'burgyl', 'cividis', 'curl',
             'darkmint', 'deep', 'delta', 'dense', 'earth', 'edge', 'electric',
             'emrld', 'fall', 'geyser', 'gnbu', 'gray', 'greens', 'greys',
             'haline', 'hot', 'hsv', 'ice', 'icefire', 'inferno', 'jet',
             'magenta', 'magma', 'matter', 'mint', 'mrybm', 'mygbm', 'oranges',
             'orrd', 'oryel', 'oxy', 'peach', 'phase', 'picnic', 'pinkyl',
             'piyg', 'plasma', 'plotly3', 'portland', 'prgn', 'pubu', 'pubugn',
             'puor', 'purd', 'purp', 'purples', 'purpor', 'rainbow', 'rdbu',
             'rdgy', 'rdpu', 'rdylbu', 'rdylgn', 'redor', 'reds', 'solar',
             'spectral', 'speed', 'sunset', 'sunsetdark', 'teal', 'tealgrn',
             'tealrose', 'tempo', 'temps', 'thermal', 'tropic', 'turbid',
             'turbo', 'twilight', 'viridis', 'ylgn', 'ylgnbu', 'ylorbr',
             'ylorrd']
 
scatter = [
    go.Scatter3d(
        x=np.random.randint(1,3,4), 
        y=np.random.randint(1,3,4), 
        z=np.random.randint(1,3,4), 
        mode='markers'
    ) for _ in colors
]

surface = [
    go.Isosurface(
        x=[0,0,0,0,1,1,1,1],
        y=[1,0,1,0,1,0,1,0],
        z=[1,1,0,0,1,1,0,0],
        value=[1,2,3,4,5,6,7,8],
        isomin=2,
        isomax=6,
        colorscale=cs
    ) for cs in colors
]

# create frames, beginning at index 1
frames = [go.Frame(data=[s, i]) for s, i in zip(scatter[1:], surface[1:])]

# create figure, use first traces for initial plot
fig = go.Figure(
    data=[scatter[0], surface[0]],
    layout=go.Layout(
        title="Start Title",
        updatemenus=[
            dict(
                type="buttons",
                buttons=[
                    dict(
                        label="Play",
                        method="animate",
                        args=[None]
                    )
                ]
            )
        ],
    ),
    frames=frames
)
fig.show()

After pressing the play button, you should see the Isosurface changing it’s colorscale and the scatter points changing their position randomly.

I hope this helps!

1 Like

@spataphore @AIMPED The solution given by @AIMPED is a workaround, but not a typical way to update two or more traces in each frame.

import plotly.graph_objects as go
import numpy as np

colorscales = ['algae', 'balance',
                'deep', 'delta', 'dense',
              'ice', 'icefire', 'inferno', 'turbo', 'plasma']
             
 
fig = go.Figure([go.Scatter3d(
        x=np.random.randint(1,3,4), 
        y=np.random.randint(1,3,4), 
        z=np.random.randint(1,3,4), 
        mode='markers',
        marker_color=[0.5, 1, 0.75, 1.25],
       marker_colorscale="haline"
    ), go.Isosurface(
        x=[0,0,0,0,1,1,1,1],
        y=[1,0,1,0,1,0,1,0],
        z=[1,1,0,0,1,1,0,0],
        value=[1,2,3,4,5,6,7,8],
        isomin=2,
        isomax=6,
        colorscale="haline"
    )
])

# Each frame updates some attributes from scatter3d trace and other attributes from isosurface
# in this case it updates marker_colorscale, respectively isosurface colorscale
frames = [go.Frame(data=[go.Scatter3d(marker_colorscale=colorscales[k]), 
                         go.Isosurface(colorscale=colorscales[k])],
                   traces=[0,1]) for k in range(len(colorscales))]

fig.update(frames=frames)
fig.update_layout( width=600, height=600,
                  scene_camera_eye=dict(x=1.5, y=1.5, z=1),
        title="Your title",
        updatemenus=[
            dict(
                type="buttons",
                buttons=[
                    dict(
                        label="Play",
                        method="animate",
                        args=[None]
                    )
                ]
            )
        ],
    )

Note that in each frame one updates only attributes from the first and the second trace that are changimg from frame to frame.

trace=[0,1]

in a frame definition tells to plotly.js that the first update from
a frame is for the fig.data[0], while the second update is for fig.data[1].

1 Like

I almost never use animations, this was interesting. Thanks for the insight @empet

1 Like

A search with the keyword β€œanim” revealed the following examples:

Multiple lines, subplots and frames - πŸ“Š Plotly Python - Plotly Community Forum
Controlling animation speed using graph_objects in python
Subplots with Geo Rotation - πŸ“Š Plotly Python - Plotly Community Forum
b'Animating a single subplot from subplots | empet | Plotly'
but there are also 3d animations.

1 Like