{"spec_id":"pyramid-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\npyramid-basic: Basic Pyramid Chart\nLibrary: plotnine 0.15.3 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-04-29\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path[0:1] = []  # Prevent script dir from shadowing the plotnine package\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_flip,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_col,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\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\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data\nage_groups = [\"0-9\", \"10-19\", \"20-29\", \"30-39\", \"40-49\", \"50-59\", \"60-69\", \"70-79\", \"80+\"]\nmale_pop = [4200, 4500, 5100, 5800, 6200, 5500, 4100, 2800, 1200]\nfemale_pop = [4000, 4300, 5000, 5600, 6000, 5700, 4500, 3200, 1800]\n\ndf = pd.DataFrame(\n    {\n        \"age_group\": age_groups * 2,\n        \"population\": [-m for m in male_pop] + female_pop,\n        \"gender\": [\"Male\"] * len(age_groups) + [\"Female\"] * len(age_groups),\n    }\n)\n\ndf[\"age_group\"] = pd.Categorical(df[\"age_group\"], categories=age_groups, ordered=True)\n# Male first in legend — matches left-side visual position\ndf[\"gender\"] = pd.Categorical(df[\"gender\"], categories=[\"Male\", \"Female\"], ordered=True)\n\nanyplot_theme = theme(\n    figure_size=(16, 9),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),\n    panel_grid_minor=element_blank(),\n    panel_border=element_blank(),\n    axis_line=element_line(color=INK_SOFT),\n    axis_title=element_text(color=INK, size=20),\n    axis_text=element_text(color=INK_SOFT, size=16),\n    axis_text_x=element_text(color=INK_SOFT, size=16),\n    axis_text_y=element_text(color=INK_SOFT, size=14),\n    plot_title=element_text(color=INK, size=24),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=16),\n    legend_title=element_text(color=INK, size=18),\n    legend_position=\"bottom\",\n)\n\n# Labels for the peak 40-49 row to emphasise the working-age bulge\ndf_peak = df[df[\"age_group\"] == \"40-49\"].copy()\ndf_peak[\"label\"] = df_peak[\"population\"].apply(lambda v: f\"{abs(v):,}\")\n# Position labels just outside each bar end (negative side left, positive side right)\ndf_peak[\"label_y\"] = df_peak[\"population\"].apply(lambda v: v * 1.08)\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"age_group\", y=\"population\", fill=\"gender\"))\n    + geom_col(width=0.85)\n    + geom_text(\n        data=df_peak, mapping=aes(x=\"age_group\", y=\"label_y\", label=\"label\"), color=INK_SOFT, size=13, inherit_aes=False\n    )\n    + scale_fill_manual(values={\"Male\": IMPRINT[0], \"Female\": IMPRINT[1]})\n    + scale_y_continuous(labels=lambda breaks: [f\"{abs(int(b)):,}\" for b in breaks])\n    + labs(\n        x=\"Age Group\",\n        y=\"Population (thousands)\",\n        title=\"Population by Age & Gender · pyramid-basic · plotnine · anyplot.ai\",\n        fill=\"Gender\",\n    )\n    + theme_minimal()\n    + anyplot_theme\n    + coord_flip()\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}