Map of a country with plotly - python

The aim of create a map of a country with plotly. I show you the code to make the map with dots from cities and I want to change it to regions:
import plotly.graph_objects as go
fig = go.Figure(go.Scattergeo())
fig.update_geos(
visible=False, resolution=50, scope="Spain",
showcountries=True, countrycolor="Black",
showsubunits=True, subunitcolor="Blue"
)
fig.update_layout(height=300, margin={"r":0,"t":0,"l":0,"b":0})
fig.show()
In the scope we have by default: "world" | "usa" | "europe" | "asia" | "africa" | "north america" | "south america". How to implement those who are not in the scope? For example Spain.

using mapbox instead of geos
you can add layers to mapbox scatter
have sourced cities and boundary geometries to demonstrate
import requests
import plotly.express as px
import pandas as pd
# get Spain municipal boundaries
res = requests.get(
"https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/spain-provinces.geojson"
)
# get some cities in Spain
df = (
pd.json_normalize(
requests.get(
"https://opendata.arcgis.com/datasets/6996f03a1b364dbab4008d99380370ed_0.geojson"
).json()["features"]
)
.loc[
lambda d: d["properties.CNTRY_NAME"].eq("Spain"),
["properties.CITY_NAME", "geometry.coordinates"],
]
.assign(
lon=lambda d: d["geometry.coordinates"].apply(lambda v: v[0]),
lat=lambda d: d["geometry.coordinates"].apply(lambda v: v[1]),
)
)
# scatter the cities and add layer that shows municiple boundary
px.scatter_mapbox(df, lat="lat", lon="lon", hover_name="properties.CITY_NAME").update_layout(
mapbox={
"style": "carto-positron",
"zoom": 3.5,
"layers": [
{
"source": res.json(),
"type": "line",
"color": "green",
"line": {"width": 1},
}
],
}
)

Related

How to create a Plotly animation from a list of figure objects?

I have a list of Plotly figures and I want to create an animation that iterates over each figure on a button press. Similar the examples found on Intro to Animations in Python. I pretty much tried re-creating several of the examples on the page with no luck.
It seems like there should be a simple solution but I have not been able to find one. I should note that I do not want to animate the geocoded cities but rather the weather layout - i.e., mapbox_layers
Below is the code to create the list of figures:
import requests
from bs4 import BeautifulSoup
import pandas as pd
import plotly.express as px
# just some geocoded data from plotly
us_cities = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/us-cities-top-1k.csv")
# GET request to pull the datetime info
r = requests.get('https://geo.weather.gc.ca/geomet?service=WMS&version=1.3.0&request=GetCapabilities&layer=GDPS.DIAG_NW_PT1H')
# create the soup
soup = BeautifulSoup(r.text, 'xml')
# start and end dates in UTC
start, end = soup.findAll('Dimension')[0].text.split('/')[:2]
# create a date range
dates = pd.date_range(start, end, freq='1h').strftime('%Y-%m-%dT%H:%M:%SZ')[0::3]
# iterate over the dates to create the figures
figs = []
for date in dates:
fig = px.scatter_mapbox(us_cities, lat="lat", lon="lon", hover_name="City", hover_data=["State", "Population"],
color_discrete_sequence=["black"], zoom=3, height=600, center={'lat': 42.18845, 'lon':-87.81544},
title=date)
fig.update_layout(
mapbox_style="open-street-map",
mapbox_layers=[
{
"below": 'traces',
"sourcetype": "raster",
"sourceattribution": "Government of Canada",
"source": ["https://geo.weather.gc.ca/geomet/?"
"SERVICE=WMS&VERSION=1.3.0"
"&REQUEST=GetMap"
"&BBOX={bbox-epsg-3857}"
"&CRS=EPSG:3857"
"&WIDTH=1000"
"&HEIGHT=1000"
"&LAYERS=GDPS.DIAG_NW_PT1H"
"&TILED=true"
"&FORMAT=image/png"
f"&TIME={date}"
],
},
]
)
fig.update_layout(margin={"r":0,"t":50,"l":0,"b":0})
figs.append(fig)
figs[0]
figs[6]
figs[12]
I think the most helpful example in the plotly documentation was on visualizing mri volume slices. Instead of creating a list of figure objects, we can store the data and layout of each figure in a list of go.Frame objects and then initialize our figure with these frames with something like fig = go.Figure(frames=[...])
The creation of the buttons and sliders follows the documentation exactly, and these can probably be tweaked to your liking.
Note: the slider will only work if we populate the name argument in each go.Frame object, as pointed out by #It_is_Chris
import requests
from bs4 import BeautifulSoup
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
# just some geocoded data from plotly
us_cities = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/us-cities-top-1k.csv")
# GET request to pull the datetime info
r = requests.get('https://geo.weather.gc.ca/geomet?service=WMS&version=1.3.0&request=GetCapabilities&layer=GDPS.DIAG_NW_PT1H')
# create the soup
soup = BeautifulSoup(r.text, 'xml')
# start and end dates in UTC
start, end = soup.findAll('Dimension')[0].text.split('/')[:2]
# create a date range
dates = pd.date_range(start, end, freq='1h').strftime('%Y-%m-%dT%H:%M:%SZ')[0::3]
# iterate over the dates to create the figures
# figs = []
frames = []
for i, date in enumerate(dates):
fig = px.scatter_mapbox(us_cities, lat="lat", lon="lon", hover_name="City", hover_data=["State", "Population"],
color_discrete_sequence=["black"], zoom=3, height=600, center={'lat': 42.18845, 'lon':-87.81544},
title=date)
fig.update_layout(
mapbox_style="open-street-map",
mapbox_layers=[
{
"below": 'traces',
"sourcetype": "raster",
"sourceattribution": "Government of Canada",
"source": ["https://geo.weather.gc.ca/geomet/?"
"SERVICE=WMS&VERSION=1.3.0"
"&REQUEST=GetMap"
"&BBOX={bbox-epsg-3857}"
"&CRS=EPSG:3857"
"&WIDTH=1000"
"&HEIGHT=1000"
"&LAYERS=GDPS.DIAG_NW_PT1H"
"&TILED=true"
"&FORMAT=image/png"
f"&TIME={date}"
],
},
]
)
fig.update_layout(margin={"r":0,"t":50,"l":0,"b":0})
frames += [go.Frame(data=fig.data[0], layout=fig.layout, name=date)]
## store the first frame to reuse later
if i == 0:
first_fig = fig
fig = go.Figure(frames=frames)
## add the first frame to the figure so it shows up initially
fig.add_trace(first_fig.data[0],)
fig.layout = first_fig.layout
## the rest is coped from the plotly documentation example on mri volume slices
def frame_args(duration):
return {
"frame": {"duration": duration},
"mode": "immediate",
"fromcurrent": True,
"transition": {"duration": duration, "easing": "linear"},
}
sliders = [
{
"pad": {"b": 10, "t": 60},
"len": 0.9,
"x": 0.1,
"y": 0,
"steps": [
{
"args": [[f.name], frame_args(0)],
"label": str(k),
"method": "animate",
}
for k, f in enumerate(fig.frames)
],
}
]
fig.update_layout(
title='Slices in volumetric data',
width=1200,
height=600,
scene=dict(
zaxis=dict(range=[-0.1, 6.8], autorange=False),
aspectratio=dict(x=1, y=1, z=1),
),
updatemenus = [
{
"buttons": [
{
"args": [None, frame_args(50)],
"label": "▶", # play symbol
"method": "animate",
},
{
"args": [[None], frame_args(0)],
"label": "◼", # pause symbol
"method": "animate",
},
],
"direction": "left",
"pad": {"r": 10, "t": 70},
"type": "buttons",
"x": 0.1,
"y": 0,
}
],
sliders=sliders
)
fig.show()

Map of a country by regions | scatter_mapbox | plotly

Thanks to Rob Raymond for the previos work. The aim is to represent the regions of a country with scatter_mapbox, I got this situation of the map (Spain is the example):
import requests
import plotly.express as px
import pandas as pd
# get Spain municipal boundaries
res = requests.get(
"https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/spain-provinces.geojson"
)
# get some cities in Spain
df = (
pd.json_normalize(
requests.get(
"https://opendata.arcgis.com/datasets/6996f03a1b364dbab4008d99380370ed_0.geojson"
).json()["features"]
)
.loc[
lambda d: d["properties.CNTRY_NAME"].eq("Spain"),
["properties.CITY_NAME", "geometry.coordinates"],
]
.assign(
lon=lambda d: d["geometry.coordinates"].apply(lambda v: v[0]),
lat=lambda d: d["geometry.coordinates"].apply(lambda v: v[1]),
)
)
# scatter the cities and add layer that shows municiple boundary
px.scatter_mapbox(df, lat="lat", lon="lon", hover_name="properties.CITY_NAME").update_layout(
mapbox={
"style": "carto-positron",
"zoom": 3.5,
"layers": [
{
"source": res.json(),
"type": "line",
"color": "green",
"line": {"width": 1},
}
],
}
)
How to change cities by regions?
using geopandas https://plotly.com/python/mapbox-county-choropleth/#using-geopandas-data-frames
have created a column measure to define colorscale of province
simple case of doing choropleth instead of scatter
same can be achieved without geopandas referencing geojson and using pandas
import plotly.express as px
import numpy as np
import geopandas as gpd
gdf = gpd.read_file(
"https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/spain-provinces.geojson",
crs="epsg:4326",
)
# for choropleth...
gdf["measure"] = np.random.randint(1, 1000, len(gdf))
px.choropleth_mapbox(
gdf, geojson=gdf["geometry"].__geo_interface__, locations=gdf.index, color="measure", hover_name="name"
).update_layout(
mapbox={
"style": "carto-positron",
"center": {
"lon": sum(gdf.total_bounds[[0, 2]]) / 2,
"lat": sum(gdf.total_bounds[[1, 3]]) / 2,
},
"zoom":4
},
margin={"l":0,"r":0,"t":0,"b":0}
)

Plotly create inset choropleth (Mapbox ideally) of Alaska and Hawaii

Is there a way to create additional choropleth (ideally Mapbox choropleths, but I'd settle for Plotly's standard choropleth function) maps and layer them on top of a base choropleth so that I can easily show data related to Alaska and Hawaii with the continental US?
Something like this image
Don't believe my existing code is necessarily helpful, but here's how I build my base map (removed my custom style so that anyone should be able to generate).
fig = px.choropleth_mapbox(
df_mar,
geojson=puma,
locations="stpuma",
color="inter_pct",
range_color=(0,25),
color_continuous_scale="Viridis",
labels={"inter_pct": "Marriage (%)"},
center={"lat": 37.0902, "lon": -95.7129},
zoom=4.2,
opacity=1.0,
mapbox_style="white-bg"
)
fig.update_layout(
coloraxis_colorbar=dict(
bgcolor="rgba(22,33,49,1)",
title="Marriage,<br>Percent Share",
titlefont=dict(
color="rgba(255,255,255,1)"
),
tickfont=dict(
color="rgba(255,255,255,1)"
),
),
margin=dict(
l=50,
r=50,
b=50,
t=50,
pad=4
),
paper_bgcolor = "rgba(8,18,23,1)",
plot_bgcolor = "rgba(8,18,23,1)",
showlegend = True,
annotations = [
dict(
x=-0.025,
y=-0.04,
xref='paper',
yref='paper',
text='Source: Census ACS 5 2015-2019',
showarrow = False,
font=dict(
color="rgba(255,255,255,1)"
),
bgcolor="rgba(8,18,23,1)",
)
]
)
fig.update_traces(
marker_line_width=0,
below="waterway"
)
fig.show(width=1920, height=1080)
it can be achieved with mapbox
key concept is modify geometry to meet you layout requirements. Hence have done https://shapely.readthedocs.io/en/stable/manual.html#affine-transformations to move Alaska and Hawaii
also found that Alaska geometry was give issues given it crosses date line. Hence clipped geometry after transformation as well
for good measure added layers to indicate geometry has been manipulated
included maritime boundary to demonstrate how it can be extended to additional geometries
import geopandas as gpd
import shapely.geometry
import numpy as np
import plotly.express as px
import requests, io
from pathlib import Path
from zipfile import ZipFile
import urllib
import pandas as pd
from shapely.affinity import affine_transform as T
# US geometry
urls = [
"https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_state_5m.zip",
"https://maritimeboundaries.noaa.gov/downloads/USMaritimeLimitsAndBoundariesSHP.zip",
]
gdfs = {}
for url in urls:
f = Path.cwd().joinpath(urllib.parse.urlparse(url).path.split("/")[-1])
if not f.exists():
r = requests.get(url, stream=True, headers={"User-Agent": "XY"})
with open(f, "wb") as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
zfile = ZipFile(f)
zfile.extractall(f.stem)
gdfs[f.stem] = gpd.read_file(
list(f.parent.joinpath(f.stem).glob("*.shp"))[0]
) # .to_crs("EPSG:4326")
gdf2 = gdfs["cb_2018_us_state_5m"]
gdf2 = gdf2.set_index("STUSPS", drop=False)
gdf2["color"] = gdf2["STATEFP"].astype(int)
# move alaska and hawaii using affine transform
t = {"AK": [0.6, 0, 0, 0.6, -20, -15], "HI": [3, 0, 0, 3, 385, -42]}
clip = (-179, 15, 0, 150)
gdf3 = gdf2.copy()
gdf3.loc[t.keys(), "geometry"] = gdf3.loc[t.keys(), ["geometry"]].apply(
lambda g: shapely.ops.clip_by_rect(T(g["geometry"], t[g.name]), *clip), axis=1
)
gdf_m = gdfs["USMaritimeLimitsAndBoundariesSHP"]
gdf_m = gdf_m.dissolve("REGION")
tm = {"Alaska": "AK", "Hawaiian Islands": "HI"}
gdf_m.loc[tm.keys(), "geometry"] = gdf_m.loc[tm.keys(), ["geometry"]].apply(
lambda g: shapely.ops.clip_by_rect(
T(g["geometry"], t[tm[g.name]]), *gdf3.loc[tm[g.name]].geometry.bounds
),
axis=1,
)
px.choropleth_mapbox(
gdf3,
geojson=gdf3.geometry.__geo_interface__,
locations=gdf3.index,
color="color",
hover_name="NAME",
).update_layout(
mapbox={
"style": "carto-positron",
"center": {"lon": -98, "lat": 33},
"zoom": 2.5,
"layers": [
{
"source": shapely.geometry.box(
*gdf3.loc[box].geometry.bounds
).__geo_interface__,
"type": "fill",
"color": "blue",
"opacity": 0.1,
}
for box in t.keys()
]
+ [
{
"source": gdf_m.geometry.__geo_interface__,
"type": "line",
"color": "blue",
"line": {"width": 1},
}
],
},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
)

Draw a polygon around point in scattermapbox using python

I am using plotlys scattermapbox to plot points on a map. I'd like to draw the polygon that cover 'x' mile radius from a POI.
dcc.Graph(id="map-graph"),
#application.callback([
Output("map-graph", "figure"),
],
[
Input("address", "value"),
Input("type", "value")
]
)
def update_graph(address, type):
for i, row in df.iterrows():
lat = row["Lat"]
lng = row["Long"]
data.append({
"type": "scattermapbox",
"lat": [lat],
"lon": [lng],
"name": "Location",
"showlegend": False,
"hoverinfo": "text",
"mode": "markers",
"marker": {
"symbol": "circle",
"size": 8,
"opacity": 0.8,
"color": "black"
}
}
)
# Plot POI
POI_Lat = 37.785908
POI_Long = -122.400803
data.append({
"type": "scattermapbox",
"lat": [POI_Lat],
"lon": [POI_Long],
"marker": {
"symbol": "circle,
"size": 28,
"opacity": 0.7,
"color": "rgb(128, 128, 128)"
}
}
)
df is a pandas dataframe that includes coordinates for locations within x miles of POI. How do I update the map-graph to draw a polygon that covers all the points?
Adding a layer to layout dictionary:
gdf = circles(Lat, Long, radius=1609.34)
print(gdf['geometry'][0])
POLYGON ((385272.0167249573 3768678.19769511, 385264.2673129799 3768520.454790493,.......))
layout = {
"autosize": True,
"hovermode": "closest",
"mapbox": {
"accesstoken": MAPBOX_KEY,
"bearing": 0,
"center": {
"lat": layout_lat,
"lon": layout_lon
},
"layers": [
{
"source": json.loads(gdf.geometry.to_json()),
"below": "traces",
"type": "line",
"color": "purple",
"line": {"width": 1.5},
}
],
"pitch": 0,
"zoom": zoom,
"style": "outdoors",
},
"margin": {
"r": 0,
"t": 0,
"l": 0,
"b": 0,
"pad": 0
}
}
based on answer to this duplicate question Obtain coordinates of a Polygon / Multi-polygon around a point in python
no sample data provided in question, so I've used UK hospital data
have created a helper function poi_poly(). NB radius is in meters as per UTM geometry
UTM geometry is used to create a polygon of specified radius
markers are then intersected with this polygon. Then get the convex hull
have provided option to return radius polygon as well, in example below I've returned this to demonstrate that the convex hull polygon is within the radius of the POI
import shapely.geometry
import pandas as pd
import geopandas as gpd
import requests, io, json
import plotly.express as px
import random
def poi_poly(
df,
radius=10 ** 5,
poi={"Longitude": 0.06665166467428207, "Latitude": 51.19034957885742},
lon_col="Longitude",
lat_col="Latitude",
include_radius_poly=False,
):
# generate a geopandas data frame of the POI
gdfpoi = gpd.GeoDataFrame(
geometry=[shapely.geometry.Point(poi["Longitude"], poi["Latitude"])],
crs="EPSG:4326",
)
# extend point to radius defined (a polygon). Use UTM so that distances work, then back to WSG84
gdfpoi = (
gdfpoi.to_crs(gdfpoi.estimate_utm_crs())
.geometry.buffer(radius)
.to_crs("EPSG:4326")
)
# create a geopandas data frame of all the points / markers
if not df is None:
gdf = gpd.GeoDataFrame(
geometry=df.loc[:, ["Longitude", "Latitude"]]
.dropna()
.apply(
lambda r: shapely.geometry.Point(r["Longitude"], r["Latitude"]), axis=1
)
.values,
crs="EPSG:4326",
)
else:
gdf = gpd.GeoDataFrame(geometry=gdfpoi)
# create a polygon around the edges of the markers that are within POI polygon
return pd.concat(
[
gpd.GeoDataFrame(
geometry=[
gpd.sjoin(
gdf, gpd.GeoDataFrame(geometry=gdfpoi), how="inner"
).unary_union.convex_hull
]
),
gpd.GeoDataFrame(geometry=gdfpoi if include_radius_poly else None),
]
)
# get some public addressess - hospitals. data that can be scattered
dfhos = pd.read_csv(
io.StringIO(
requests.get("http://media.nhschoices.nhs.uk/data/foi/Hospital.csv").text
),
sep="¬",
engine="python",
)
# generate polygon of markers within 5 mile radius of Point of Interest
poi = dfhos.loc[random.randint(0, len(dfhos) - 1), ["Longitude", "Latitude"]].to_dict()
gdf = poi_poly(dfhos, poi=poi, radius=1609.34 * 5, include_radius_poly=True)
fig = (
px.scatter_mapbox(
dfhos,
lat="Latitude",
lon="Longitude",
color="Sector",
hover_data=["OrganisationName", "Postcode"],
)
.update_traces(marker={"size": 10})
.update_layout(
mapbox={
"style": "open-street-map",
"zoom": 9,
"center": {"lat": poi["Latitude"], "lon": poi["Longitude"]},
"layers": [
{
"source": json.loads(gdf.geometry.to_json()),
"below": "traces",
"type": "line",
"color": "purple",
"line": {"width": 1.5},
}
],
},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
)
)
fig.show()
draw just a circle polygon
poi_poly() has been updated. DataFrame is no longer mandatory for finding markers within POI
simple example of creating a circle (actually a polygon) centred on a single set of GPS co-ordinates
import plotly.graph_objects as go
poi = {"Latitude": 37.785908, "Longitude": -122.400803}
go.Figure(go.Scattermapbox()).update_layout(
mapbox={
"style": "open-street-map",
"zoom": 9,
"center": {"lat": poi["Latitude"], "lon": poi["Longitude"]},
"layers": [
{
"source": json.loads(poi_poly(None, poi=poi, radius=1609).to_json()),
"below": "traces",
"type": "line",
"color": "purple",
"line": {"width": 1.5},
}
],
},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
)
See an example in the documentation here
import plotly.graph_objects as go
fig = go.Figure(go.Scattermapbox(
mode = "markers",
lon = [-73.605], lat = [45.51],
marker = {'size': 20, 'color': ["cyan"]}))
fig.update_layout(
mapbox = {
'style': "stamen-terrain",
'center': { 'lon': -73.6, 'lat': 45.5},
'zoom': 12, 'layers': [{
'source': {
'type': "FeatureCollection",
'features': [{
'type': "Feature",
'geometry': {
'type': "MultiPolygon",
'coordinates': [[[
[-73.606352888, 45.507489991], [-73.606133883, 45.50687600],
[-73.605905904, 45.506773980], [-73.603533905, 45.505698946],
[-73.602475870, 45.506856969], [-73.600031904, 45.505696003],
[-73.599379992, 45.505389066], [-73.599119902, 45.505632008],
[-73.598896977, 45.505514039], [-73.598783894, 45.505617001],
[-73.591308727, 45.516246185], [-73.591380782, 45.516280145],
[-73.596778656, 45.518690062], [-73.602796770, 45.521348046],
[-73.612239983, 45.525564037], [-73.612422919, 45.525642061],
[-73.617229085, 45.527751983], [-73.617279234, 45.527774160],
[-73.617304713, 45.527741334], [-73.617492052, 45.527498362],
[-73.617533258, 45.527512253], [-73.618074188, 45.526759105],
[-73.618271651, 45.526500673], [-73.618446320, 45.526287943],
[-73.618968507, 45.525698560], [-73.619388002, 45.525216750],
[-73.619532966, 45.525064183], [-73.619686662, 45.524889290],
[-73.619787038, 45.524770086], [-73.619925742, 45.524584939],
[-73.619954486, 45.524557690], [-73.620122362, 45.524377961],
[-73.620201713, 45.524298907], [-73.620775593, 45.523650879]
]]]
}
}]
},
'type': "fill", 'below': "traces", 'color': "royalblue"}]},
margin = {'l':0, 'r':0, 'b':0, 't':0})
fig.show()
Adjust the above based on your point and polygon coordinates.
If you want to use another mapbox style:
The accepted values for layout.mapbox.style are one of:
"white-bg" yields an empty white canvas which results in no external HTTP requests
"open-street-map", "carto-positron", "carto-darkmatter", "stamen-terrain",
"stamen-toner" or "stamen-watercolor" yield maps composed of raster tiles from various public tile servers which do not require signups or access tokens
"basic", "streets", "outdoors", "light", "dark", "satellite", or "satellite-streets" yield maps composed of vector tiles from the Mapbox service, and do require a Mapbox Access Token or an on-premise Mapbox installation.
A Mapbox service style URL, which requires a Mapbox Access Token or an on-premise Mapbox installation.
A Mapbox Style object as defined at https://docs.mapbox.com/mapbox-gl-js/style-spec/
https://plotly.com/python/mapbox-layers/

Creating a dropdown to filter by date month (pandas + plotly)

DataFrame
Current_Graph
Is it possible for me to integrate a dropdownbox onto this bar graph with 12 selections for each month of the year. When a month is selected the data is filtered for the month and consecutively updates the plotly graph to only display bars for the selected month?
have simulated data to match structure of your data frame
you can build updatemenus buttons to set the xaxis range. Have done this dynamically by building another dataframe that contains data to do this in a list comprehension
import numpy as np
import pandas as pd
import plotly.express as px
# simulate data
df = pd.DataFrame({"Date": pd.date_range("1-jun-2020", "now")}).pipe(
lambda d: d.assign(**{"Time Per Ball (Seconds)": np.random.uniform(20, 51, len(d))})
)
fig = px.bar(df, x="Date", y="Time Per Ball (Seconds)")
dfbut = (
(df["Date"] + pd.offsets.MonthBegin(-1))
.drop_duplicates()
.to_frame()
.assign(
label=lambda d: d["Date"].dt.strftime("%b-%Y"),
start=lambda d: (d["Date"] - pd.Timestamp("1970-01-01")) // pd.Timedelta("1ms"),
end=lambda d: d["start"].shift(-1),
)
.fillna((df["Date"].max() - pd.Timestamp("1970-01-01")) // pd.Timedelta("1ms"))
)
# build buttons to filter by month
fig.update_layout(
updatemenus=[
{
"buttons": [
{
"label": r[1]["label"],
"method": "relayout",
"args": [{"xaxis": {"range": [r[1]["start"], r[1]["end"]]}}],
}
for r in dfbut.iterrows()
]
},
{"buttons":[{"label":"All data", "method":"relayout","args":[{"xaxis":{"range":[]}}]}],"y":.8,"type":"buttons"}
]
)
approach with multiple traces
have a trace for each month. run new cell after df and dfbut have been built
set visibility of traces in update menus
also use uirevision and {"xaxis": {"autorange": True} to maintain layout between view switches
# add EndDate to make next part simpler
dfbut = dfbut.assign(EndDate=dfbut["Date"].shift(-1) - pd.Timedelta(days=1)).fillna(df["Date"].max())
dfbut = dfbut.loc[~(dfbut["EndDate"] < df["Date"].min())]
# create a trace per month so zoom / pan is maintained between selections
go.Figure(
[
px.bar(
df.loc[df["Date"].between(start, end)],
x="Date",
y="Time Per Ball (Seconds)",
)
.update_traces(name=label)
.data[0]
for label, start, end in dfbut.loc[:, ["label", "Date", "EndDate"]].values
]
).update_layout(
updatemenus=[
{
"buttons": [
{
"label": "All",
"method": "update",
"args": [
{"visible": (dfbut["label"] == dfbut["label"]).tolist()},
{"xaxis": {"autorange": True}},
],
}
]
+ [
{
"label": l,
"method": "update",
"args": [
{"visible": (dfbut["label"] == l).tolist()},
{"xaxis": {"autorange": True}},
],
}
for l in dfbut["label"]
]
}
],
xaxis={"autorange": True},
)

Categories