I am trying to create a Mapbox graph using Plotly. All goes well in creating the traces and selecting which ones to display using the dropdown.
However, I am trying to draw layers over the map as well. When I do this without using a dropdown, it all goes fine. But when I try to add the layers as options using the "updatemenus" function, the layers will not be displayed.
The traces and base layout are created like this and work fine (a bit simplified, normally in a loop):
fig_dict = {
"data": [],
"layout": {}
}
data = dict(type='scattermapbox',
lat=dft['lat'],
lon=dft['lon'],
mode='markers',
marker=dict(size=3,
color=dft['Color']),
showlegend=False,
)
fig_dict["data"].append(data)
layout = dict(legend=dict(orientation="h",yanchor="bottom",y=1.02,xanchor="right",x=1),
mapbox= dict(accesstoken=mapboxtoken,
center=dict(lat=52.1436,lon=5.383),
style="dark",
zoom=6.25)
)
fig_dict["layout"] = layout
The layers are created like so (in the same loop as the traces):
R = stand.iloc[0]['Distance in KM']/66.8
R2 = (3/5)*R
center_lon = dft.iloc[0,9]
center_lat = dft.iloc[0,8]
t = np.linspace(0, 2*pi, 100)
circle_lon =center_lon + R*cos(t)
circle_lat =center_lat + R2*sin(t)
coords=[]
for lo, la in zip(list(circle_lon), list(circle_lat)):
coords.append([lo, la])
layer=dict(sourcetype = 'geojson',
source={ "type": "Feature",
"geometry": {"type": "LineString",
"coordinates": coords
}
},
templateitemname=dft.iloc[0,2],
visible=True,
color= dft.iloc[0,13],
type = 'fill',
opacity=0.5,
fill = dict(outlinecolor= 'white')
)
layers.append(layer)
Finally the updatemenus part is structured like this (based on multiple traces created in the loop):
fig_dict["layout"]["updatemenus"] = [dict(
active=0,
buttons=list([
dict(label="Q3",
method="update",
args=[{"visible": [True, False],
{'title':'test1','mapbox':{'layers':layer1}}
]),
dict(label="Q4",
method="update",
args=[{"visible": [False, True],
{'title':'test2','mapbox':{'layers':layer2}}
])
])
)]
fig = go.Figure(fig_dict)
I have tried a lot of different ways to structure "updatemenus", but to no succes. The above is my last try. Like I said, I have cut down my original code by a bit to keep it a little more tidy (I'm quite new to this), but the essentials are the same. If it's better to have the full code, I will provide it!
Edit:
The data looks as followed:
I have one dataset through which the script loops to create traces for all the individual locations on the map. Each ID (approx. 3k) has its own dot on the map. The collor corresponds to the location (dft):
id Location lat lon
0 1 Leeuwarden 53.3238509 6.864812147
1 2 Leeuwarden 53.20375983 5.806213178
2 3 Enschede 52.28652282 6.845358477
3 4 Leeuwarden 53.18575506 5.833720622
4 5 Breda 51.81459037 5.851585979
5 6 Leeuwarden 53.20965451 5.777285517
6 7 Groningen 53.32373805 6.695864432
7 8 Leeuwarden 52.95470465 5.919419689
8 9 Leeuwarden 52.82104961 5.96997389
9 10 Enschede 52.50522699 6.612134113
10 11 Leeuwarden 53.15089124 5.844570929
Then for each location a layer is created on the map based on this dataset (stand):
Location lon lat Average distance in KM
0 Breda 4.234844574 51.45673726 15.1
1 Enschede 6.123570731 52.23459517 71.3
2 Groningen 6.234960418 53.12302082 22.2
3 Leeuwarden 5.795739613 53.19886105 30.0
import numpy as np
import pandas as pd
import geopandas as gpd
import requests, io
import plotly.express as px
dft = pd.read_csv(io.StringIO(""" id Location lat lon
0 1 Leeuwarden 53.3238509 6.864812147
1 2 Leeuwarden 53.20375983 5.806213178
2 3 Enschede 52.28652282 6.845358477
3 4 Leeuwarden 53.18575506 5.833720622
4 5 Breda 51.81459037 5.851585979
5 6 Leeuwarden 53.20965451 5.777285517
6 7 Groningen 53.32373805 6.695864432
7 8 Leeuwarden 52.95470465 5.919419689
8 9 Leeuwarden 52.82104961 5.96997389
9 10 Enschede 52.50522699 6.612134113
10 11 Leeuwarden 53.15089124 5.844570929"""), sep="\s+")
stock = pd.read_csv(
io.StringIO(
""" Location lon lat "Average distance in KM"
0 Breda 4.234844574 51.45673726 15.1
1 Enschede 6.123570731 52.23459517 71.3
2 Groningen 6.234960418 53.12302082 22.2
3 Leeuwarden 5.795739613 53.19886105 30.0"""
),
sep="\s+",
)
# calculate geometry of areas with appropriate radius
stock = gpd.GeoDataFrame(
stock, geometry=gpd.points_from_xy(stock["lon"], stock["lat"]), crs="epsg:4326"
)
utm = stock.estimate_utm_crs()
stock = gpd.GeoDataFrame(
stock,
geometry=stock.to_crs(utm).apply(
lambda r: r["geometry"].buffer(r["Average distance in KM"] * 1000), axis=1
),
crs=utm,
).to_crs("epsg:4326")
# create scatter of points
fig = px.scatter_mapbox(
dft, lat="lat", lon="lon", color="Location"
).update_layout(
mapbox={"style": "carto-positron", "zoom": 5},
margin={"t": 25, "b": 0, "l": 0, "r": 0},
)
# build a dict of layers that corresponds to traces
layers = {
t.name: {
"source": stock.loc[stock["Location"].eq(t.name), "geometry"].__geo_interface__,
"type": "line",
"color": t.marker.color,
}
for t in fig.data
}
# # add circles as layers...
fig = fig.update_layout(mapbox_layers=list(layers.values()))
# # finally build menus...
fig.update_layout(
updatemenus=[
{
"buttons": [
{
"label": q,
"method": "update",
"args": [
{"visible": [t2.name == q or q == "All" for t2 in fig.data]},
{
"title": q,
"mapbox": {
"style": fig.layout.mapbox.style,
"center": fig.layout.mapbox.center,
"zoom": fig.layout.mapbox.zoom,
"layers": [layers[q]]
if q != "All"
else list(layers.values()),
},
},
],
}
for q in stock["Location"].unique().tolist() + ["All"]
],
}
]
)
fig