Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plotly/Dash display real time data in smooth animation

We are trying to produce a real-time dashboard in plotly-dash that displays live data as it is produced. We are generally following the guidance here (https://dash.plotly.com/live-updates).

We have a callback that gathers a chunk of new data points from the source approximately every second and then appends the data to the graph.

When we do this the update to the graph is choppy because we are generating a new graph object on the callback every second. We want the graph to flow smoothly, even if that means we're a second or two behind the live data.

We are looking at animations (https://plotly.com/python/animations/) but it's not clear how we might apply an animation to a live stream of data being appended to a graph.

like image 940
David Parks Avatar asked Aug 26 '20 01:08

David Parks


People also ask

Is Plotly dash better than tableau?

Compared to traditional visualization tools like Tableau, Plotly allows full control over what is being plotted. Since Plotly is plotted based on Pandas, you can easily perform complex transformations to your data before plotting it.

Is plotly better than Matplotlib?

Plotly has several advantages over matplotlib. One of the main advantages is that only a few lines of codes are necessary to create aesthetically pleasing, interactive plots. The interactivity also offers a number of advantages over static matplotlib plots: Saves time when initially exploring your dataset.

Is bokeh better than plotly?

In this comparison of Bokeh vs Plotly, we can't make out a decisive choice between the two. Though Plotly is good for plotting graphs and visualizing data for insights, it is not good for making dashboards. To make dashboards we can use bokeh and can have very fast dashboards and interactivity.

Is Dash plotly free for commercial use?

Is Dash free? Yes. Plotly's Dash analytics application framework is also free and open-source software, licensed under the MIT license.


1 Answers

Updating traces of a Graph component without generating a new graph object can be achieved via the extendData property. Here is a small example that appends data each second,

import dash
import dash_html_components as html
import dash_core_components as dcc
import numpy as np

from dash.dependencies import Input, Output

# Example data (a circle).
resolution = 20
t = np.linspace(0, np.pi * 2, resolution)
x, y = np.cos(t), np.sin(t)
# Example app.
figure = dict(data=[{'x': [], 'y': []}], layout=dict(xaxis=dict(range=[-1, 1]), yaxis=dict(range=[-1, 1])))
app = dash.Dash(__name__, update_title=None)  # remove "Updating..." from title
app.layout = html.Div([dcc.Graph(id='graph', figure=figure), dcc.Interval(id="interval")])


@app.callback(Output('graph', 'extendData'), [Input('interval', 'n_intervals')])
def update_data(n_intervals):
    index = n_intervals % resolution
    # tuple is (dict of new data, target trace index, number of points to keep)
    return dict(x=[[x[index]]], y=[[y[index]]]), [0], 10


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

Depending of the network connection between client and server (at each update, a request is exchanged between client and server), this approach works up to a refresh rate of around 1s.

If you need a higher refresh rate, i would suggest doing the graph update using a client side callback. Adopting the previous example, the code would be along the lines of

import dash
import dash_html_components as html
import dash_core_components as dcc
import numpy as np

from dash.dependencies import Input, Output, State

# Example data (a circle).
resolution = 1000
t = np.linspace(0, np.pi * 2, resolution)
x, y = np.cos(t), np.sin(t)
# Example app.
figure = dict(data=[{'x': [], 'y': []}], layout=dict(xaxis=dict(range=[-1, 1]), yaxis=dict(range=[-1, 1])))
app = dash.Dash(__name__, update_title=None)  # remove "Updating..." from title
app.layout = html.Div([
    dcc.Graph(id='graph', figure=dict(figure)), dcc.Interval(id="interval", interval=25),
    dcc.Store(id='offset', data=0), dcc.Store(id='store', data=dict(x=x, y=y, resolution=resolution)),
])
app.clientside_callback(
    """
    function (n_intervals, data, offset) {
        offset = offset % data.x.length;
        const end = Math.min((offset + 10), data.x.length);
        return [[{x: [data.x.slice(offset, end)], y: [data.y.slice(offset, end)]}, [0], 500], end]
    }
    """,
    [Output('graph', 'extendData'), Output('offset', 'data')],
    [Input('interval', 'n_intervals')], [State('store', 'data'), State('offset', 'data')]
)

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

Client side updates should be fast enough to achieve a smooth update. The gif below shows the above example running with 25 ms refresh rate,

Client side update

Keep in mind that a client side update is only possible if the data is already present client side, i.e. another mechanism is needed to fetch the data from the server. A possible data flow could be

  1. Use a slow Interval component (e.g. 2 s) to trigger a (normal) callback that fetches a chunk of data from the source and places it in a Store component
  2. Use a fast Interval component (e.g. 25 ms) to trigger a client side callback that streams data from the Store component to the Graph component
like image 117
emher Avatar answered Oct 07 '22 21:10

emher