I have workouts logged in JSON like this:
[
{
"date": "2025-07-14",
"workout_name": "Lower",
"exercises": [
{
"name": "Barbell Back Squat",
"type": "",
"sets": [
{
"reps": 3,
"weight": 85.0
},
{
"reps": 3,
"weight": 85.0
}
]
},
{
"name": "Barbell Deadlift",
"type": "",
"sets": [
{
"reps": 3,
"weight": 115.0
},
{
"reps": 3,
"weight": 115.0
}
]
}
],
"notes": "submax but still left me sore"
},
I'm visualising them in a dashboard with streamlit, and I have a plotly.graph_objects.Bar chart showing the last 7 days, below is another version I made with matplotlib.pyplot that illustrates how it should look.

But since I want a drill-through effect using streamlit, I'm now trying to recreate this using graph objects, and it will not adjust the bar height to the values in my dataframe, nor will it place bars only where non-0 values are present, it appears to be visualising an incremental index, even though I definitely have the y axis values pointing to my totals.
That is, it appears to be populating them with [0,1,2,3,4,5,6] rather than the actual values of [0,0,1200,0,3286,0,0].
Here's the Python I have to place my JSON in a pandas DataFrame:
# Load and flatten workout data
with open("workout_log.json") as f:
raw_data = json.load(f)
rows = []
notes_by_date = {}
for entry in raw_data:
date = pd.to_datetime(entry["date"]) # Use full Timestamp objects
notes_by_date[date.normalize()] = entry.get("notes", "") # Normalize to midnight for matching
for ex in entry["exercises"]:
for s in ex["sets"]:
rows.append({
"date": date.normalize(), # Store as normalized Timestamp
"workout": entry.get("workout_name", ""),
"exercise": ex["name"],
"type": ex.get("type", ""),
"reps": s["reps"],
"weight": s["weight"]
})
df = pd.DataFrame(rows)
And here's the Python that calculates this new "volume per day" metric and places that in a new DataFrame attached to the last 7 days:
# Ensure last 7 days are always present, even if rest days
today = pd.Timestamp.today().normalize()
last_7_days = [today - pd.Timedelta(days=i) for i in range(6, -1, -1)]
volume_by_day = (
df.groupby("date")
.apply(lambda x: (x["reps"] * x["weight"]).sum())
.reindex(last_7_days, fill_value=0)
)
df_volume = volume_by_day.reset_index()
df_volume.columns = ["date", "volume"]
df_volume = df_volume.set_index("date")
And this code generates the chart:
fig = go.Figure()
fig.add_trace(go.Bar(
y=df_volume.index,
x=df_volume['volume'],
marker_color='indigo'
))
fig.update_layout(
title="🏋️ Training Volume — Last 7 Days",
yaxis_title="Date",
xaxis_title="Total Volume (kg)",
xaxis_tickangle=-45,
height=400
)
I've changed the x and y axis around, I've renamed the index, I've renamed the values column, I've made the x and y axis point directly at this df:
volume_by_day = (
df.groupby("date")
.apply(lambda x: (x["reps"] * x["weight"]).sum())
.reindex(last_7_days, fill_value=0)
)
rather than making a new one. I've dictated the axis heights. Whatever I do, the height of the bars renders as [0,1,2,3,4,5,6].
For clarity, here is the result of the graph_objects.Bar in my dash, and I displayed the dataframe above it to illustrate the actual values:
