Scatterplot with dropdown function in dash leaflet

Viewed 25

I want to start using dash leaflet for a project of mine (I was using folium). I´m trying to create a scatterplot with a dropdown functionality. I was able to create the scatterplot and add the dropdown but it doesn`t work.

Here is my code:

import dash_leaflet as dl
import dash_leaflet.express as dlx
import pandas as pd
from dash_extensions.javascript import assign
from dash import Dash, html, dcc

colorscale = ["white", "yellow", "red"]
chroma = "https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js"
color_prop = 'NumerodeTrans'
df = pd.read_csv("assets/ParadasAtributos.csv")

da = df[['Latitud', 'Longitud', 'ID', color_prop, "CODIGOCONTRATO"]]

da[color_prop ]= da[color_prop ].astype(int)

dicts = da.to_dict('rows')

dd_options = df["CODIGOCONTRATO"].unique()

dd_defaults = ["Atributo Nacional"]

for item in dicts:
    item["tooltip"] = "{} ({:.1f})".format(item[color_prop], item[color_prop])

geojson = dlx.dicts_to_geojson(dicts, lat= "Latitud", lon = "Longitud")

geobuf = dlx.geojson_to_geobuf(geojson)
geojson_filter = assign("function(feature, context){return 
context.props.hideout.includes(feature.properties.name);}")

vmax = (df[color_prop].max())

colorbar = dl.Colorbar(colorscale=colorscale, width=20, height=150, min=0, max=vmax, unit="Transacciones por parada")

point_to_layer = 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);  // sender a simple circle marker.
}""")

geojson = dl.GeoJSON(data=geobuf, id="geojson", format="geobuf",
                     zoomToBounds=True,  # when true, zooms to bounds when data changes
                     options=dict(pointToLayer=point_to_layer),  # how to draw points
                     superClusterOptions=dict(radius=50),   # adjust cluster size
                     hideout=dict(colorProp=color_prop, circleOptions=dict(fillOpacity=1, stroke=False, radius=5),
                                  min=0, max=vmax, colorscale=colorscale))

app = Dash(external_scripts=[chroma], prevent_initial_callbacks=True)

app.layout = html.Div([
    dl.Map([dl.TileLayer(), geojson, colorbar]), dcc.Dropdown(id="dd", value=dd_defaults, options=dd_options, clearable=False, multi=True)], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block", "position": "relative"})

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

Where "NumerodeTrans" is the prop that I want to show and "CODIGOCONTRATO" the prop that I want to use as a filter.

Here is a link to a sample of the data : https://drive.google.com/file/d/1R0gkVES0NrQrbRrTBYtG-BC64ROV90SB/view?usp=sharing

I will really appreciate any help.

1 Answers

Running the sample code, I get a scatter plot, but the filtering is not working. Hence I assume that this question is about the filtering functionality. There are a few issue with your code in that respect.

First, you haven't included filter information in the hideout prop. Let's include a property named filter with this information,

hideout=dict(..., filter=dd_defaults))

Next, your filter function filters against the wrong property (name), i.e. you filter function should be update to something like,

geojson_filter = assign(
    "function(feature, context){return context.props.hideout.filter.includes(feature.properties.CODIGOCONTRATO);}")

Next, you forgot to bind the filter function, which is done via the options property,

options=dict(..., filter=geojson_filter)

Finally, you must include a callback for linking the drop down value to the hideout property,

@app.callback(Output("geojson", "hideout"), Input("dd", "value"))
def update_filter(value):
    return dict(..., filter=value)

With these modifications, the filtering seems to work as intended. Here is the complete code for reference,

import dash_leaflet as dl
import dash_leaflet.express as dlx
import pandas as pd
from dash_extensions.javascript import assign
from dash import Dash, html, dcc, Input, Output

colorscale = ["white", "yellow", "red"]
chroma = "https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js"
color_prop = 'NumerodeTrans'
df = pd.read_csv("assets/ParadasAtributos.csv")

da = df[['Latitud', 'Longitud', 'ID', color_prop, "CODIGOCONTRATO"]]

da[color_prop] = da[color_prop].astype(int)

dicts = da.to_dict('rows')

dd_options = df["CODIGOCONTRATO"].unique()

dd_defaults = ["Atributo Nacional"]

for item in dicts:
    item["tooltip"] = "{} ({:.1f})".format(item[color_prop], item[color_prop])

geojson = dlx.dicts_to_geojson(dicts, lat="Latitud", lon="Longitud")

geobuf = dlx.geojson_to_geobuf(geojson)
geojson_filter = assign(
    "function(feature, context){return context.props.hideout.filter.includes(feature.properties.CODIGOCONTRATO);}")

vmax = (df[color_prop].max())

colorbar = dl.Colorbar(colorscale=colorscale, width=20, height=150, min=0, max=vmax, unit="Transacciones por parada")

point_to_layer = 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);  // sender a simple circle marker.
}""")

geojson = dl.GeoJSON(data=geobuf, id="geojson", format="geobuf",
                     zoomToBounds=True,  # when true, zooms to bounds when data changes
                     options=dict(pointToLayer=point_to_layer, filter=geojson_filter),  # how to draw points
                     superClusterOptions=dict(radius=50),  # adjust cluster size
                     hideout=dict(colorProp=color_prop, circleOptions=dict(fillOpacity=1, stroke=False, radius=5),
                                  min=0, max=vmax, colorscale=colorscale, filter=dd_defaults))

app = Dash(external_scripts=[chroma], prevent_initial_callbacks=True)
app.layout = html.Div([
    dl.Map([dl.TileLayer(), geojson, colorbar]),
    dcc.Dropdown(id="dd", value=dd_defaults, options=dd_options, clearable=False, multi=True)],
    style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block", "position": "relative"})


@app.callback(Output("geojson", "hideout"), Input("dd", "value"))
def update_filter(value):
    return dict(colorProp=color_prop, circleOptions=dict(fillOpacity=1, stroke=False, radius=5),
                min=0, max=vmax, colorscale=colorscale, filter=value)


if __name__ == '__main__':
    app.run_server()
Related