{"spec_id":"pyramid-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\npyramid-basic: Basic Pyramid Chart\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-16\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\n\n# Theme-adaptive chrome (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\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\"\nGRID = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n\n# Imprint palette — Male is the first categorical series (brand green), Female lavender\nMALE_COLOR = \"#009E73\"\nFEMALE_COLOR = \"#C475FD\"\n\n# Data - Population pyramid showing age distribution by gender (in thousands)\nage_groups = [\"0-9\", \"10-19\", \"20-29\", \"30-39\", \"40-49\", \"50-59\", \"60-69\", \"70-79\", \"80+\"]\nmale_population = [45, 52, 68, 72, 65, 58, 48, 32, 18]\nfemale_population = [43, 50, 71, 75, 68, 62, 55, 42, 28]\n\n# Negative values place male bars on the left, female on the right of the shared axis\nsigned = [-x for x in male_population] + female_population\ndf = pd.DataFrame(\n    {\n        \"age\": age_groups * 2,\n        \"population\": signed,\n        \"gender\": [\"Male\"] * len(age_groups) + [\"Female\"] * len(age_groups),\n        # Absolute-value tip labels; nudge outward so text clears each bar end\n        \"label\": male_population + female_population,\n        \"label_pos\": [v - 5 for v in signed[: len(age_groups)]] + [v + 5 for v in signed[len(age_groups) :]],\n    }\n)\ndf[\"age\"] = pd.Categorical(df[\"age\"], categories=age_groups, ordered=True)\n\n# Storytelling: spotlight the dominant working-age cohorts (20-29, 30-39)\npeak = df[df[\"age\"].isin([\"20-29\", \"30-39\"])]\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"age\", y=\"population\", fill=\"gender\"))\n    + geom_bar(stat=\"identity\", width=0.8, color=PAGE_BG, size=0.4)\n    # Faint central reference line anchoring the two opposing sides\n    + geom_hline(yintercept=0, color=INK_SOFT, size=0.6, linetype=\"dashed\")\n    # Emphasis outline on the peak working-age cohorts to create a focal point\n    + geom_bar(data=peak, stat=\"identity\", width=0.8, fill=\"rgba(0,0,0,0)\", color=INK, size=0.9)\n    # Population value at each bar tip for direct read-off\n    + geom_text(aes(x=\"age\", y=\"label_pos\", label=\"label\"), inherit_aes=False, size=4, color=INK_SOFT)\n    + coord_flip()\n    + scale_fill_manual(values={\"Male\": MALE_COLOR, \"Female\": FEMALE_COLOR})\n    + scale_y_continuous(\n        breaks=[-80, -60, -40, -20, 0, 20, 40, 60, 80], labels=[\"80\", \"60\", \"40\", \"20\", \"0\", \"20\", \"40\", \"60\", \"80\"]\n    )\n    + labs(\n        x=\"Age Group\", y=\"Population (thousands)\", title=\"pyramid-basic · python · letsplot · anyplot.ai\", fill=\"Gender\"\n    )\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=GRID, size=0.3),\n        axis_title=element_text(size=13, color=INK),\n        axis_text=element_text(size=12, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT),\n        plot_title=element_text(size=16, color=INK),\n        legend_title=element_text(size=12, color=INK),\n        legend_text=element_text(size=11, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n    + ggsize(800, 450)\n)\n\n# Save PNG (scale 4x -> 3200 x 1800 px) and interactive HTML\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}