Express data not updating?

Hi. I want to update the data points in a 3D scatter plot fast while my python script runs. But I can’t get the update to work. I thought I knew how to do it Graph Objects, but then was advised online that “You should be using Express”.
So…I’m trying but it’s not working. Can anyone help?

Here’s my sample code:

import plotly.express as px
import time

# this top part is from docs: plotly express 3d scatter example
df = px.data.iris()
fig = px.scatter_3d(df, x='sepal_length', y='sepal_width', z='petal_width',
            color='petal_width', symbol='species')
fig.update_layout(template="plotly_dark")
fig.show()


# now update the plot in real time ? 
for i in range(1000):
    fig.data[0].x, fig.data[0].y = fig.data[0].y, fig.data[0].x # simple data operation: flip x & y
    ## Ok that alone had no effect. So...maybe push change to the plot? 
    fig.update_layout(uirevision='constant')   # NO EFFECT
    fig.update_layout(uirevision='constant', data=fig.data)  ##  Error: No 'data' keyword
    fig.update_traces(data=fig.data)           ## Error: no 'data' keyword
    fig.data.update( dict(mode='markers') )   # Error: No .update on .data
    time.sleep(0.05)  # 50 miliseconds

What am I missing? Thanks very much!.

Caveats:

  1. Just an Express solution, if you please. I notice that answers on her often mandate switching to some other flavor of Plotly. I’ve already switched once now! (I did get updates working in Jupyter – by going back to Graph Objects! – but would rather not have to switch the rest of my stuff over to Jupyter.)
  2. I don’t want an “animation”, as in storing values as they run and then later plotting them; I want the plot to be changing in real time as the data changes.

@drscotthawley
A figure created by plotly express is an instance of the go.Figure. Hence to update it you must first inspect its structure, namely, how many traces are composing that figure and what are the layout settings performed by plotly express.

In your example, printing fig.data or just its length, you can find out that fig.data contains three go.Scatter3d instances, while
fig.layout is defined as follows:

Layout({
    'coloraxis': {'colorbar': {'title': {'text': 'petal_width'}},
                  'colorscale': [[0.0, '#0d0887'], [0.1111111111111111,
                                 '#46039f'], [0.2222222222222222, '#7201a8'],
                                 [0.3333333333333333, '#9c179e'],
                                 [0.4444444444444444, '#bd3786'],
                                 [0.5555555555555556, '#d8576b'],
                                 [0.6666666666666666, '#ed7953'],
                                 [0.7777777777777778, '#fb9f3a'],
                                 [0.8888888888888888, '#fdca26'], [1.0, '#f0f921']]},
    'legend': {'title': {'text': 'species'}, 'tracegroupgap': 0},
    'margin': {'t': 60},
    'scene': {'domain': {'x': [0.0, 1.0], 'y': [0.0, 1.0]},
              'xaxis': {'title': {'text': 'sepal_length'}},
              'yaxis': {'title': {'text': 'sepal_width'}},
              'zaxis': {'title': {'text': 'petal_width'}}},
    'template': '...'
})

Only after inspecting these two components of a Plotly figure you can start making updates.

To find out the attributes of involved traces, as well as for go.Layout, import:

import plotly.graph_objects as go
print(help(go.Scatter3d)) # in your example all three traces have this type

and then

print(help(go.Layout))

The direct assignment

fig.data[0].x =fig.data[0].y

is forbidden!!!
Instead, use fig.update_traces (which is meant to update all traces or a single one selected from fig.data by a given selector:

fig.update_traces(x=fig.data[0].y, y= fig.data[0].x, selector=dict(legendgroup= 'setosa'))

i.e. the above updates are performed on the trace with the legendroup="setosa", and this one is fig.data[0].

Please read here the role of go.Layout.uirevision:

import plotly.graph_objects as go
help(go.Layout.uirevision)

Help on property:

    Used to allow user interactions with the plot to persist after
    `Plotly.react` calls that are unaware of these interactions. If
    `uirevision` is omitted, or if it is given and it changed from
    the previous `Plotly.react` call, the exact new figure is used.
    If `uirevision` is truthy and did NOT change, any attribute
    that has been affected by user interactions and did not receive
    a different value in the new figure will keep the interaction
    value. `layout.uirevision` attribute serves as the default for
    `uirevision` attributes in various sub-containers. For finer
    control you can set these sub-attributes directly. For example,
    if your app separately controls the data on the x and y axes
    you might set `xaxis.uirevision=*time*` and
    `yaxis.uirevision=*cost*`. Then if only the y data is changed,
    you can update `yaxis.uirevision=*quantity*` and the y axis
    range will reset but the x axis range will retain any user-
    driven zoom.
    
    The 'uirevision' property accepts values of any type
    
    Returns
    -------
    Any

These two lines have no sense:

 fig.update_traces(data=fig.data)           ## Error: no 'data' keyword
 fig.data.update( dict(mode='markers') )   # Error: No .update on .data

The first one because the trace go.Scatter3d has no attribute data, and the second one because fig.data is a list of traces, not a list of dicts with one dict having the key mode.

To be performant in working with Plotly, you first should learn the architecture of go.Figure, and how to inspect a trace properties and layout attributes.

1 Like