✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

📣 Preserving UI State, like Zoom, in dcc.Graph with uirevision with Dash

Can you please specify, what should I do, if the dataset is changed, but I want to preserve the zoom?
My example is - I have a 3D graph of frauds/non frauds and a threshold slider bar. I would like to keep the zoom, when i move the threshold, since I might find an interesting area to observe. But the zoom keeps resetting, after I move the slider, even when I’ve set the uirevision property to True, because the dataset is changed each time

@levkach that looks like a bug - thanks! Filed at https://github.com/plotly/plotly.js/issues/3378, I suspect it’ll be an easy fix.

1 Like

@daragon looks like we have a bug with uirevision and mapbox (and geo) too - I’ve included those in the same plotly.js issue. Thanks for the report, we’ll get all of these fixed soon!

3 Likes

@alexcjohnson , thanks for the info! For anyone needing a solution in the interim here’s a snippet of what worked for me (in the app callbacks):

# Update map figure  
@app.callback(
		Output('risk-map', 'figure'),
		[Input('risk-checklist', 'values'),
        Input('structurebasedrisk_dropdown','value'),
        Input('confidence-slider', 'value'),
        Input('colorscale-picker', 'colorscale')],
		[State('risk-map', 'relayoutData')])
# def display_map(values, dropdownvalue, value, colorscale, figure):
def display_map(values, dropdownvalue, value, colorscale, relayoutData):
    cm = dict(zip(BINS, colorscale))

    # Control of zoom and center for mapbox map
    try: # hold existing map extent constant during user interaction
        latInitial = (relayoutData['mapbox.center']['lat'])
        lonInitial = (relayoutData['mapbox.center']['lon'])
        zoom = (relayoutData['mapbox.zoom'])
    except: # incase of using checklist before changing map extent
        latInitial=39.715
        lonInitial=-105.065
        zoom=12

As a note, seems that the mapbox object works with relayoutData[‘mapbox.center’][‘lon’] RATHER than [‘mapbox’][‘center’][‘lon’] as in some other examples put out there in the past.
Cheers

1 Like

Hi all,

I am trying to get some plots working and I also want the zoom and angle to stay fixed when updating the visible data since the user might have zoomed-in out or changed the angle in order to inspect and compare different datasets more closely. I am setting the following as a return from my callback function:

return {
    'data': traces,
    'layout': go.Layout(
        margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
        uirevision='same',
        hovermode='closest',
        showlegend=False,
        scene=dict(camera=camera)
    )
}

You see how I have set the uirevision to a fixed string “same”. However, the plot is still reset to a fixed position every time I am changing my data. Any ideas what I could do to fix this?

Many thanks,
C.

What version are you and what chart types do you have inside traces?

Hi and thanks for the reply!

Dash version is 0.35.1

Traces is a list which has been filled in my callback function as follows:

for i in filtered_df: 
    traces.append(go.Scatter3d(
        x=filtered_df['x'], 
        y=filtered_df['y'],
        z=filtered_df['z'],
        text=filtered_df['Status'],
        mode='markers',
        opacity=0.7,
        name=i,
        marker={
            'size': marker_size,
            'color': filtered_df['color_val'],  
            'colorscale': 'Viridis',
            'colorbar': {
                'title': 'Distance from mean',
                'tick0': df['color_val'].min(),    
                'dtick': df['color_val'].max()
            }
        }
    ))

and filtered_df is a pandas DataFrame

Cheers,
C.

It looks like this was fixed in plotly.js 1.43.2 (https://github.com/plotly/plotly.js/releases/tag/v1.43.2). The latest version of dash-core-components bumped plotly.js to 1.44.3 (https://github.com/plotly/dash-core-components/blob/master/CHANGELOG.md#0431---2019-02-11), so I recommend upgrading to the latest. Doing a pip install dash --upgrade should do the trick.

Upgrading got uirevision working!

Many thanks:)
C.

A post was split to a new topic: Retrieve state of graph after legend update

Hi guys,

This is a really cool feature and very useful as I have made company dashboards that update every 30 seconds as they query a database to update.

For the life of me I cannot get uirevision working. I have the latest versions of Dash and Plotly and all the related packages.

I believe the issue is the fact that I cannot specify uid, so every 30 seconds it redraws the same traces with new uids, rendering uirevision useless. In the above examples no one specifies uid it just works, I cannot figure out what makes my case different.

Can post code if helpful.

How do I specify the uid of my traces?

Thanks!

I cannot get uirevision to work with 3D geo-type graphs. Both rotation and zoom are reset after callback execution although the revision property is never changed. I am using dash version 0.39 and plotly version 3.7.1

My code:

import dash
import plotly
import dash_html_components as html
import dash_core_components as dcc

# make app
app = dash.Dash(__name__)

# one marker
data = [plotly.graph_objs.Scattergeo(
    lat = [40.7127],
    lon = [-74.0059],
    hovertext="test"
)]

# layout = globe
layout = dict(
    geo=dict(
        projection = dict( type='orthographic' ),
        uirevision = False
    )
)

# graph = globe with one marker
graph = dcc.Graph(
    id='graph',
    figure=dict(data=data, layout=layout),
)

# app = div with graph
app.layout = html.Div(id="div", children=[graph])


randomlat = list(range(30,50))
# hover callback: add random marker on hover
@app.callback(
    dash.dependencies.Output('div', 'children'),
    [dash.dependencies.Input('graph', 'hoverData')],
)
def hoverEvent(hoverData):
    random = plotly.graph_objs.Scattergeo(
        lat = [randomlat[len(graph.figure["data"])]],
        lon = [-74.0059],
    )
    graph.figure["data"].append(random)
    return [graph]


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

Furthermore, workarounds using “relayout” data do not work. For some reason, the relayout state data contains no zoom information. It contains rotation data only.

I can’t quite understand how to get the legend selections to not reset after changing the trace.

Here is how I currently have it setup:


# for loop with logic to create each trace

    traces.append(go.Bar(
            x=x,
            y=y,
            uid=uid
            visible='legendonly'
        ))
    layout = deepcopy(BASE_LAYOUT)
    layout['legend']['uirevision'] = True
    return go.Figure(data=traces, layout=layout)

But this just results in all my traces becoming invisible at initial startup and resetting back to invisible for each new figure.

Also, setting trace uid does nothing for me, or at least it looks like it is just setting it to some random sequence (ex: uid: "c5fff344-7522-4ece-b17d-9fd474c7f4a1") and not what I set it to.

@mbkupfer It does seem like something funny is happening with uid in plotly.graph_objs - your example works fine if I bypass it:

from random import random
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objs as go

app = dash.Dash(__name__)
app.layout = html.Div([
    html.Button(
        id='button',
        children='Update Data',
        n_clicks=0
    ),
    dcc.Graph(id='graph')
])


@app.callback(Output('graph', 'figure'), [Input('button', 'n_clicks')])
def update_graph(n_clicks):
    traces = [dict(
        type='bar',
        x=range(10),
        y=[random() for _ in range(10)],
        uid=str(i),
        visible='legendonly'
    ) for i in range(5)]

    return dict(data=traces, layout={'legend': {'uirevision': True}})


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

@Pommespapst I missed your question when you asked it, so I hope you already solved this! But for anyone else looking at this now, it seems to work fine if I just set a truthy uirevision. False (or anything else falsy like missing/None) will cause the user interaction state to be ignored when an updated figure is provided.

Thanks @mbkupfer for bringing this up - we’re working on a fix over at https://github.com/plotly/plotly.py/issues/1512 but in the meantime you can construct your figure with a plain dict. I suspect only the top level matters dict(data=traces, layout=layout) instead of go.Figure(data=traces, layout=layout) but I haven’t tested that.

1 Like

Using dict(data=traces, layout=layout) works! Thanks for the big help on this :slight_smile:

By the way, not sure what you meant here

But if you give each trace a uid ( 'France' , 'Germany' , 'UK' would do), the visible: 'legendonly' flag will follow Germany as it moves to the second trace.

Using legendonly resulted in my app starting with all traces being hidden. Removing this from my traces all together seemed to do what I was expecting. Am I misinterpreting something in your explanation?

@alexcjohnson, another issue I just noticed, which I’m not sure is related or not, is that changing dcc.Tabs() results in a the legend traces getting reset. I can create a reproduceable example, but not at the current moment. Either way, I thought I would at least put this out there and see if you had any hunches at to what is going on.

it seems to work fine if I just set a truthy uirevision . False (or anything else falsy like missing/ None ) will cause the user interaction state to be ignored

This did the trick! Thank you so much :slight_smile:

1 Like