# Dash animated 3D graph with callback to change animation frames

**URL:** <https://community.plotly.com/t/dash-animated-3d-graph-with-callback-to-change-animation-frames/46939>\
**Category:** Dash Python\
**Created:** [November 7, 2020, 3:19pm UTC](https://community.plotly.com/t/dash-animated-3d-graph-with-callback-to-change-animation-frames/46939 "2020-11-07T15:19:43Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![steefm](https://avatars.discourse-cdn.com/v4/letter/s/ccd318/32.png) [@steefm](https://community.plotly.com/u/steefm)\
**Post date:** [November 7, 2020, 3:19pm UTC](https://community.plotly.com/t/dash-animated-3d-graph-with-callback-to-change-animation-frames/46939/1 "2020-11-07T15:19:43Z")

</div>

Hi there,

I’m looking for some help in building a callback in Dash that updates the frames of an animation.

Specifically, my main figure is a plotly Mesh3d, which has a mesh of a cube, and an animation that makes the cube move: The x,y,z coordinates of the verts change per frame. Now, I’d like users to be able to change the animation of _motion_ to an animation of _color change_, via a dropdown menu.

However, I’m having trouble updating the figure in a callback: The callback unexpectedly leads the animation slider to influence the _camera_ angle.

## Attempt 1

First, I tried simply updating the `data`, `frames`, and `layout` keys by returning these in a `dict` (sorry MWE is a bit long..):

```auto
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import numpy as np

def make_frames(x, y, z, i, j, k, 
                animation='motion', n_frames=10):
    frames = []
    for frame_n in np.arange(n_frames, dtype='int'):
        if animation == 'motion':
            data = go.Mesh3d(x=x+frame_n*.1, y=y+frame_n*.1, z=z, i=i, j=j, k=k, color='rgb(0.5,0.5,0.5)')
        elif animation == 'color':
            data = go.Mesh3d(x=x, y=y, z=z, i=i, j=j, k=k, color='rgb('+str(frame_n*.1)+',0,0)')
            
        frames.append(go.Frame(data=data, name=str(frame_n)))
    return frames

def build_sliders():
    # slider for animation
    sliders_dict = {
        "active": 0,
        "yanchor": "top",
        "xanchor": "left",
        "currentvalue": {
            "font": {"size": 20},
            "prefix": "Step:",
            "visible": True,
            "xanchor": "right"
        },
        "transition": {"duration": 300, "easing": "cubic-in-out"},
        "pad": {"b": 10, "t": 50},
        "len": 0.9,
        "x": 0.1,
        "y": 0,
        "steps": []
    }

    # add frames
    frames = make_frames(x,y,z,i,j,k, animation='motion')
    n_frames = 10
    for frame_n in np.arange(n_frames, dtype='int'):
        slider_step = {"args": [
            [int(frame_n)],
            {"frame": {"duration": 300, "redraw": True},
             "mode": "immediate",
             "transition": {"duration": 300}}
            ],
            "label": int(frame_n),
            "method": "animate"}
        sliders_dict["steps"].append(slider_step)
    return sliders_dict

# verts, faces for cube
x = [0, 0, 1, 1, 0, 0, 1, 1]
y = [0, 1, 1, 0, 0, 1, 1, 0]
z = [0, 0, 0, 0, 1, 1, 1, 1]
i = [7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2]
j = [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3]
k = [0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6]

# Make figure
fig = go.Figure(data=[
    go.Mesh3d(
        # 8 vertices of a cube
        x=x,
        y=y,
        z=z,
        color='rgb(0.5,0.5,0.5)',
        # i, j and k give the vertices of triangles
        i=i,
        j=j,
        k=k,
        name='y'
    )
])

# add frames, slider, update layout
frames = make_frames(x,y,z,i,j,k, animation='motion')
sliders_dict = build_sliders()    
fig['layout']['scene'] = {'aspectmode': 'cube',
                            'xaxis': {'range': [-3, 3], 'title': 'x', 'gridcolor': '#FFFFFF'},
                            'yaxis': {'range': [-3, 3], 'title': 'y', 'gridcolor': '#FFFFFF'},
                            'zaxis': {'range': [-3, 3], 'title': 'z', 'gridcolor': '#FFFFFF'}}
fig['layout']['sliders'] = [sliders_dict]
fig['frames'] = frames

# Create app
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash( __name__ , external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    html.H1(children='MWE', style={'textAlign': 'center'}),
    html.Div([ # Div containing everything
        html.Div([ # div for mesh: Left
            html.Div([
                dcc.Dropdown(id='animation_type', 
                             options=[{'label': i, 'value': i} for i in ['motion', 'color']],
                             value='motion')
            ]),
            html.Div([
                dcc.Graph(
                    id='mesh_fig',
                    figure=fig)
            ])
        ])
    ])
])

# Callback
@app.callback(
    dash.dependencies.Output('mesh_fig', 'figure'),
    [dash.dependencies.Input('animation_type', 'value')])
def update_figure(animation_type):
    if animation_type == 'motion':
        data = go.Mesh3d(x=x, y=y, z=z,
                         color='rgb(0.5,0.5,0.5)', i=i, j=j, k=k, name='y')
    else:
        data = go.Mesh3d(x=x, y=y, z=z,
                         color='rgb(0,0,0)', i=i, j=j, k=k, name='y')
    frames = make_frames(x,y,z,i,j,k,animation=animation_type)
    layout_dict = {'scene': {'aspectmode': 'cube',
                             'xaxis': {'range': [-3, 3], 'title': 'x', 'gridcolor': '#FFFFFF'},
                             'yaxis': {'range': [-3, 3], 'title': 'y', 'gridcolor': '#FFFFFF'},
                             'zaxis': {'range': [-3, 3], 'title': 'z', 'gridcolor': '#FFFFFF'}},
                   'sliders': [build_sliders()]}
    return {'data': [data],
            'frames': frames,
            'layout': layout_dict}

if __name__ == ' __main__':
    app.run_server(debug=True, host="localhost", port=8050)

```

At first, this seems to work as intended: After selecting `color` from the dropdown menu, the animation slider indeed changes the color (instead of the location) of the cube. However, there’s an annoying side effect: _after_ selecting `color` from the dropdown menu, using the animation slider always resets the camera to the default. Hence, you can’t change the angle with which you want to view the animation.  
(NB: refreshing the browser window actually ‘solves’ this: if you change the dropdown to `color`, and then refresh the window, you can actually use the animation slider to change color without changing the camera angle. Curious, eh?)

## Attempt 2

I also tried to use the `animate` property of `dcc.Graph` as follows:

```auto
            html.Div([
                dcc.Graph(
                    id='mesh_fig',
                    figure=fig,
                    animate=True,
                    animation_options={'frame': { 'redraw': True}})
            ])

```

Which does appear to solve the problem of Attempt 1 (i.e., the camera angle isn’t affected by the animation slider after changing the dropdown menu’s `motion` to `color`); but unfortunately, this introduces a new problem, namely that the `data` seem to change due to the callback, but not the `frames`. That is, after selecting `color` from the dropdown menu, the animation slider still animates motion (and also reverts the cube’s color to grey). (I know animate is in beta, so I’m guessing this may just not be implemented yet here).

Does anyone know if I’m doing something wrong? Or, if this (especially Attempt 1) is a bug, is there any workaround? (I’m thinking, since the browser window refresh actually makes the figure work as intended again, is there any way to trigger a “refresh” of the div containing the figure or something?

Thanks in advance!

---

<div class="post-metadata">

**Author:** ![steefm](https://avatars.discourse-cdn.com/v4/letter/s/ccd318/32.png) [@steefm](https://community.plotly.com/u/steefm)\
**Post date:** [December 1, 2020, 10:48am UTC](https://community.plotly.com/t/dash-animated-3d-graph-with-callback-to-change-animation-frames/46939/2 "2020-12-01T10:48:54Z")

</div>

I’m still hoping someone can help out / point me in the right direction. Just for clarity, I created a gif of the problem, maybe it helps understanding what’s going on. Once switched to “color”, the animation works, but any change of the slider automatically resets the camera angle.

 ![dash_mwe](https://us1.discourse-cdn.com/flex024/uploads/plot/original/2X/6/65b16bb7c57c9459bf6ff41969547645c16f8657.gif)

For now I created a workaround by substituting the dcc.Graph element with an html.Iframe, and then using a callback to load a new plotly 3dMesh html (pre-written). It solves the problem above but it’s not a great solution since it also makes me lose the ability to do any other callback/access the clickData from the 3D mesh.

---

<div class="post-metadata">

**Author:** ![steefm](https://avatars.discourse-cdn.com/v4/letter/s/ccd318/32.png) [@steefm](https://community.plotly.com/u/steefm)\
**Post date:** [December 10, 2020, 5:01pm UTC](https://community.plotly.com/t/dash-animated-3d-graph-with-callback-to-change-animation-frames/46939/3 "2020-12-10T17:01:53Z")

</div>

For future reference, I found a better workaround that doesn’t require iframe. The issue appears to arise due to the dcc.Graph component having the same `id` before and after the callback. After updating the `dcc.Graph` using the dropdown menu, the gl3d scene doesn’t seem to save the user-specified camera’s state properly anymore. I think it doesn’t detect/store that the cameradata has changed upon user interaction (but I lack proper JS/react knowledge to dive deeper into this).

Anyway, if you have the callback update the children of the parent div of `dcc.Graph`, and change the id of dcc.Graph itself, the result works normally, e.g.:

```auto
app.layout = html.Div(children=[
    html.H1(children='MWE', style={'textAlign': 'center'}),
    html.Div([ # Div containing everything
        html.Div([ # div for mesh: Left
            html.Div([
                dcc.Dropdown(id='animation_type', 
                             options=[{'label': i, 'value': i} for i in ['motion', 'color']],
                             value='motion')
            ]),
            html.Div(id='parent_div', children=[
                dcc.Graph(
                    id='mesh_fig',
                    figure=fig)
            ])
        ])
    ])
])
@app.callback(
    dash.dependencies.Output('parent_div', 'children'),
    [dash.dependencies.Input('animation_type', 'value')])
def update_figure(animation_type):
    if animation_type == 'motion':
        data = go.Mesh3d(x=x, y=y, z=z,
                         color='rgb(0.5,0.5,0.5)', i=i, j=j, k=k, name='y')
    else:
        data = go.Mesh3d(x=x, y=y, z=z,
                         color='rgb(0,0,0)', i=i, j=j, k=k, name='y')
    frames = make_frames(x,y,z,i,j,k,animation=animation_type)
    layout_dict = {'scene': {'aspectmode': 'cube',
                             'xaxis': {'range': [-3, 3], 'title': 'x', 'gridcolor': '#FFFFFF'},
                             'yaxis': {'range': [-3, 3], 'title': 'y', 'gridcolor': '#FFFFFF'},
                             'zaxis': {'range': [-3, 3], 'title': 'z', 'gridcolor': '#FFFFFF'}},
                   'sliders': [build_sliders()]}
    go.Figure(data=[data], layout=layout_dict, frames=frames)

    return [dcc.Graph(figure=fig, id='new_id_name')]

```

This still limits the ability to interact with the `dcc.Graph` itself (eg get clickData or something), so it’s not perfect, but it seems to serve my purposes.

---

<div class="post-metadata">

**Author:** ![d0v0b](https://avatars.discourse-cdn.com/v4/letter/d/7feea3/32.png) [@d0v0b](https://community.plotly.com/u/d0v0b)\
**Post date:** [January 24, 2023, 11:24pm UTC](https://community.plotly.com/t/dash-animated-3d-graph-with-callback-to-change-animation-frames/46939/4 "2023-01-24T23:24:28Z")

</div>

Hi @steefm , I encounter the exact same issue. I tried your work-around by running the codes you provided, but the issue still exist. Could you elaborate more on how you get it resolved please?
