Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callback to apply boundary json files - plotly dash

I'm trying to assign a checklist that allows a user to visualise json boundary files. I've got a scatterplot where the user to filter unique values in Cat. I'm aiming to achieve the same output but for plotting boundary files. Also, the scatter map box should start with no boundary files until the user selects one or more.

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
import geopandas as gpd
import json

gdf_poly = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
gdf_poly = gdf_poly.drop('name', axis = 1)

# subset African continent
Afr_gdf_area = gdf_poly[gdf_poly['continent'] == 'Africa'].reset_index(drop = True)

# subset European continent
Eur_gdf_area = gdf_poly[gdf_poly['continent'] == 'Europe'].reset_index(drop = True)

data = pd.DataFrame({
    'Cat': ['t', 'y', 'y', 'y', 'f', 'f', 'j', 'k', 'k', 'k', 's', 's', 's', 's', 'f', 'j', 'k', 'f', 'j', 'k'],
    'LAT': [5, 5, 5, 6.2, 4.3, 5.7, 14, 7, 7, 7, 7, 8.5, 19, 15, 16, 18, 17, 15, 16, 7],
    'LON': [10, 10, 10, 11.1, 9.8, 11.4, 3, 3, 3, 3, 3, 8.8, 8, 15, 8, 17, 8, 6, 16, 7],
})


external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]

app = dash.Dash(__name__, external_stylesheets = external_stylesheets, suppress_callback_exceptions = True)

app.layout = dbc.Container([
    dbc.Row([
        dbc.Col([html.Label('Cats', style = {'paddingTop': '2rem', 'display': 'inline-block'}),
                 dcc.Checklist(
                     id = 'Cats',
                     options = [
                         {'label': 't', 'value': 't'},
                         {'label': 'y', 'value': 'y'},
                         {'label': 'f', 'value': 'f'},
                         {'label': 'j', 'value': 'j'},
                         {'label': 'k', 'value': 'k'},
                         {'label': 's', 'value': 's'},
                     ],
                     value = ['t', 'y', 'f', 'j', 'k', 's'],
                 ),
                 ], 
                 width = 2),
            
                html.Label('Polygon', style = {'paddingTop': '1rem'}),
                dcc.Checklist(
                    id = 'polygons',
                    options = [
                             {'label': 'Afr', 'value': 'Afr'},
                             {'label': 'Eur', 'value': 'Eur'},                          
                             ],
                    value = ['Afr', 'Eur'],
                    #clearable=True
                ),
                 dbc.Col([
                         html.Div(dcc.Graph(id = 'chart', style = {'height':800}))
                        ], width = 5)
    ])
], fluid = True)

df = data

@app.callback(
     Output('chart', 'figure'),
    [Input("Cats", "value"),
     Input("polygons", "value"),
    ],
    )

def scatter_chart(cats, polygon):

    dff = df[df['Cat'].isin(cats)]

    data = px.scatter_mapbox(data_frame = dff,
                             lat = 'LAT',
                             lon = 'LON',
                             color = 'Cat',
                             zoom = 1,
                             mapbox_style = 'carto-positron',
                             )

    if polygon == 'Afr':
    
        data.update_layout(mapbox={
            "layers": [
                {"source": json.loads(Afr_gdf_area.geometry.to_json()),
                    "below": "traces",
                    "type": "fill",
                    "color": "orange",
                    "opacity" : 0.1,
                    "line": {"width": 1}
                },
            ],
        })    

    elif polygon == 'Eur':
    
        data.update_layout(mapbox={
            "layers": [
                {"source": json.loads(Eur_gdf_area.geometry.to_json()),
                    "below": "traces",
                    "type": "fill",
                    "color": "purple",
                    "opacity" : 0.1,
                    "line": {"width": 0.2}
                },
            ],
        })    

    else:
        pass

    fig = go.Figure(data = data)

    return fig


if __name__ == '__main__':
    app.run_server(debug=True, port = 8052)
like image 225
Chopin Avatar asked Sep 27 '26 12:09

Chopin


1 Answers

Your current code isn't drawing the boundaries, because of your checks using polygon:

if polygon == 'Afr':
  ...
elif polygon == 'Eur':
  ...

These conditions never evaluate to True since polygon refers to the value property of a dcc.CheckList which is always a list of things:

value (list of strings | numbers | booleans; optional): The currently selected value.

https://dash.plotly.com/dash-core-components/checklist

So you're doing comparisons like this ['Afr'] == 'Afr'.

Since you indicate that:

...the scatter map box should start with no boundary files until the user selects one or more.

I recommend you set the default value of your polygons dcc.CheckList to an empty list:

dcc.Checklist(
    id="polygons",
    options=[
      {"label": "Afr", "value": "Afr"},
      {"label": "Eur", "value": "Eur"},
    ],
    value=[]
)

Then in your callback you can do something like this:

@app.callback(
    Output("chart", "figure"), 
    Input("Cats", "value"), 
    Input("polygons", "value")
)
def scatter_chart(cats, polygons):
    dff = df[df["Cat"].isin(cats)]

    data = px.scatter_mapbox(
        data_frame=dff,
        lat="LAT",
        lon="LON",
        color="Cat",
        zoom=1,
        mapbox_style="carto-positron",
    )

    polygon_mapping = {
        "Afr": {"area": Afr_gdf_area, "line-width": 1, "color": "orange"},
        "Eur": {"area": Eur_gdf_area, "line-width": 0.2, "color": "purple"},
    }

    layers = []
    for polygon in polygons:
        p = polygon_mapping[polygon]
        layers.append(
            {
                "source": json.loads(p["area"].geometry.to_json()),
                "below": "traces",
                "type": "fill",
                "color": p["color"],
                "opacity": 0.1,
                "line": {"width": p["line-width"]},
            }
        )

    data.update_layout(mapbox={"layers": layers})

    fig = go.Figure(data=data)

    return fig

In the code above I've replaced the if statements with a dictionary and a loop. For each selected boundary checkbox we add a layer to a list of layers and we use this list to update the mapbox layers of the figure.

like image 96
Bas van der Linden Avatar answered Sep 30 '26 01:09

Bas van der Linden