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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With