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

@chriddyp This is awesome!
I use dash to make a live streaming app and it will be very handy to preserve the dcc.graph UI state.
Is there anyway to set uirevision to something that would make this constant? (Rather than setting it to something like dataset)

Yeah, so uirevision can be anything, the important thing is that when you change it, we reset the graph. So if you never want to reset the graph, just set it to a constant string: any constant string, ‘foo’ or ‘static’ or ‘dash’ etc

Awesome, thanks you for clarifying!

Hello!
That doesn’t seem to recognise it on my Dash:

print(dcc.version)

gives me :

0.39.0rc4

So I should be fine. But the piece of code:

@app.callback(
Output('Charts','children'),
[Input('DropDown Live Charts', 'value'),
 Input('dataset_storage', 'children')]
)

def update_charts(factor_names,jon_dataset):
# =============================================================================
#     print('live chart data loading time')
#     t0 = time.time()
# =============================================================================

fact_mid_db = pd.read_csv(fact_mid_chart_path,index_col=0,parse_dates = True)
fact_mid_db = fact_mid_db[pricing.index.tolist()]
# =============================================================================
#     print(type(fact_mid_db.index[0]))
# =============================================================================
# =============================================================================
#     t1= time.time()
#     print (t1-t0)    
#     t0 = time.time()  
# =============================================================================

Charts = []
for factor in factor_names:
    Charts.append(html.Div([
        dcc.Graph(
            figure=go.Figure(
                data=[
                    go.Scatter(
                        x = fact_mid_db.index.tolist(),
                        y = fact_mid_db[factor],
                        mode = 'lines',
                        name = factor),
                ],
                
                layout=go.Layout(
                    title=factor,
                    xaxis=dict(tickangle=-45,automargin=True),
                    yaxis=dict(tickangle=-45,automargin=True),
                    margin=dict(l=20, r=0, t=30, b=0),
                    uirevision='DropDown Live Charts'
                ),
                
            ),
            id=factor+'_graph'
        ),
    ], className = 'three columns',style={'margin-left':15,'margin-right':15,'margin-top':5,'margin-bottom':5}))
# =============================================================================
#     t1= time.time()
#     print (t1-t0)  
# =============================================================================
              
return Charts

Doesn’t let me run everything:

prop_descriptions=self._prop_descriptions))
ValueError: Invalid property specified for object of type plotly.graph_objs.Layout: 'uirevision'

Any idea?

Thank you

@QueRico you are getting that ValueError from plotly.py because it’s validation functions haven’t been updated to include the new layout property uirevision yet.

For now, replacing go.Figure and go.Layout with dict will prevent this validation problem.

2 Likes

2 posts were split to a new topic: Preserving state and selected inputs of a previous tab

Yup can confirm.
I had a bit of difficulty crafting the correct dictionary for Layout, but found it easy to use go.Layout and print that object to the terminal, copy and paste that dictionary and finally edit it.

This is awesome, wish I had this when I started working on my app (ended up storing the zoom state in a cache between callbacks) but I’m very glad you implemented this. Thank you!

1 Like

Don’t see any comments on the associated PRs. Is there a target release for adding the uirevision prop to dash-core-components?

Good question. For now, use the pre-releases. For this to get part of an official release dash-core-components, we’ll need to publish a new plotly.js release and then upgrade dash-core-components with the new release. I believe uirevision will be part of plotly.js 1.43.0 (Releases · plotly/plotly.js · GitHub), this week or next.

started playing with dash this weekend and loving it so far. Just came accross this problem and the new property works awesome! pretty excited to play with dash more!

1 Like

Thanks! I was doing sliders to work around the problem, but this is just amazing! It’s working mega!

I’m trying to add this uirevision to dcc.graph mapbox layout, but not having any luck. Is there an example of that type of implementation of uirevision? Is this possible yet? Would be a great addition and very important for mapping applications. Cheers

This should be possible but I personally haven’t tried this. Here are the keys that uirevision controls: center, zoom, bearing, pitch (source code: plotly.js/src/plots/mapbox/layout_attributes.js at 4f8628f4c7b8f0d604f1f6e7585e962428316cff · plotly/plotly.js · GitHub).

Could you create a small, reproducable example?

Thank you for the direction, but I’ve made some additional attempts and am not getting the uirevision to work. Probably something simple I’m missing (hopefully). I’ve also tried emulating the functionality of the dash-opioid example which seems to use the State from dash.dependencies… no luck there either. Here’s a simple example though that I’d like to get working. Any further thoughts on uirevision with mapbox is greatly appreciated. Cheers

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output


# Set mapbox public access token
mapbox_access_token = PROVIDE YOUR TOKEN HERE

app = dash.Dash(__name__)

'''
~~~~~~~~~~~~~~~~
~~ APP LAYOUT ~~
~~~~~~~~~~~~~~~~
'''

app.layout = html.Div([
html.Label('Color'),
dcc.Dropdown(
    id='selected_color',
    options=[
        {'label': 'Navy', 'value': '#001f3f'},
        {'label': 'Blue', 'value': '#0074D9'},
        {'label': 'Aqua', 'value': '#7FDBFF'},
        {'label': 'TEAL', 'value': '#39CCCC'},
    ],
    value='#001f3f'
),

dcc.Graph(
    id='mymap',
    figure=dict(
        data = dict(
            lon=[-105.055618],
            lat=[39.70911],
            type='scattermapbox',
        ),
        layout = dict(
            mapbox = dict(
                layers = [],
                accesstoken = mapbox_access_token,
                style = 'light',
                center=dict(
                    lat=39.70911,
                    lon=-105.055618,            
                ),
                pitch=0,
                zoom=14.5
            )
        )
    )
)
])


'''
~~~~~~~~~~~~~~~~
~~ APP CALLBACKS ~~
~~~~~~~~~~~~~~~~
'''

@app.callback(
Output('mymap', 'figure'),
[Input('selected_color','value')])
def displaymap(selected_color):
data = [dict(
    lon=[-105.055618],
    lat=[39.70911],
    type='scattermapbox',
    marker=dict(
        size=50, color=selected_color
    ),
    opacity = 1,
)]
layout = dict(
    height = 600,
    mapbox = dict(
        uirevision='no reset of zoom',
        accesstoken = mapbox_access_token,
        style = 'light'
    ),
)
figure = dict(data=data,layout=layout)
return figure

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

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