Plotly / Dash zeigt Echtzeitdaten in flüssiger Animation an
Wir versuchen, ein Echtzeit-Dashboard in Plotly-Dash zu erstellen, das Live-Daten während der Erstellung anzeigt. Wir folgen im Allgemeinen den Anweisungen hier (https://dash.plotly.com/live-updates).
Wir haben einen Rückruf, der ungefähr jede Sekunde einen Teil der neuen Datenpunkte aus der Quelle sammelt und die Daten dann an das Diagramm anfügt.
Wenn wir dies tun, ist die Aktualisierung des Diagramms abgehackt, da wir jede Sekunde ein neues Diagrammobjekt im Rückruf generieren. Wir möchten, dass der Graph reibungslos fließt, auch wenn dies bedeutet, dass wir ein oder zwei Sekunden hinter den Live-Daten liegen.
Wir betrachten Animationen (https://plotly.com/python/animations/), aber es ist nicht klar, wie wir eine Animation auf einen Live-Datenstrom anwenden können, der an ein Diagramm angehängt wird.
Antworten
Das Aktualisieren von Traces einer GraphKomponente ohne Generieren eines neuen Diagrammobjekts kann über die extendDataEigenschaft erfolgen. Hier ist ein kleines Beispiel, das jede Sekunde Daten anfügt:
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()
Abhängig von der Netzwerkverbindung zwischen Client und Server (bei jedem Update wird eine Anforderung zwischen Client und Server ausgetauscht) funktioniert dieser Ansatz bis zu einer Aktualisierungsrate von etwa 1 s.
Wenn Sie eine höhere Aktualisierungsrate benötigen, würde ich empfehlen, die Diagrammaktualisierung mithilfe eines clientseitigen Rückrufs durchzuführen . Bei Annahme des vorherigen Beispiels würde der Code in Anlehnung an
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()
Clientseitige Updates sollten schnell genug sein, um ein reibungsloses Update zu erreichen. Das folgende GIF zeigt das obige Beispiel mit einer Aktualisierungsrate von 25 ms.
Beachten Sie, dass eine clientseitige Aktualisierung nur möglich ist, wenn die Daten bereits clientseitig vorhanden sind, dh ein anderer Mechanismus erforderlich ist, um die Daten vom Server abzurufen. Ein möglicher Datenfluss könnte sein
- Verwenden Sie eine langsame
IntervalKomponente (z. B. 2 s), um einen (normalen) Rückruf auszulösen, der einen Datenblock von der Quelle abruft und in eineStoreKomponente einfügt - Verwenden Sie eine schnelle
IntervalKomponente (z. B. 25 ms), um einen clientseitigen Rückruf auszulösen, der Daten von derStoreKomponente zurGraphKomponente überträgt
Bearbeiten: Revision 1
Am Ende meines Vorschlags finden Sie ein vollständig reproduzierbares, wenn auch minimales Code-Snippet. Beachten Sie jedoch, dass es sich um ein Beispiel handelt, das in JupyterDash gestartet werden soll.
Ich muss nur davon ausgehen, dass Sie Ihren Datenstrom in einem oder mehreren Pandas-Datenrahmen erfassen. Um zu simulieren, was ich als Ihre reale Situation verstehe, muss ich mich nur auf die Generierung einiger zufälliger Daten verlassen. In der folgenden Überarbeitung meiner ursprünglichen Antwort werde ich argumentieren, dass das einzige, was Sie benötigen, um eine reibungslose Animation mit Echtzeitdaten zu erstellen, ist
1. df.plot() mit Pandas Plotten Backend eingestellt auf plotly,
2. eine Dash-Komponente wie diese:
dcc.Interval(id='interval-component',
interval=1*1000, # in milliseconds
n_intervals=0
)
3. und eine Rückruffunktion wie diese:
@app.callback(
Output('graph', 'figure'),
[Input('interval-component', "n_intervals")]
)
Das folgende Snippet enthält Code, der genau das tut, was Sie in Ihrer Frage beschrieben haben:
1. Es sammelt df2jede Sekunde einen Teil zufälliger Daten in einem Datenrahmen .
2. fügt das einem vorhandenen Datenrahmen hinzu df1und
3. Zeichnet das Ergebnis.
Die erste Abbildung sieht folgendermaßen aus:
Nach einigen Sekunden sieht die Figur folgendermaßen aus:
Und das mag zu schön klingen, um wahr zu sein, aber die Übergänge zwischen den Figuren sehen sofort ziemlich gut aus. Neue Punkte werden elegant am Ende der Linien hinzugefügt, und sowohl die x- als auch die y-Achse werden ziemlich reibungslos aktualisiert.
Die Aktualisierung mag zunächst etwas abgehackt erscheinen, aber nach einigen tausend Läufen sehen Sie nur das Ende der Zeilen, die sich bewegen:
In der Abbildung oben sehen Sie, dass der Startpunkt nach einigen tausend Läufen enthalten ist. Dies ist wahrscheinlich offensichtlich, aber wenn Sie nach beispielsweise 1000 Läufen eine konstante Fensterlänge beibehalten möchten, fügen Sie einfach Ersetzen df3 = df3.cumsum()durch hinzu df3 = df3.cumsum().tail(1000), um Folgendes zu erhalten:
Aber du musst nicht mein Wort dafür nehmen. Starten Sie einfach das folgende Snippet in JupyterLab und überzeugen Sie sich selbst:
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
# code and plot setup
# settings
pd.options.plotting.backend = "plotly"
countdown = 20
#global df
# sample dataframe of a wide format
np.random.seed(4); cols = list('abc')
X = np.random.randn(50,len(cols))
df=pd.DataFrame(X, columns=cols)
df.iloc[0]=0;
# plotly figure
fig = df.plot(template = 'plotly_dark')
app = JupyterDash(__name__)
app.layout = html.Div([
html.H1("Streaming of random data"),
dcc.Interval(
id='interval-component',
interval=1*1000, # in milliseconds
n_intervals=0
),
dcc.Graph(id='graph'),
])
# Define callback to update graph
@app.callback(
Output('graph', 'figure'),
[Input('interval-component', "n_intervals")]
)
def streamFig(value):
global df
Y = np.random.randn(1,len(cols))
df2 = pd.DataFrame(Y, columns = cols)
df = df.append(df2, ignore_index=True)#.reset_index()
df.tail()
df3=df.copy()
df3 = df3.cumsum()
fig = df3.plot(template = 'plotly_dark')
#fig.show()
return(fig)
app.run_server(mode='external', port = 8069, dev_tools_ui=True, #debug=True,
dev_tools_hot_reload =True, threaded=True)
Dieses Beispiel ist nicht sehr elegant und es gibt viel Raum für Verbesserungen (sogar eine globale Variable ...), aber ich hoffe, es wird Ihnen nützlich sein.
Edit: Revision 2:
Nach ungefähr 6000 Läufen sieht das Diagramm folgendermaßen aus:
Und jetzt macht es nicht mehr so viel Spaß, die Dinge anzusehen, obwohl die Dinge sehr reibungslos laufen . Jedes Update zeigt nur eine winzige Bewegung an den Endpunkten. Deshalb habe ich dort am Ende einige Anmerkungen hinzugefügt, um klarer zu machen, dass die Dinge tatsächlich noch laufen:
Vollständiger Code mit Anmerkungen
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
# code and plot setup
# settings
pd.options.plotting.backend = "plotly"
countdown = 20
#global df
# sample dataframe of a wide format
np.random.seed(4); cols = list('abc')
X = np.random.randn(50,len(cols))
df=pd.DataFrame(X, columns=cols)
df.iloc[0]=0;
# plotly figure
fig = df.plot(template = 'plotly_dark')
app = JupyterDash(__name__)
app.layout = html.Div([
html.H1("Streaming of random data"),
dcc.Interval(
id='interval-component',
interval=1*1000, # in milliseconds
n_intervals=0
),
dcc.Graph(id='graph'),
])
# Define callback to update graph
@app.callback(
Output('graph', 'figure'),
[Input('interval-component', "n_intervals")]
)
def streamFig(value):
global df
Y = np.random.randn(1,len(cols))
df2 = pd.DataFrame(Y, columns = cols)
df = df.append(df2, ignore_index=True)#.reset_index()
#df.tail()
df3=df.copy()
df3 = df3.cumsum()#.tail(1000)
fig = df3.plot(template = 'plotly_dark')
#fig.show()
colors = px.colors.qualitative.Plotly
for i, col in enumerate(df3.columns):
fig.add_annotation(x=df3.index[-1], y=df3[col].iloc[-1],
text = str(df3[col].iloc[-1])[:4],
align="right",
arrowcolor = 'rgba(0,0,0,0)',
ax=25,
ay=0,
yanchor = 'middle',
font = dict(color = colors[i]))
return(fig)
app.run_server(mode='external', port = 8069, dev_tools_ui=True, #debug=True,
dev_tools_hot_reload =True, threaded=True)
Originelle Antwort und Vorschläge
Sie haben keinen Beispielcode angegeben, daher kann ich nur einen allgemeinen Vorschlag machen. Dies ist ein genauerer Blick darauf, wie Plot Forex-Daten in einem Beispiel in der Dash-Galerie plotten :
Ich würde insbesondere einen Blick darauf werfen, wie sie ihre Rückrufe und die Funktion generate_figure_callback(pair)aus Zeile 932 in der Quelle eingerichtet haben :
# Function to update Graph Figure
def generate_figure_callback(pair):
def chart_fig_callback(n_i, p, t, s, pairs, a, b, old_fig):
if pairs is None:
return {"layout": {}, "data": {}}
pairs = pairs.split(",")
if pair not in pairs:
return {"layout": {}, "data": []}
if old_fig is None or old_fig == {"layout": {}, "data": {}}:
return get_fig(pair, a, b, t, s, p)
fig = get_fig(pair, a, b, t, s, p)
return fig
return chart_fig_callback
Das ist alles, was ich jetzt habe, aber ich hoffe, Sie werden es nützlich finden!
Bearbeiten: Nur um zu zeigen, dass Updates nicht auf 5 Minuten beschränkt sind.
Bildschirmaufnahme bei 21:16:29
Bildschirmaufnahme bei 21:16:55
Was Sie im Bid / Ask-Text sehen, ist genau das: Gebote und Angebote. Und sie ändern sich ständig. Wenn ich zu 100% Recht habe, steht die Linie für abgeschlossene Geschäfte, und das kommt nur von Zeit zu Zeit vor. Ich denke, dies ist nur eine Frage der Daten, die Sie hier anzeigen. Und ich hoffe, das einzige, was Sie tun müssen, um das zu erreichen, wonach Sie suchen, ist, zentrale Teile dieses Beispiels durch Ihre Datenquelle zu ersetzen. Sie können sich auch das Wind Streaming-Beispiel ansehen . Dies ist möglicherweise noch einfacher für Ihr Szenario zu implementieren.