In [34]:
In [38]:
# ---------- Load and Preprocess Data ----------
df = pd.read_csv("Compiled_Data.csv")
# Filter coordinates (Allamakee County bounding box)
df = df[
(df["Latitude"] >= 43.1) & (df["Latitude"] <= 43.5) &
(df["Longitude"] >= -91.7) & (df["Longitude"] <= -91.1)
].copy()
# Clean data
df = df.dropna(subset=["Latitude", "Longitude"])
df["Sex"] = df["Sex"].str.title()
df["Age"] = df["Age"].str.title()
# Include "Yearling" explicitly in Age options
all_ages = set(df["Age"].dropna().unique())
all_ages.add("Yearling")
age_options = [{"label": "All", "value": "All"}] + [
{"label": age, "value": age} for age in sorted(all_ages)
]
# Do NOT filter years — keep full range including 2025 and 2026
default_year = 2025
# Sex options
sex_options = [{"label": "All", "value": "All"}] + [
{"label": sex, "value": sex} for sex in sorted(df["Sex"].dropna().unique())
]
app = Dash(__name__)
def generate_timelapse_figure(filtered_df):
if filtered_df.empty:
return px.scatter_mapbox(
lat=[], lon=[], zoom=9, height=600
).update_layout(
mapbox_style="carto-positron",
title="No Data Available for Selected Filters"
)
fig = px.scatter_mapbox(
filtered_df,
lat="Latitude",
lon="Longitude",
color="Sex",
# symbol="Age", # Uncomment if you want different shapes for each age group
animation_frame="Year",
center={"lat": 43.3, "lon": -91.4},
zoom=9,
height=600,
hover_data={
"Sex": True,
"Age": True,
"Latitude": False,
"Longitude": False,
"Year": False
}
)
fig.update_layout(
mapbox_style="carto-positron",
title="CWD Marker Map - Allamakee County (Timelapse Starting 2025)"
)
return fig
# Initial figure includes all years starting at 2025
initial_figure = generate_timelapse_figure(df)
app.layout = html.Div([
html.H2("CWD Marker Map - Allamakee County, Iowa", style={"marginBottom": "20px"}),
html.Div([
html.Label("Filter by Age:", style={"fontWeight": "bold"}),
dcc.Dropdown(id="age-dropdown", options=age_options, value="All", clearable=False),
html.Br(),
html.Label("Filter by Sex:", style={"fontWeight": "bold"}),
dcc.Dropdown(id="sex-dropdown", options=sex_options, value="All", clearable=False),
html.Br(),
html.Button("Reset Filters", id="reset-button", n_clicks=0),
], style={"width": "25%", "display": "inline-block", "verticalAlign": "top"}),
html.Div([
dcc.Graph(id="heatmap", figure=initial_figure)
], style={"width": "70%", "display": "inline-block", "paddingLeft": "30px"})
])
@app.callback(
Output("heatmap", "figure"),
Input("age-dropdown", "value"),
Input("sex-dropdown", "value")
)
def update_heatmap(selected_age, selected_sex):
filtered_df = df.copy()
if selected_age != "All":
filtered_df = filtered_df[filtered_df["Age"] == selected_age]
if selected_sex != "All":
filtered_df = filtered_df[filtered_df["Sex"] == selected_sex]
return generate_timelapse_figure(filtered_df)
@app.callback(
Output("age-dropdown", "value"),
Output("sex-dropdown", "value"),
Input("reset-button", "n_clicks")
)
def reset_filters(n_clicks):
if n_clicks is None or n_clicks == 0:
raise dash.exceptions.PreventUpdate
return "All", "All"
if __name__ == "__main__":
app.run_server(debug=True, port=8030)
In [ ]: