{"spec_id":"pyramid-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\npyramid-basic: Basic Pyramid Chart\nLibrary: altair 6.3.0 | Python 3.14.9\nQuality: 83/100 | Updated: 2026-11-01\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\n\n# Drop script directory from sys.path so the `altair` package resolves, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\nalt = importlib.import_module(\"altair\")\nnp = importlib.import_module(\"numpy\")\npd = importlib.import_module(\"pandas\")\nImage = importlib.import_module(\"PIL.Image\")\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\"\n\n# Imprint palette: all eight positions in canonical order\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Columns: set these to your table's column names\nAGE_GROUP = \"Age group\"  # role: category\nMALE = \"Male\"  # role: value_left\nFEMALE = \"Female\"  # role: value_right\nSIDE = \"Gender\"  # role: extra\n\n# Data: replace this block with your own table, for example df = pd.read_csv(\"population.csv\")\ndf = pd.DataFrame(\n    {\n        AGE_GROUP: [\"0-9\", \"10-19\", \"20-29\", \"30-39\", \"40-49\", \"50-59\", \"60-69\", \"70-79\", \"80+\"],\n        MALE: [4.8, 5.2, 6.1, 7.3, 8.5, 7.8, 5.9, 3.2, 1.2],\n        FEMALE: [4.5, 5.0, 6.3, 7.5, 8.7, 8.2, 6.8, 4.8, 2.5],\n    }\n)\n\n# Plot: reads only df and the constants above\nESCAPE = str.maketrans({\".\": \"\\\\.\", \"[\": \"\\\\[\", \"]\": \"\\\\]\", \":\": \"\\\\:\"})\nVALUE = \"Population (millions)\"\nSIGNED = \"Signed value\"\n\nsides = [MALE, FEMALE]\nlong = df.melt(id_vars=AGE_GROUP, value_vars=sides, var_name=SIDE, value_name=VALUE)\nlong[SIGNED] = np.where(long[SIDE] == MALE, -long[VALUE], long[VALUE])\nlimit = float(np.ceil(long[VALUE].max() / 2) * 2 + 2)\n\n# Interactive highlight — click legend to isolate a side\nselection = alt.selection_point(fields=[SIDE], bind=\"legend\")\n\nchart = (\n    alt.Chart(long)\n    .mark_bar(cornerRadiusEnd=2)\n    .encode(\n        y=alt.Y(\n            AGE_GROUP.translate(ESCAPE),\n            type=\"ordinal\",\n            title=AGE_GROUP,\n            sort=\"descending\",\n            axis=alt.Axis(titleFontSize=12, labelFontSize=10),\n        ),\n        x=alt.X(\n            SIGNED.translate(ESCAPE),\n            type=\"quantitative\",\n            title=VALUE,\n            axis=alt.Axis(titleFontSize=12, labelFontSize=10, labelExpr=\"abs(datum.value)\", tickMinStep=2),\n            scale=alt.Scale(domain=[-limit, limit], nice=False),\n        ),\n        color=alt.Color(\n            SIDE.translate(ESCAPE),\n            type=\"nominal\",\n            title=SIDE,\n            sort=None,\n            scale=alt.Scale(domain=sides, range=IMPRINT_PALETTE[: len(sides)]),\n            legend=alt.Legend(titleFontSize=10, labelFontSize=10, orient=\"bottom\"),\n        ),\n        opacity=alt.condition(selection, alt.value(0.9), alt.value(0.25)),\n        tooltip=[\n            alt.Tooltip(SIDE.translate(ESCAPE), type=\"nominal\", title=SIDE),\n            alt.Tooltip(AGE_GROUP.translate(ESCAPE), type=\"ordinal\", title=AGE_GROUP),\n            alt.Tooltip(VALUE.translate(ESCAPE), type=\"quantitative\", title=VALUE, format=\".1f\"),\n        ],\n    )\n    .add_params(selection)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(text=\"pyramid-basic · altair · anyplot.ai\", fontSize=16, anchor=\"middle\"),\n    )\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(\n        domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.10, labelColor=INK_SOFT, titleColor=INK\n    )\n    .configure_title(color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK, padding=6)\n)\n\n# Save\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n\n# Pad (never crop) to the canonical 3200 x 1800 canvas\nTARGET_W, TARGET_H = 3200, 1800\nimg = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\nw, h = img.size\nif w > TARGET_W or h > TARGET_H:\n    raise SystemExit(f\"altair produced {w}x{h}, exceeds {TARGET_W}x{TARGET_H}; shrink width/height\")\nif (w, h) != (TARGET_W, TARGET_H):\n    canvas = Image.new(\"RGB\", (TARGET_W, TARGET_H), PAGE_BG)\n    canvas.paste(img, ((TARGET_W - w) // 2, (TARGET_H - h) // 2))\n    canvas.save(f\"plot-{THEME}.png\")\n"}