Skip to content

Instantly share code, notes, and snippets.

@cavedave
Last active January 10, 2026 18:45
Show Gist options
  • Select an option

  • Save cavedave/1656b3689e49453546d5d435052ad957 to your computer and use it in GitHub Desktop.

Select an option

Save cavedave/1656b3689e49453546d5d435052ad957 to your computer and use it in GitHub Desktop.
Cheesburgers are not possible before refrigeration and modern farming. The really soft burger buns were not very possible but around harvest time something approaching it was possible. Later breads got dryer. Cheese keeps itself but the mild cheese on cheeseburgers not that long unlike the cavey ones. In general it was not made until later in th…
import pandas as pd
from io import StringIO
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
csv_data = """Product,Start Date,End Date,Notes,Color
Bread (Wheat Flour),2024-01-01,2024-12-31,Grain can be stored year-round,#C2A14D
Bacon (Cured Pork),2024-01-01,2024-12-31,Pigs slaughtered in winter and meat cured for year-round storage,#C45A4A
Turkey,2024-11-01,2025-01-31,Traditional seasonal consumption around holidays,#E8D2C5
Lettuce,2024-05-15,2024-09-30,Cool-season crop; short shelf life before modern cooling,#4CAF50
Tomato,2024-07-20,2024-09-20,Frost-sensitive; outdoor field tomatoes only,#E53935
Mayonnaise (Eggs),2024-03-01,2024-09-30,Chicken egg laying is seasonal before artificial light,#FFD966"""
df = pd.read_csv(StringIO(csv_data), parse_dates=["Start Date", "End Date"])
df
# ----------------------------
# 1) Prep data
# ----------------------------
df["Start Date"] = pd.to_datetime(df["Start Date"])
df["End Date"] = pd.to_datetime(df["End Date"])
# Optional label cleanup (match your CSV names if needed)
df["Product"] = df["Product"].replace({
"Bread (Wheat Flour)": "Bread",
"Bacon (Cured Pork)": "Bacon",
"Mayonnaise (Eggs)": "Mayonnaise",
})
plot_year = 2024
# Force everything onto the same seasonal axis year (month alignment only)
df["Start Date"] = df["Start Date"].apply(lambda d: d.replace(year=plot_year))
df["End Date"] = df["End Date"].apply(lambda d: d.replace(year=plot_year))
# ---- Duplicate Bread into 3 layers ----
bread_rows = df[df["Product"].astype(str).eq("Bread")].copy()
if len(bread_rows) >= 1:
base = bread_rows.iloc[0].copy()
df = df[~df["Product"].astype(str).eq("Bread")].copy()
bread_layers = []
for name in ["Top bread", "Middle bread", "Bottom bread"]:
r = base.copy()
r["Product"] = name
bread_layers.append(r)
df = pd.concat([df, pd.DataFrame(bread_layers)], ignore_index=True)
# ---- Club order (top -> bottom) ----
club_order = [
"Top bread",
"Mayonnaise",
"Lettuce",
"Tomato",
"Bacon",
"Middle bread",
"Turkey",
"Bottom bread",
]
df["Product"] = pd.Categorical(df["Product"], categories=club_order, ordered=True)
df = df.dropna(subset=["Product"]).sort_values("Product").reset_index(drop=True)
# ----------------------------
# 2) Variable row heights (thickness)
# ----------------------------
layer_pct = {
"Top bread": 22,
"Middle bread": 18,
"Bottom bread": 22,
"Turkey": 28,
"Bacon": 10,
"Mayonnaise": 7,
"Lettuce": 9,
"Tomato": 9,
}
MIN_H = 6
row_height = [max(layer_pct.get(str(p), MIN_H), MIN_H) for p in df["Product"]]
gap = 1.8
y_centers = []
cursor = 0.0
for h in row_height:
y_centers.append(cursor + h / 2)
cursor += h + gap
total_height = cursor - gap
pad_top = 7.0
pad_bottom = 12.0
# ----------------------------
# 3) Plot
# ----------------------------
fig, ax = plt.subplots(figsize=(12, 5))
plt.figtext(
0.01, 0.02,
"Dates from Wikipedia; approximate for temperate North East USA\nChart by @iamreddave",
ha="left",
fontsize=9,
style="italic",
color="gray"
)
fig.subplots_adjust(left=0.10, right=0.98, top=0.88, bottom=0.28)
# Invert and add internal padding above/below stack
ax.set_ylim(total_height + pad_bottom, -pad_top)
def draw_bar(y, height, start, end, color, alpha=1.0):
ax.barh(
y=y,
width=(end - start).days,
left=start,
height=height,
color=color,
edgecolor="black",
alpha=alpha,
zorder=2
)
# Draw layers (square bars only)
for i in range(len(df)):
product = str(df.loc[i, "Product"])
color = df.loc[i, "Color"]
h = row_height[i]
y = y_centers[i]
start = df.loc[i, "Start Date"]
end = df.loc[i, "End Date"]
# (Optional) slight de-emphasis for year-round cured/stored items
bar_alpha = 0.9 if product in {"Bacon", "Top bread", "Middle bread", "Bottom bread"} else 1.0
if product == "Turkey":
# Wrap-around segments on one Jan–Dec axis
# Example: Nov 1 – Jan 31 -> Jan 1–Jan 31 + Nov 1–Dec 31
turkey_segments = [
(pd.Timestamp(f"{plot_year}-01-01"), pd.Timestamp(f"{plot_year}-01-31")),
(pd.Timestamp(f"{plot_year}-11-01"), pd.Timestamp(f"{plot_year}-12-31")),
]
for s, e in turkey_segments:
draw_bar(y, h, s, e, color, alpha=bar_alpha)
else:
draw_bar(y, h, start, end, color, alpha=bar_alpha)
# Y labels
ax.set_yticks(y_centers)
ax.set_yticklabels([str(x) for x in df["Product"]], fontsize=10)
# X axis
ax.set_xlim(pd.Timestamp(f"{plot_year}-01-01"), pd.Timestamp(f"{plot_year}-12-31"))
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b"))
ax.set_xlabel("Month")
# Title + subtitle
ax.set_title("Natural Availability of Club Sandwich Elements", fontsize=20, pad=14)
ax.text(
0.5, 1.005,
# "A club sandwich gathers ingredients naturally ready at different times",
"Dates reflect traditional outdoor local agriculture and preservation",
transform=ax.transAxes,
ha="center",
va="bottom",
fontsize=11,
color="dimgray"
)
# Overlap highlight (tune dates later if you want)
ax.axvspan(
pd.Timestamp(f"{plot_year}-08-20"),
pd.Timestamp(f"{plot_year}-09-10"),
color="gray",
alpha=0.18,
zorder=0
)
ax.text(
pd.Timestamp(f"{plot_year}-08-25"),
-0.10,
"Closest natural overlap",
transform=ax.get_xaxis_transform(),
fontsize=9,
color="gray",
ha="left",
va="top"
)
ax.xaxis.grid(True, linestyle="--", alpha=0.4)
plt.savefig("club_sandwich_seasonality.png", dpi=600)
plt.show()
Based on https://waldo.jaquith.org/blog/2011/12/impractical-cheeseburger/
On the impracticality of a cheeseburger.
which stuck in my brain for 15 years and came out as a visualisation
Cheesburgers are not possible before refrigeration and modern farming.
The really soft burger buns were not very possible but around harvest time something approaching it was possible.
Later breads got dryer. Cheese keeps itself but the mild cheese on cheeseburgers not that long unlike the cavey ones. \
In general it was not made until later in the year.
Beef can be had at anytime but in general it was kept ofr fattening during the summer.
import pandas as pd
from io import StringIO
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
csv_data = """Product,Start Date,End Date,Notes,Color
Sesame seeds,2024-09-10,2024-10-05,Warm-season annual; seed heads dry in early fall,#D2B48C
Soft bun,2024-08-01,2024-09-15,Spring wheat harvested late summer; represents grain readiness,#C2A14D
Lettuce,2024-05-15,2024-09-30,Cool-season crop; bolts in midsummer heat,#4CAF50
Onion,2024-07-15,2024-09-15,Bulb onions harvested once tops fall over,#C7B7D6
Tomato,2024-07-20,2024-09-20,Frost-sensitive; outdoor field tomatoes only,#E53935
Pickles,2024-01-01,2024-12-31,Harvested immature; heat-loving but short-lived,#6B8E23
"Cheese",2024-10-01,2025-03-31,Milk peaks May–Aug; aging creates delayed availability,#FFD966
Beef (pastured),2024-08-15,2024-10-31,Grass finishing at peak weight before winter,#8B4513
Bottom bun,2024-08-01,2024-09-15,Same harvest as top bun,#C2A14D
"""
df = pd.read_csv(StringIO(csv_data), parse_dates=["Start Date", "End Date"])
df
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import FancyBboxPatch, PathPatch
from matplotlib.path import Path
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import FancyBboxPatch, PathPatch
from matplotlib.path import Path
# ----------------------------
# 1) Prep data
# ----------------------------
df["Start Date"] = pd.to_datetime(df["Start Date"])
df["End Date"] = pd.to_datetime(df["End Date"])
df["Product"] = df["Product"].replace({
"Top bun (wheat)": "Soft bun",
"Bottom bun (wheat)": "Bottom bun",
"Cheese (aged, pasture milk)": "Cheese",
"Pickles (cucumbers)": "Pickles"
})
# Stretch pickles to all year (historically preserved)
pickle_mask = df["Product"] == "Pickles"
df.loc[pickle_mask, "Start Date"] = pd.Timestamp("2024-01-01")
df.loc[pickle_mask, "End Date"] = pd.Timestamp("2024-12-31")
burger_order = [
"Sesame seeds",
"Soft bun",
"Lettuce",
"Onion",
"Tomato",
"Pickles",
"Cheese",
"Beef (pastured)",
"Bottom bun",
]
df["Product"] = pd.Categorical(df["Product"], categories=burger_order, ordered=True)
df = df.dropna(subset=["Product"]).sort_values("Product").reset_index(drop=True)
plot_year = 2024
df["Start Date"] = df["Start Date"].apply(lambda d: d.replace(year=plot_year))
df["End Date"] = df["End Date"].apply(lambda d: d.replace(year=plot_year))
# ----------------------------
# 2) Variable row heights
# ----------------------------
layer_pct = {
"Soft bun": 32,
"Bottom bun": 21,
"Beef (pastured)": 24,
"Cheese": 7,
}
MIN_H = 5
row_height = [max(layer_pct.get(str(p), MIN_H), MIN_H) for p in df["Product"]]
gap = 1.5
y_centers = []
cursor = 0.0
for h in row_height:
y_centers.append(cursor + h / 2)
cursor += h + gap
total_height = cursor - gap # vertical span of stacked layers (data units)
# Add breathing room inside the axes: space above seeds + below bottom bun
pad_top = 6.0 # above sesame seeds (visual top)
pad_bottom = 10.0 # below bottom bun (visual bottom)
# ----------------------------
# 3) Plot
# ----------------------------
fig, ax = plt.subplots(figsize=(12, 5))
# Footer text (figure coords)
plt.figtext(
0.01, 0.01,
"Dates from Wikipedia; approximate for temperate North East USA\nChart by @iamreddave",
ha="left",
fontsize=9,
style="italic",
color="gray"
)
# Breathing room OUTSIDE the axes (top and bottom of the figure)
fig.subplots_adjust(left=0.10, right=0.98, top=0.88, bottom=0.28)
# IMPORTANT: invert y-axis BEFORE drawing patches
ax.set_ylim(total_height + pad_bottom, -pad_top)
def draw_bar(y, height, start, end, color, alpha=1.0):
ax.barh(
y=y,
width=(end - start).days,
left=start,
height=height,
color=color,
edgecolor="black",
alpha=alpha,
zorder=2
)
def draw_rounded_bar(y, height, start, end, color, alpha=1.0):
"""Fully rounded bar (patty-like)."""
x0 = mdates.date2num(start)
x1 = mdates.date2num(end)
width = x1 - x0
rounding = height * 0.45
patch = FancyBboxPatch(
(x0, y - height / 2),
width,
height,
boxstyle=f"round,pad=0,rounding_size={rounding}",
linewidth=1,
edgecolor="black",
facecolor=color,
alpha=alpha,
transform=ax.transData,
zorder=3
)
ax.add_patch(patch)
def draw_one_sided_rounded_bar(y, height, start, end, color, alpha=1.0, which="top"):
"""
Round ONLY the *visual* top corners (which='top') or *visual* bottom corners (which='bottom').
Since we set_ylim(..., ...) above, ax.yaxis_inverted() is True during drawing.
"""
effective = which
if ax.yaxis_inverted():
effective = "bottom" if which == "top" else "top"
x0 = mdates.date2num(start)
x1 = mdates.date2num(end)
w = x1 - x0
h = height
y0 = y - h / 2
y1 = y + h / 2
r = min(h * 0.45, w * 0.25)
verts, codes = [], []
if effective == "top":
# Flat bottom, rounded TOP corners
verts.append((x0, y0)); codes.append(Path.MOVETO)
verts.append((x1, y0)); codes.append(Path.LINETO)
verts.append((x1, y1 - r)); codes.append(Path.LINETO)
verts.append((x1, y1)); codes.append(Path.CURVE3)
verts.append((x1 - r, y1)); codes.append(Path.CURVE3)
verts.append((x0 + r, y1)); codes.append(Path.LINETO)
verts.append((x0, y1)); codes.append(Path.CURVE3)
verts.append((x0, y1 - r)); codes.append(Path.CURVE3)
verts.append((x0, y0)); codes.append(Path.LINETO)
elif effective == "bottom":
# Flat top, rounded BOTTOM corners
verts.append((x0, y1)); codes.append(Path.MOVETO)
verts.append((x1, y1)); codes.append(Path.LINETO)
verts.append((x1, y0 + r)); codes.append(Path.LINETO)
verts.append((x1, y0)); codes.append(Path.CURVE3)
verts.append((x1 - r, y0)); codes.append(Path.CURVE3)
verts.append((x0 + r, y0)); codes.append(Path.LINETO)
verts.append((x0, y0)); codes.append(Path.CURVE3)
verts.append((x0, y0 + r)); codes.append(Path.CURVE3)
verts.append((x0, y1)); codes.append(Path.LINETO)
else:
raise ValueError("which must be 'top' or 'bottom'")
verts.append((0, 0)); codes.append(Path.CLOSEPOLY)
patch = PathPatch(
Path(verts, codes),
facecolor=color,
edgecolor="black",
linewidth=1,
alpha=alpha,
transform=ax.transData,
zorder=3
)
ax.add_patch(patch)
def draw_seed_cluster(y, height, start, end, color, n_seeds=4, alpha=1.0):
"""Draw multiple small rounded rectangles to suggest sesame seeds."""
x0 = mdates.date2num(start)
x1 = mdates.date2num(end)
total_width = x1 - x0
seed_width = total_width / (n_seeds * 1.6)
seed_height = height * 0.45 # slightly bigger
y_jitter = height * 0.18
rounding = seed_height * 0.75
for i in range(n_seeds):
sx = x0 + (i + 0.5) * total_width / n_seeds - seed_width / 2
sy = y + ((-1) ** i) * y_jitter * 0.5
patch = FancyBboxPatch(
(sx, sy - seed_height / 2),
seed_width,
seed_height,
boxstyle=f"round,pad=0,rounding_size={rounding}",
facecolor=color,
edgecolor="black",
linewidth=0.8,
alpha=alpha,
transform=ax.transData,
zorder=4
)
ax.add_patch(patch)
# ----------------------------
# Draw layers
# ----------------------------
for i in range(len(df)):
product = str(df.loc[i, "Product"])
color = df.loc[i, "Color"]
h = row_height[i]
y = y_centers[i]
bar_alpha = 0.6 if product == "Pickles" else 1.0
if product == "Cheese":
cheese_segments = [
(pd.Timestamp(f"{plot_year}-01-01"), pd.Timestamp(f"{plot_year}-04-30")),
(pd.Timestamp(f"{plot_year}-10-01"), pd.Timestamp(f"{plot_year}-12-31")),
]
for s, e in cheese_segments:
draw_bar(y, h, s, e, color, alpha=bar_alpha)
elif product == "Beef (pastured)":
start = df.loc[i, "Start Date"]
end = df.loc[i, "End Date"]
draw_rounded_bar(y, h, start, end, color, alpha=bar_alpha)
elif product == "Soft bun":
start = df.loc[i, "Start Date"]
end = df.loc[i, "End Date"]
draw_one_sided_rounded_bar(y, h, start, end, color, alpha=bar_alpha, which="top")
elif product == "Bottom bun":
start = df.loc[i, "Start Date"]
end = df.loc[i, "End Date"]
draw_one_sided_rounded_bar(y, h, start, end, color, alpha=bar_alpha, which="bottom")
elif product == "Sesame seeds":
start = df.loc[i, "Start Date"]
end = df.loc[i, "End Date"]
draw_seed_cluster(y, h, start, end, color, n_seeds=4, alpha=1.0)
else:
start = df.loc[i, "Start Date"]
end = df.loc[i, "End Date"]
draw_bar(y, h, start, end, color, alpha=bar_alpha)
# Y labels
ax.set_yticks(y_centers)
ax.set_yticklabels(df["Product"], fontsize=10)
# X axis
ax.set_xlim(pd.Timestamp(f"{plot_year}-01-01"), pd.Timestamp(f"{plot_year}-12-31"))
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b"))
ax.set_xlabel("Month")
# Title + subtitle
ax.set_title("Natural Seasonal Availability of a Cheeseburger", fontsize=20, pad=14)
ax.text(
0.5, 1.005,
"A cheeseburger gathers ingredients naturally ready at different times",
transform=ax.transAxes,
ha="center",
va="bottom",
fontsize=11,
color="dimgray"
)
# Overlap highlight (slightly darker)
ax.axvspan(
pd.Timestamp(f"{plot_year}-08-20"),
pd.Timestamp(f"{plot_year}-09-10"),
color="gray",
alpha=0.18,
zorder=0
)
# Overlap label in the bottom margin
ax.text(
pd.Timestamp(f"{plot_year}-08-25"),
-0.10,
"Closest natural overlap",
transform=ax.get_xaxis_transform(),
fontsize=9,
color="gray",
ha="left",
va="top"
)
ax.xaxis.grid(True, linestyle="--", alpha=0.4)
plt.savefig("cheeseburger_seasonality.png", dpi=600)
plt.show()
@cavedave
Copy link
Author

cavedave commented Jan 7, 2026

image

@cavedave
Copy link
Author

cavedave commented Jan 8, 2026

cheeseburger_seasonality

@cavedave
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment