plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.17k stars 2.04k forks source link

Scattermapbox and Choroplethmapbox plots can't be placed under an inactive dcc.Tab #1296

Open cczhu opened 4 years ago

cczhu commented 4 years ago

I'm building a webapp that has multiple tabs, some of which include Plotly go.Scattermapbox and go.Choroplethmapbox elements. Tab visibilty is controlled by a callback using display set to 'block' or 'none'. I'm getting a Javascript error if I move the Mapbox plots to a tab which is not displayed on app startup.

My current environment (Python 3.6.9 under the Windows Subsystem for Linux version of Ubuntu 18.04 LTS) includes

dash-bootstrap-components (0.9.1)
dash-core-components (1.10.0)
dash-html-components (1.0.3)
dash-renderer (1.4.1)
dash-table (4.7.0)
plotly (4.8.1)

My browser is Chrome 83.0.4103.61.

This is a minimum working example of my code that reproduces the error:

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

import pathlib
import configparser

import plotly.express as px
px.set_mapbox_access_token(open(".mapbox_token").read())

df = px.data.carshare()

app = dash.Dash()
app.layout = html.Div([

    dcc.Tabs(
        children=[dcc.Tab(label='Text Tab', value='text'),
                  dcc.Tab(label='Map Tab', value='map')],
        value='text',
        id='tabs'
    ),

    html.Div(id='content-text',
             children=dcc.Markdown("# Some Text")),

    html.Div(
        id='content-map',
        children= [
            dcc.Markdown('# Test of Figure'),
            dcc.Graph(id='test_fig',
                      style={'height': "500px"})
        ]
    )
])

@app.callback([Output('content-text', 'style'),
               Output('content-map', 'style')],
              [Input('tabs', 'value')])
def display_tabs(value):
    tab_settings = [{'display': 'none'}, {'display': 'none'}]
    which_tab = {'text': 0, 'map': 1}
    tab_settings[which_tab[value]] = {'display': 'block'}
    return tab_settings

@app.callback(Output('test_fig', 'figure'),
              [Input('test_fig', 'id')])
def callback_graph(value):

    fig = px.scatter_mapbox(df, lat="centroid_lat", lon="centroid_lon",    
                            color="peak_hour", size="car_hours",
                            color_continuous_scale=px.colors.cyclical.IceFire,
                            size_max=15, zoom=10)

    return fig

app.run_server(debug=True)

When the default value of dcc.Tabs is set to value='map', the app works as expected. When it's set to value='text', the map is not loaded, and the debugger returns the following JS error:

Error: Something went wrong with axis scaling

    at Object.t.setScale (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:2480138)

    at http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:1985622

    at SVGGElement.<anonymous> (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:1989968)

    at http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:315738

    at ut (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:312104)

    at Array.Y.each (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:315711)

    at draw (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:1982743)

    at Object.r.drawMarginPushers (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:2358498)

    at A (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:2331674)

    at Object.l.syncOrAsync (http://127.0.0.1:8050/_dash-component-suites/dash_core_components/async-plotlyjs.v1_10_0m1588696753.js:2:2247780)

I've had no problems setting display to none for other types of Plotly plots, so figured this has to do with being unable to hide the Mapbox layer?

cczhu commented 4 years ago

My current workaround is to create a hidden div trigger that only renders the plots after the user clicks on the Map tab for the first time.

alexcjohnson commented 4 years ago

Thanks @cczhu - the problem isn't specifically with mapbox, in fact the error you're seeing appears to be coming from the colorbar. Here's the same problem in pure plotly.js with a scatter plot and a colorbar https://codepen.io/alexcjohnson/pen/QWyymxY

But the root cause of the issue is that we're not correctly inferring the size of the graph - which is extremely difficult to do for a hidden element given all the options available through CSS and other items that might be on the page influencing it. Normally this isn't a problem, as we redraw the graph when it becomes visible - but after throwing an error it seems like the plot is in an inconsistent state and can't recover by redrawing.

I think we want to solve this at the plotly.js level via https://github.com/plotly/plotly.js/issues/4155