dash icon indicating copy to clipboard operation
dash copied to clipboard

[BUG] Restyling clustered points in scatter_mapbox via callback breaks dcc.Graph figure

Open ndymlls opened this issue 1 year ago • 5 comments

Applying different colors to clustered points via a callback appears to break the figure and raises the following errors in the browser console:

  • Error: Source "source-7f412d-circle" cannot be removed while layer "plotly-trace-layer-7f412d-cluster" is using it.
  • Uncaught (in promise) Error: Mapbox error.

Example code provided below. To recreate the error, toggle the filter checkbox on and off. Additional clustering checkbox shows that the error does not happen if clustering not enabled.

from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd

print('Mapbox token required')
mapbox_token='xxx'

sitesDF = data = pd.DataFrame({'latitude': [1,1,2,2,1,1,2,2],
                               'longitude': [1,2,1,2,-11,-12,-11,-12],
                               'name':['A','B','C','D','E','F','G','H']})

def create_map(included,excluded,clustering):
    included['color'] = 'included'
    excluded['color'] = 'excluded'
    data = pd.concat([included,excluded])
    
    color_discrete_map = {'included': 'rgb(64,217,18)','excluded': 'grey'}
    fig = px.scatter_mapbox(data, lat='latitude', lon='longitude', color='color',color_discrete_map=color_discrete_map)
    fig.update_layout(mapbox_style='dark', mapbox_accesstoken=mapbox_token)
    fig.update_layout(mapbox={'zoom': 4})
    fig.update_layout(margin={'r': 0, 't': 0, 'l': 0, 'b': 0})
    fig.update_layout(legend={'yanchor':'top','y':0.99,'xanchor':'left','x':0.01})
    fig.update_traces(cluster_enabled=clustering)
    return fig

app = Dash(__name__)
app.layout = dbc.Container([dcc.Loading(dcc.Graph(id='index-map')),
                       html.Br(),
                       dbc.Checkbox(id='clustering',label='Cluster',value=True),
                       dbc.Checkbox(id='filtering',label='Filter',value=False)])

@app.callback(Output('index-map', 'figure'),
              Input('clustering', 'value'),
              Input('filtering', 'value'))
def render_map_extras(clustering,filtering):
    included = sitesDF.iloc[:4] if filtering else sitesDF
    excluded = sitesDF[~sitesDF['name'].isin(list(included['name']))]
    fig = create_map(included,excluded,clustering)
    return fig

app.run_server(debug=True)

Package versions:

    dash==2.9.3
    dash-bootstrap-components==1.4.1
    dash-core-components==2.0.0
    dash-html-components==2.0.0
    dash-renderer==1.9.1
    dash-table==5.0.0

Bug tested and seen in various browsers in Windows 10.

ndymlls avatar May 03 '23 09:05 ndymlls

Hello! Try this updated code that will avoid the error:

from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd

print('Mapbox token required')
mapbox_token = 'xxx'

sitesDF = pd.DataFrame({'latitude': [1, 1, 2, 2, 1, 1, 2, 2],
                        'longitude': [1, 2, 1, 2, -11, -12, -11, -12],
                        'name': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']})


def create_map(included, excluded, clustering):
    included.loc[:, 'color'] = 'included'
    excluded.loc[:, 'color'] = 'excluded'
    data = pd.concat([included, excluded])

    color_discrete_map = {'included': 'rgb(64,217,18)', 'excluded': 'grey'}
    fig = px.scatter_mapbox(data, lat='latitude', lon='longitude', color='color', color_discrete_map=color_discrete_map)
    fig.update_layout(mapbox_style='dark', mapbox_accesstoken=mapbox_token)
    fig.update_layout(mapbox={'zoom': 4})
    fig.update_layout(margin={'r': 0, 't': 0, 'l': 0, 'b': 0})
    fig.update_layout(legend={'yanchor': 'top', 'y': 0.99, 'xanchor': 'left', 'x': 0.01})
    fig.update_traces(cluster_enabled=clustering)
    return fig.to_html(full_html=False)


app = Dash(__name__)
app.layout = dbc.Container([
    html.Iframe(id='index-map', style={'border': 'none', 'width': '100%', 'height': '500px'}),
    html.Br(),
    dbc.Checkbox(id='clustering', label='Cluster', value=True),
    dbc.Checkbox(id='filtering', label='Filter', value=False)
])


@app.callback(Output('index-map', 'srcDoc'),
              Input('clustering', 'value'),
              Input('filtering', 'value'))
def render_map_extras(clustering, filtering):
    included = sitesDF.iloc[:4] if filtering else sitesDF
    excluded = sitesDF[~sitesDF['name'].isin(list(included['name']))]
    map_html = create_map(included, excluded, clustering)
    return map_html


app.run_server(debug=True)

This code uses the fig.to_html(full_html=False) function to get the HTML code of the graph. This code is then passed to the srcDoc attribute of the HTML-IFrame element.

To update the HTML code of the graph, the dcc.Iframe component uses Output('index-map', 'srcDoc').

To create an HTML-IFrame element, use html.Iframe.

To create selection components (checkboxes), use dbc.Checkbox in the markup.

The render_map_extras callback is called when the values of the clustering and filtering checkboxes are changed and returns the HTML code of the graph.

nickmelnikov82 avatar Jun 01 '23 10:06 nickmelnikov82

Hey Nick. Thank you for your response.

Although your suggestion does fix the example, it doesn't work in my actual app.

The map selection is done by lassoing points on the map which triggers a callback for the restyling of the points. The iframe does not have the required selectedData and relayoutData triggers.

ndymlls avatar Jun 01 '23 13:06 ndymlls

hi @ndymlls I can confirm I'm getting the same error on Windows 11 as well.

mapcluster

Coding-with-Adam avatar Mar 18 '24 15:03 Coding-with-Adam

@Coding-with-Adam, thank you for looking into this. I have learned a little more which I thought I would share and could perhaps help.

I have ended up implementing my own code to produce the clustering effect. In doing so I did encounter similar console errors (even though I was not using the cluster argument). I have built the figure using different traces either styled as clusters or separate points, and again styled either as selected or unselected. I have been able to get the error to reproduce itself if the 'name' of the traces did not match another. I wonder if this is happening within your cluster logic. Having different names resolves the errors seen.

Screenshot 2024-03-28 085532

ndymlls avatar Mar 28 '24 08:03 ndymlls