Search code examples
pythonplotlyplotly-dashplotly-python

What's the fastest way to update a scatterplot?


I have a dashboard that is very similar to this-

import datetime

import dash
from dash import dcc, html
import plotly
from dash.dependencies import Input, Output

# pip install pyorbital
from pyorbital.orbital import Orbital
satellite = Orbital('TERRA')

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(
    html.Div([
        html.H4('TERRA Satellite Live Feed'),
        html.Div(id='live-update-text'),
        dcc.Graph(id='live-update-graph'),
        dcc.Interval(
            id='interval-component',
            interval=1*1000, # in milliseconds
            n_intervals=0
        )
    ])
)


@app.callback(Output('live-update-text', 'children'),
              Input('interval-component', 'n_intervals'))
def update_metrics(n):
    lon, lat, alt = satellite.get_lonlatalt(datetime.datetime.now())
    style = {'padding': '5px', 'fontSize': '16px'}
    return [
        html.Span('Longitude: {0:.2f}'.format(lon), style=style),
        html.Span('Latitude: {0:.2f}'.format(lat), style=style),
        html.Span('Altitude: {0:0.2f}'.format(alt), style=style)
    ]


# Multiple components can update everytime interval gets fired.
@app.callback(Output('live-update-graph', 'figure'),
              Input('interval-component', 'n_intervals'))
def update_graph_live(n):
    satellite = Orbital('TERRA')
    data = {
        'time': [],
        'Latitude': [],
        'Longitude': [],
        'Altitude': []
    }

    # Collect some data
    for i in range(180):
        time = datetime.datetime.now() - datetime.timedelta(seconds=i*20)
        lon, lat, alt = satellite.get_lonlatalt(
            time
        )
        data['Longitude'].append(lon)
        data['Latitude'].append(lat)
        data['Altitude'].append(alt)
        data['time'].append(time)

    # Create the graph with subplots
    fig = plotly.tools.make_subplots(rows=2, cols=1, vertical_spacing=0.2)
    fig['layout']['margin'] = {
        'l': 30, 'r': 10, 'b': 30, 't': 10
    }
    fig['layout']['legend'] = {'x': 0, 'y': 1, 'xanchor': 'left'}

    fig.append_trace({
        'x': data['time'],
        'y': data['Altitude'],
        'name': 'Altitude',
        'mode': 'lines+markers',
        'type': 'scatter'
    }, 1, 1)
    fig.append_trace({
        'x': data['Longitude'],
        'y': data['Latitude'],
        'text': data['time'],
        'name': 'Longitude vs Latitude',
        'mode': 'lines+markers',
        'type': 'scatter'
    }, 2, 1)

    return fig


if __name__ == '__main__':
    app.run_server(debug=True)

I want to update a single scatterplot. The scatterplot has about twenty-thousand data points. Right now this is my code:

fig['data'][1]['x'] = dataframe_with_new_data['measure_x']
fig['data'][1]['y'] = dataframe_with_new_data['measure_y']

There are only a couple hundred new data points every time the chart updates. I am concerned that it might be slower to replace the entire x and y entries in the fig object, but I'm not sure what a faster alternative might look like.

This is sample data with five hundred points:

import random

x = range(0, 10000, 20)
y = random.sample(range(10, 30000), 500)

Solution

  • You can use extendData property of graph to add data to an existing graph without building the graph from scratch every time the interveral triggers. As written in the documentation:

    extendData (list | dict; optional): Data that should be appended to existing traces. Has the form [updateData, traceIndices, maxPoints], where updateData is an object containing the data to extend, traceIndices (optional) is an array of trace indices that should be extended, and maxPoints (optional) is either an integer defining the maximum number of points allowed or an object with key:value pairs matching updateData

    I selected the time interval 100 ms to show how fast it is to plot the points without any delay.

    import dash
    from dash import *
    import plotly
    import numpy as np 
    
    
    
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
    
    
    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    
    app.layout = html.Div(
        html.Div([
            dcc.Graph(id='live-update-graph', figure = dict(
                    data=[{'x': [0],
                           'y': [0],
                           'name': 'Altitude',
                            'mode': 'lines+markers',
                            'type': 'scatter'
                           }]
                )),
            dcc.Interval(
                id='interval-component',
                interval=100, # in milliseconds
                n_intervals=0
            )
        ])
    )
    
    
    @app.callback(Output('live-update-graph', 'extendData'),
                  Input('interval-component', 'n_intervals'),
                  [State('live-update-graph', 'figure')])
    def update_graph_live(n, existing):
        
        data = {
            'time': [],
            'Altitude': []
        }
    
        # Collect some data
        
        time = existing['data'][0]['x'][-1] + 1 
        alt = np.random.random()
    
        return dict(x=[[time]], y=[[alt]])
    
        
    
    if __name__ == '__main__':
        app.run_server(debug=True)
    

    Output: enter image description here