Search code examples
pythonexceptionloadingplotly-dashchoropleth

dcc.Loading on First Load Only (Python)


I am working on a Plotly Dash project that has a lot of choropleth maps that are filtered, sliced, and diced. These are pretty expensive computations that slow everything down, so I thought as a nice touch, I could add in a dcc.loading wrapper around the maps to at least let the user know it's loading instead of my dashboard having the appearance of lagging.

The issue I'm having is that the loading icon will appear after every change in the map. Even if it's a quick change that takes less than 1 second, the loading icon will appear. My challenge is that I would like to still use the dcc.loading wrapper, but have it show on the initial load of the map only.

I was reading through this blog on the Plotly Community site that addresses the same problem, but no one was able to come up with a solution: https://community.plotly.com/t/loading-states-and-loading-component/19650/35. Further, I was playing around with the "PreventUpdate" argument from the Dash help page here: https://dash.plotly.com/advanced-callbacks, but still couldn't find a solution.

Can anyone help point me in the right direction?

Here is some example code:

import pandas as pd
import dash
from urllib.request import urlopen
import json
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output 
#from dash.exceptions import PreventUpdate


with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response:
    counties = json.load(response)

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/fips-unemp-16.csv",
                   dtype={"fips": str})

fips_choices = df['fips'].sort_values().unique()


app = dash.Dash(__name__)
server = app.server
app.layout = html.Div([
    dcc.Dropdown(id='dropdown1',
                 options=[{'label': i, 'value': i} for i in fips_choices],
                 value=fips_choices[0]
    ),
    dcc.Loading(
        id='loading',
        children=[
            dcc.Graph(id='us_map')
    ])
])



@app.callback(
    Output('us_map','figure'),
    Input('dropdown1','value'))

def update_map(county_select):
        new_df = df[df['fips']==county_select]
        fig = px.choropleth_mapbox(new_df, geojson=counties, locations='fips', color='unemp',
                           color_continuous_scale="Viridis",
                           range_color=(0, 12),
                           mapbox_style="carto-positron",
                           zoom=3, center = {"lat": 37.0902, "lon": -95.7129},
                           opacity=0.5
                          )
        fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})

        return fig

app.run_server(host='0.0.0.0',port='8051')

Solution

  • There might not be a way with dcc.Loading

    This is a solution that worked for me.

    Give your map a style of {"display":"none"}

    dcc.Graph(id="us_map", style={"display": "none"})
    

    In the callback add the us-map style as an output and return {"display":"inline"}

    @app.callback(
        [Output("us_map", "figure"), Output("us_map", "style")], Input("dropdown1", "value")
    )
    def update_map(county_select):
        new_df = df[df["fips"] == county_select]
        fig = px.choropleth_mapbox(
            new_df,
            geojson=counties,
            locations="fips",
            color="unemp",
            color_continuous_scale="Viridis",
            range_color=(0, 12),
            mapbox_style="carto-positron",
            zoom=3,
            center={"lat": 37.0902, "lon": -95.7129},
            opacity=0.5,
        )
        fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
    
        return fig, {"display": "inline"} 
    

    If you want a loading bar instead of the area being blank, place the loading bar in a div above the map, add the loading bar as an output to the div, add a a return of {"display":"false"} so that when the map loads the loading animation disappeares. You cannot get this to work with dcc.Loading so I just used a custom CSS loading bar.