Rendering the data with memoization

Hi,

I’m trying to render a live point cloud & trajectory and I’m currently using global variables to do this. However, this isn’t ideal, but I can’t figure out the proper gluing logic such that I can free myself of them. Below is a dummy script, and I marked with comments my questions.

app = dash.Dash(__name__)
app.layout = html.Div(
    [
        dash.dcc.Graph(id='live-graph'),
        dash.dcc.Interval(id='time', interval=1000),
    ],
        style={"display": "inline-block"}
)


path_history = [[], [], []]
map_history  = [[], [], []]
@app.callback(Output('live-graph', 'figure'),
          [
              Input('time', 'n_intervals')
    ]
)
def update(t):
    global path_history, map_history
    if t is None:
        return dash.no_update
    x, y, z  = P[t % len(P)]
    x_map, y_map, z_map = zip(*M[t*step:(t+1)*step])
    path_history[0].append(x) # <-- the same as below
    path_history[1].append(y)
    path_history[2].append(-z)
    map_history[0].extend(x_map) # <-- this is very expensive. if I have a huge point cloud, it's getting laggy; how can I keep the values that I currently plotted on during the next update? 
    map_history[1].extend(y_map)
    map_history[2].extend(z_map)
    fig = go.Figure(data={'data':
                  [
                      go.Scatter3d(x=path_history[0],
                                   y=path_history[1],
                                   z=path_history[2],
                                   mode='lines+markers',
                                   name='path',
                                   marker=dict(size=2, color='red')),

                      go.Scatter3d(x=map_history[0],
                                   y=map_history[1],
                                   z=map_history[2],
                                   mode='markers',
                                   name='map',
                                   marker=dict(size=1, color='navy', symbol='diamond'))
            ]
        }
    )
    fig.update_layout(
                      uirevision='constant',
                      width=1500,
                      height=1500,
                      scene=dict(xaxis=dict(range=[-10, 10]), yaxis=dict(range=[-10, 10]), zaxis=dict(range=[0, 3]))
    )
    return fig


if __name__ == '__main__':
    port = 8888
    M, P = load_map_and_path()
    step = len(M) // len(P)
    camera = dict(eye=dict(x=0., y=2.5, z=0.))
    app.run_server(debug=True, port=port)

Thanks!

Hi,

Welcome to the community! :slight_smile:

There are two things that can speed your callback up significantly:

  1. You can add the “figure” prop as State in the callback and manipulate it directly as a dictionary, instead of calling go.Figure on each callback. It might require you to get familiar with the figure object structure, but it is a quite useful in Plotly to do so.

  2. dcc.Graph has also a prop called extendData, which can be used to add new points to traces very efficiently (should be faster than the first approach). There are a few examples around the forum on how to make it work in scatter plots, and it should be very similar to your case.

I can certainly find two small examples of each case in my laptop if needed. Please follow up if you have additional question, I will be happy to assist!

Hi,

Thanks a lot for your answer.

  1. Do you suggest to move the go.Scatter3d outside the function? Is this achieved as passing an empty figure inside the dcc.Graph in the app.layout?
  2. I don’t think I understood the logic; will the update return only the extendedData? does the update need the additional State('graph', 'figure')?

Some brief examples would be very helpful.

Thanks again for your time

Managed to make it work:

layout = dict(uirevision='constant',
              scene_camera=dict(eye=dict(x=0., y=2.5, z=0.)),
              width=1000,
              height=1000,
              margin=dict(l=20, r=0, t=20, b=20),
              scene=dict(xaxis=dict(range=[-10, 10]), yaxis=dict(range=[-10, 10]), zaxis=dict(range=[0, 2])))

figure = dict(data=[{'type': 'scatter3d', 'x': [], 'y': [], 'z': []}], layout=layout)

app = dash.Dash(__name__)
app.layout = html.Div([dcc.Graph(id='graph', figure=figure),
                       dcc.Interval(id='time', interval=100)])


@app.callback(Output('graph', 'extendData'),
              [Input('time', 'n_intervals')])
def update(t):
    if t is None:
        return dash.no_update
    x, y, z  = P[t % len(P)]
    return dict(x=[[x]], y=[[y]], z=[[-z]])


if __name__ == '__main__':
    port = 8888
    M, P = load_map_and_path()
    step = len(M) // len(P)
    app.run_server(debug=True, port=port)


1 Like

Nicely done! I marked as a solution for you.