{"spec_id":"bar-heart-rate-zones","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nbar-heart-rate-zones: Time in Heart Rate Zones Bar Chart\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 87/100 | Created: 2026-06-14\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Zone colors — semantic mapping per spec (conventional fitness zone colors)\n# Z1 grey   → Imprint muted anchor (theme-adaptive)\n# Z2 blue   → Imprint #4467A3\n# Z3 green  → Imprint #009E73 (brand green; aerobic / moderate effort)\n# Z4 orange → Imprint #BD8233 (ochre, nearest to orange in Imprint palette)\n# Z5 red    → Imprint #AE3030 (matte red, semantic anchor for max / critical)\nZONE_COLORS = [INK_MUTED, \"#4467A3\", \"#009E73\", \"#BD8233\", \"#AE3030\"]\n\n# Data — 60-minute tempo run: most time near threshold (Z4)\nzones = [\"Z1\", \"Z2\", \"Z3\", \"Z4\", \"Z5\"]\nzone_names = [\"Recovery\", \"Endurance\", \"Aerobic\", \"Threshold\", \"Maximum\"]\nminutes = [5, 10, 18, 20, 7]\nhr_ranges = [\"< 111 bpm\", \"111–130 bpm\", \"130–148 bpm\", \"148–166 bpm\", \"> 166 bpm\"]\n\n# Multi-line x-tick labels: zone ID / zone name / HR boundary\nx_tick_labels = [f\"{z}\\n{n}\\n{h}\" for z, n, h in zip(zones, zone_names, hr_ranges, strict=False)]\n\n# Palette dict for sns.barplot hue mapping\nzone_palette = dict(zip(zones, ZONE_COLORS, strict=False))\n\n# Seaborn theme\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# DataFrame for seaborn — categorical order preserves Z1→Z5 sequence\ndf = pd.DataFrame({\"zone\": pd.Categorical(zones, categories=zones, ordered=True), \"minutes\": minutes})\n\n# Plot — landscape 3200 × 1800 px\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# sns.barplot — seaborn-native bar chart with hue for per-bar palette\nsns.barplot(\n    data=df, x=\"zone\", y=\"minutes\", hue=\"zone\", palette=zone_palette, ax=ax, errorbar=None, legend=False, width=0.55\n)\n\n# Apply edge color to match page background (post-draw, patches are accessible)\nfor patch in ax.patches:\n    patch.set_edgecolor(PAGE_BG)\n    patch.set_linewidth(1.2)\n\n# X-tick labels (zone ID / name / HR boundary) — restore after barplot\nax.set_xticks(range(len(zones)))\nax.set_xticklabels(x_tick_labels, fontsize=8)\nax.tick_params(axis=\"x\", which=\"both\", length=0)\n\n# Y-tick labels\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\n\n# Bar duration labels\nfor patch, mins in zip(ax.patches, minutes, strict=False):\n    ax.text(\n        patch.get_x() + patch.get_width() / 2,\n        patch.get_height() + 0.4,\n        f\"{mins} min\",\n        ha=\"center\",\n        va=\"bottom\",\n        fontsize=9,\n        fontweight=\"bold\",\n        color=INK,\n    )\n\n# Title and axis labels\ntitle = \"bar-heart-rate-zones · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=12)\nax.set_xlabel(\"Heart Rate Zone\", fontsize=10, color=INK, labelpad=10)\nax.set_ylabel(\"Time (minutes)\", fontsize=10, color=INK, labelpad=8)\n\n# Y-axis grid\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_axisbelow(True)\n\n# Spines\nsns.despine(ax=ax, top=True, right=True)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Y-axis range — headroom for bar labels\nax.set_ylim(0, max(minutes) * 1.3)\n\n# Layout\nfig.subplots_adjust(left=0.09, right=0.97, top=0.93, bottom=0.22)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}