I use FastAPI for a Python backend and Plotly to generate a plot from some data. On the client side I use HTMX.
The server sends directly the Plot's HTML using Figure.to_html( full_html = False)
and HTMX swaps it directly in the correct position in the page.
This was all very convenient and easy to do, but now I have the need of live-updating the plot when the data changes. So I've set up a websocket connection using FastAPI and the ws
extension of HTMX, to update the plot when needed.
The problem is, that if I send the entire plot again at every update of the data, the entire thing gets swapped by HTMX and the user looses the current view state of the plot, namely zoom rate, pan position, etc...
So is there a way to be more granular about what to send from Plotly?
I would need to:
How do I do that? is it possible?
HTMX aside (the principle should remain the same), I would recommend :
The Python backend serves the initial page : it contains an empty div wrapper with a specific id, the initial data and the scripts for creating/updating the plot with Plotly.js :
The backend send live data (JSON) to the client.
The client append new data to current data and update the plot accordingly (see Plotly.js Function Reference)
<head>
<!-- Load plotly.js into the DOM -->
<script src='https://cdn.plot.ly/plotly-2.35.2.min.js'></script>
</head>
<body>
<div id='myDiv'><!-- Plotly chart will be drawn inside this DIV --></div>
</body>
<script>
// Create graph with initial data
const gd = document.getElementById('myDiv');
const data = [{ /* ... */ }];
const layout = { /* ... */ };
Plotly.newPlot(gd, data, layout);
// Update graph when receiving new data
function onUpdate(newData) {
append(data, newData); // custom function, depends on data (plotly trace type)
Plotly.restyle(gd, data); // use Plotly.restyle to update data only
}
/* Logic for updating data (register `onUpdate`, define `append` etc.) ... */
</script>
You can also initialize the whole figure in python to leverage Plotly.py and its templates (which are not included in Plotly.js), so you don't have to do that manually in javascript. Use fig.to_json()
to serialize the whole figure, eg.
import plotly.express as px
df = px.data.medals_wide()
fig = px.bar(df, x='nation', y=['gold', 'silver', 'bronze'])
fig_json = fig.to_json()
and inject fig_json
in your script (instead of just those data
props that need to be updated) :
// Create graph with initial data
const gd = document.getElementById('plot');
const fig = JSON.parse(fig_json);
const data = fig.data;
const layout = fig.layout;
Plotly.newPlot(gd, data, layout);
In order to properly serialize the data
props (apart from the figure), specifically when they contain types provided by third-party modules like numpy, pandas, PIL, use pio.json.to_json_plotly()
:
import numpy as np
import plotly.io as pio
data = dict(x=np.array([1, 2, 3]), y=np.array([1, 2, 3]))
data_json = pio.json.to_json_plotly(data)