{"spec_id":"pyramid-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// pyramid-basic: Basic Pyramid Chart\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\nconst ageGroups = [\"0-9\", \"10-19\", \"20-29\", \"30-39\", \"40-49\", \"50-59\", \"60-69\", \"70-79\", \"80+\"];\nconst malePopulation = [42, 45, 40, 38, 41, 35, 28, 15, 6];\nconst femalePopulation = [40, 43, 39, 39, 42, 37, 31, 19, 9];\nconst axisMax = 50;\n\n// Oldest two cohorts show the widest female/male gap — give them a subtle\n// accent border so the eye lands on the chart's own insight.\nconst highlightedRows = new Set([7, 8]);\nconst accentBorder = (color) => ageGroups.map((_, i) => (highlightedRows.has(i) ? color : \"transparent\"));\nconst accentWidth = ageGroups.map((_, i) => (highlightedRows.has(i) ? 2 : 0));\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart.js-specific plugin --------------------------------------------\n// A small local plugin (Chart.js's own extensibility hook, not a cross-library\n// pattern) draws a dashed center axis line plus a bracket + label calling out\n// the widening gender gap in the two oldest cohorts.\nconst pyramidAnnotations = {\n  id: \"pyramidAnnotations\",\n  afterDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    const centerX = scales.x.getPixelForValue(0);\n\n    ctx.save();\n\n    ctx.setLineDash([4, 4]);\n    ctx.strokeStyle = t.grid;\n    ctx.lineWidth = 1;\n    ctx.beginPath();\n    ctx.moveTo(centerX, chartArea.top);\n    ctx.lineTo(centerX, chartArea.bottom);\n    ctx.stroke();\n    ctx.setLineDash([]);\n\n    const rowHeight = scales.y.getPixelForTick(1) - scales.y.getPixelForTick(0);\n    const topY = scales.y.getPixelForTick(7) - rowHeight / 2;\n    const bottomY = scales.y.getPixelForTick(8) + rowHeight / 2;\n    const bracketX = chartArea.right + 10;\n\n    ctx.strokeStyle = t.amber;\n    ctx.lineWidth = 2;\n    ctx.beginPath();\n    ctx.moveTo(bracketX - 6, topY);\n    ctx.lineTo(bracketX, topY);\n    ctx.lineTo(bracketX, bottomY);\n    ctx.lineTo(bracketX - 6, bottomY);\n    ctx.stroke();\n\n    ctx.fillStyle = t.amber;\n    ctx.font = \"600 13px sans-serif\";\n    ctx.textAlign = \"left\";\n    ctx.textBaseline = \"middle\";\n    ctx.fillText(\"gap widens\", bracketX + 8, (topY + bottomY) / 2);\n\n    ctx.restore();\n  },\n};\n\n// --- Chart -------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"bar\",\n  data: {\n    labels: ageGroups,\n    datasets: [\n      {\n        label: \"Male\",\n        data: malePopulation.map((v) => -v),\n        backgroundColor: t.palette[0],\n        borderColor: accentBorder(t.amber),\n        borderWidth: accentWidth,\n        borderRadius: 4,\n        categoryPercentage: 0.85,\n        barPercentage: 0.9,\n      },\n      {\n        label: \"Female\",\n        data: femalePopulation,\n        backgroundColor: t.palette[1],\n        borderColor: accentBorder(t.amber),\n        borderWidth: accentWidth,\n        borderRadius: 4,\n        categoryPercentage: 0.85,\n        barPercentage: 0.9,\n      },\n    ],\n  },\n  plugins: [pyramidAnnotations],\n  options: {\n    indexAxis: \"y\",\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 0, right: 100 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"pyramid-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"600\" },\n      },\n      subtitle: {\n        display: true,\n        text: \"Female population increasingly exceeds male past age 70\",\n        color: t.inkSoft,\n        font: { size: 14, style: \"italic\" },\n        padding: { bottom: 8 },\n      },\n      legend: {\n        position: \"top\",\n        labels: { color: t.ink, font: { size: 16 } },\n        padding: 8,\n      },\n      tooltip: {\n        callbacks: {\n          label: (ctx) => `${ctx.dataset.label}: ${Math.abs(ctx.parsed.x)}k`,\n        },\n      },\n    },\n    scales: {\n      x: {\n        stacked: true,\n        min: -axisMax,\n        max: axisMax,\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 10,\n          callback: (value) => Math.abs(value),\n        },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Population (thousands)\", color: t.ink, font: { size: 18 } },\n      },\n      y: {\n        stacked: true,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n        title: { display: true, text: \"Age Group\", color: t.ink, font: { size: 18 } },\n      },\n    },\n  },\n});\n"}