Edit: found out why!
It’s due to returning the id 2 times in the heatmap file.
Do this instead
def update...()
return dcc.Graph(figure=fig)
return html.Div(id=ids.HEATMAP_UPDATE)
I’m trying to make an interactive heatmap for baseball.
The input/selection is from a dropdown menu, and the output is a heatmap for that player.
However, for some reason, the graph only updates once!
Let me explain:
If I use initial value ‘’ for the dropdown menu, then I start with nothing, which is normal.
Then whoever I selected from the dropdown gets their heatmap displayed… but then it’ll no longer update after that, no matter who else I picked.
And if I set the initial value to any other player, then only that graph for the initial player is shown, no new graphs are draw.
In other words, the webpage generated will show the first graph generated only. It doesn’t matter what I select from the dropdown menu, it remains the same graph, despite the data in the backend is getting updated and showing input was updated correctly.
I have run debugging tools and saw the new values from the selection are being used.
So both the heatmap_data
and new_df
below do get updated, but the new graph is not showing!
If I just print out the value of pitcher_dropdown from the parameter, it will also say the correct new pitcher has been selected, and their stats are also reflected in the data. But despite using the new data, the graph stays the same and refuses to update/redraw.
# TODO: fix graph not updating after selection!
# importing dependencies
import pandas as pd
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
import plotly.express as px
from . import ids, data
# input
players = data.PLAYERS # this is to keep the framework dynamic for updates, recall this is a ndarray of str!
player_default = data.BASEBALL['Pitcher'][0] # pick the first pitcher as default
df_default = data.BASEBALL[(data.BASEBALL['Pitcher'] == player_default)] # this is VERY important, as it gives a dataframe for use later
# Make data for merging later
pitch_types = data.BASEBALL['TaggedPitchType']
pitch_calls = data.BASEBALL['PitchCall']
def render(app: Dash) -> html.Div:
# interactive update to dropdown
@app.callback(
Output(ids.HEATMAP_UPDATE, "children"),
Input(ids.PLAYER_DROPDOWN, 'value')
)
# NOTE: the input is from the dropdown selection. It is currently a str, but needs to work with multiple players, which would be list[str]
def update_heatmap(pitcher_dropdown) -> html.Div:
heatmap_data = data.BASEBALL[(data.BASEBALL['Pitcher'] == pitcher_dropdown)][['TaggedPitchType','PitchCall','SpinRate']]
new_df = heatmap_data.groupby(["TaggedPitchType","PitchCall"])["SpinRate"].median().reset_index() # using median instead of count()
new_df = new_df.pivot(index='TaggedPitchType', columns='PitchCall')['SpinRate'].fillna(0)
fig = px.imshow(new_df)
fig.update_layout(
title=f'Heatmap of Pitcher {pitcher_dropdown.upper()}',
)
return html.Div(
dcc.Graph(figure=fig, id=ids.HEATMAP_UPDATE)
)
return html.Div(id=ids.HEATMAP_UPDATE)
if you want to see my dropdown file:
from dash import Dash, html, dcc
from dash.dependencies import Input, Output
from . import ids, data
def render(app: Dash) -> html.Div:
# declare variables first
all_players = data.PLAYERS
return html.Div(
children = [
html.H6("Pitchers"),
# dropdown function
dcc.Dropdown(
id = ids.PLAYER_DROPDOWN,
options = [{"label" : p, "value": p} for p in all_players], # list comprehension for selection options
value = all_players[0], # initial value of the dropdown
),
]
)
I tried looking up here on stackoverflow and other forums, haven’t seen any similar issues yet.
I tried using go.Figure() to manually create the heatmap to see if it’s a plotly express issue, it’s not because still no updates.
I looked up documentation but none of them help. The closest is update_traces() from go.Figure() but it’s not useful for what I need.
Here’s a truncated sample of the data in data.BASEBALL
, which is a dataframe read from a csv file using pandas:
Date | Pitcher | PitchCall | TaggedPitch | SpinRate | Others |
---|---|---|---|---|---|
2022/10/04 | Tom | Fastball | Strike | 2000 | NA |
2022/10/04 | Joe | Sinker | Ball | 1300 | NA |
Here’s the above table in dictionary format:
{'Date': {2022-10-04, 2022-10-04}, 'Pitcher': {'Tom', 'Joe'}, 'PitchCall': {'Fastball', 'Sinker'}, 'TaggedPitch' : {'Strike', 'Ball'}, 'SpinRate': {2000, 1300}, 'Others': {'', ''}}
The data.PLAYERS
is a ndarry of str from calling data.BASEBALL["Pitcher'].unique()
Edit:
My other files.
I’m using main to call other components. The layout handles the bundling. The ID file is just a collection of the ids for references.
My main.py
:
from dash import Dash, html
from dash_bootstrap_components.themes import BOOTSTRAP
from src.components.layout import create_layout
import src.components.data as data
def main() -> None:
app = Dash(external_stylesheets=[BOOTSTRAP]) #input can be a list
app.title = "Pitcher dashboard"
app.layout = create_layout(app)
app.run()
if __name__ == "__main__":
main()
My layout.py
:
from dash import Dash, html
from . import player_dropdown, heatmap_update # the "." means relative import
# call to the render file with the IDs, see *.py files in components
def create_layout(app: Dash) -> html.Div:
return html.Div(
className = "app-div",
children=[
html.H1(app.title),
html.Hr(),
# using render() here
html.Div(
className = "dropdown-container",
children = [
player_dropdown.render(app) # same name as the .py file
]
),
# add heatmap here
heatmap_update.render(app)
],
)
My ids.py
:
PLAYER_DROPDOWN = "player-dropdown"
HEATMAP_UPDATE = "heatmap-update"