Dash Leaflet Callbacks and Hideout

I have been wrestling with Dash Leaflet, as a non-js person. I think DL is great and provides many cool things beyond the scattermapbox that I am familiar with, but I can’t seem to wrap my head around the js parts and the hideout. I believe the hideout is there to feed props to the js. Is there a fairly complete example where many of the props are fed thru callback, instead of directly as in the DL Heroku app examples?

The code below does add the map, and the clusters appear, but the unclustered points do not.

Thank you


## Here is the callback that attempts to feed the geojson component

@callback(Output('tab1-blocks', 'data'),
          Output('tab1-blocks', 'options'),
          Output('tab1-blocks', 'hideout'),
          Output('tab1-colorbar-div', 'children'),
                                          
           Input('metricdrop', 'value'),
           Input('rampdrop', 'value'),
           Input('block-store-data', 'data')         
         )          

def make_tab1map (metric, ramp, blkdf):
    
    if blkdf is None or blkdf.empty:
        return no_update
    else:
               
        dfblk = blkdf[['Block ID', 'Block Pop', metric, 'Lat', 'Lon']]
        dicts = dfblk.to_dict('rows')
        for item in dicts:
            metval = int(round(item[metric],1)) if item[metric] >= 1 else round(item[metric],1)
            item["tooltip"] = f"""<b>Block ID:</b> {item['Block ID']}<br>
            <b>Block Pop:</b> {item['Block Pop']}<br>
            <b>{list(item)[2]}:</b> {metval}
                """
        blks_geojson = dlx.dicts_to_geojson(dicts, lat = 'Lat', lon='Lon')
        blks_buf = dlx.geojson_to_geobuf(blks_geojson)
        
        color_prop = metric
        vmax = dfblk[color_prop].max()        
        options = dict(pointToLayer=draw_scatter)
        circleoptions=dict(fillOpacity=1, stroke=False, radius=5)
        hideout=dict(min=0, max=vmax, colorscale=colorscale, circleOptions=circleoptions, colorProp=color_prop)              
        colorbarkids = dl.Colorbar(colorscale=colorscale, width=20, height=150, min=0, max=vmax, unit='')
        
                
        return blks_buf, options, hideout, colorbarkids


## Here is the relevant part of the layout (separate script) where the map is attempted

draw_scatter = assign("""function(feature, latlng, context){
    const {min, max, colorscale, circleOptions, colorProp} = context.props.hideout;
    const csc = chroma.scale(colorscale).domain([min, max]);  // chroma lib to construct colorscale
    circleOptions.fillColor = csc(feature.properties[colorProp]);  // set color based on color prop.
    return L.circleMarker(latlng, circleOptions);  // send a simple circle marker.
}""")

     
colorscale = ['red', 'yellow', 'green', 'blue', 'purple']  # rainbow

chroma = "https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js"  # js lib used for colors

dbc.Col([dcc.Loading(type = 'circle', children = [
                                                
                                                                html.Div("Put title here..."),
                                                                
                                                                dl.Map(id="tab1-map", center=[37,-80], zoom = 5, minZoom = 3,
                                                                        children = [dl.LayersControl([aerial, esri, mapbox, dark, light, openstreet]  +
                                                                                                    [countylayer, tribelayer, roads, facilities]),
                                                                                   
                                                                                    dl.GeoJSON(data=[],                                                                                               
                                                                                            id='tab1-blocks',
                                                                                            format='geobuf',
                                                                                            cluster=True,
                                                                                            zoomToBoundsOnClick=True,
                                                                                            zoomToBounds = True,
                                                                                            superClusterOptions=dict(radius=100, maxZoom = 12),
                                                                                            options = [],
                                                                                            hideout=dict(min=0, max=10000, colorscale=colorscale,
                                                                                                         circleOptions=dict(fillOpacity=1, stroke=False, radius=5), colorProp='Cancer Risk (per million)'),
                                                                                            # hoverStyle=arrow_function(dict(weight=5, color='#666'))
                                                                                            ),
                                                                                            
                                                                                    html.Div(id='tab1-colorbar-div'),
                                                                                    
                                                                                    dl.MeasureControl(position="topleft", primaryLengthUnit="kilometers", primaryAreaUnit="hectares", activeColor="#214097", completedColor="#972158")
                                                                                    
                                                                                    ],
                                                                                               
                                                                                               
                                                                        style={'width': '620px', 'height': '600px'}
                                                                        )
                                                                 ]
                                                                 ),
                                                     
                                                     html.Div(id="countyfooter"), 
                                                    
                                                     
                                                   ], xs=11, sm=11, md=5, lg=5, xl=5, align='start'),

Found this example which is a good one for cases of many properties being sent via callback. My main issue was not including external_scripts=[chroma] in

app = DashProxy(transforms=[ServersideOutputTransform(backend = fss)],
                                  external_stylesheets=dbc_stylesheets,
                                  external_scripts=[chroma],)

Still have some head wrapping to do, but gotta love dash leaflet.

1 Like

The GeoJSON component was designed with large datasets in mind (say, a million points). When the data is large, recreating the GeoJSON component, e.g. during interactive filtering and/or rendering (e.g. change a color map), is slow as the data is passed from the Dash server to the client. Hence, the user experience will be poor.

The intention of the hideout prop is to enable interactivity without recreating the GeoJSON object from Dash. The key idea is to implement the geojson rendering and/or filtering function(s) in such a way that they depend on the content of the hideout property. By doing so, you can change filtering and/or rendering by changing the hideout property without recreating the GeoJSON object, thus enabling smooth interactivity. Does that make sense? :smiley:

EDIT: Yes, similar to Python, you must include all the libraries used in the JS functions, in this case chroma :+1:

That does make sense. In my case, I’m loading all census blocks in a given county (max points about 80,000). It seemed a trade-off whether to send geojson with just the desired colorProp field, or to go ahead and send all fields and then choose the colorProp thru callback. I was thinking it might be better to have small geojson size. With relatively few points it may be moot.
What is the largest number of points sent to geojson (and clustered) that seem reasonable in your experience?
Thanks for the quick reply.