{"spec_id":"heatmap-stripes-climate","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// heatmap-stripes-climate: Climate Warming Stripes\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-08-25\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Synthetic global-mean temperature anomaly, °C relative to a 1961-1990\n// baseline, 1850-2024 (175 years) — accelerating-warming shape matches the\n// well-known HadCRUT / NASA GISS instrumental record without reproducing it.\nconst START_YEAR = 1850;\nconst END_YEAR = 2024;\nconst years = [];\nfor (let year = START_YEAR; year <= END_YEAR; year++) years.push(year);\n\nfunction lcg(seed) {\n  let state = seed >>> 0;\n  return function random() {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst random = lcg(42);\n\nconst anomalies = years.map((year) => {\n  const progress = (year - START_YEAR) / (END_YEAR - START_YEAR);\n  const trend = -0.4 + 1.65 * Math.pow(progress, 2.1);\n  const noise = (random() - 0.5) * 0.22;\n  return trend + noise;\n});\nconst maxAbsAnomaly = Math.max(...anomalies.map(Math.abs));\n\n// --- Diverging color scale (imprint_div, symmetric around zero) ------------\nfunction lerpColor(hexA, hexB, fraction) {\n  const a = parseInt(hexA.slice(1), 16);\n  const b = parseInt(hexB.slice(1), 16);\n  const channel = (shift) => {\n    const ca = (a >> shift) & 255;\n    const cb = (b >> shift) & 255;\n    return Math.round(ca + (cb - ca) * fraction);\n  };\n  return `rgb(${channel(16)}, ${channel(8)}, ${channel(0)})`;\n}\nconst [warmStop, midStop, coolStop] = t.div; // [\"#AE3030\", pageBg, \"#4467A3\"]\nfunction anomalyColor(anomaly) {\n  const fraction = Math.max(-1, Math.min(1, anomaly / maxAbsAnomaly));\n  return fraction >= 0\n    ? lerpColor(midStop, warmStop, fraction)\n    : lerpColor(midStop, coolStop, -fraction);\n}\nconst stripeColors = anomalies.map(anomalyColor);\n\n// --- Stripes plugin: draws the stripes via a direct chartjs draw hook ------\n// Chart.js's plugin system — a registered object with lifecycle hooks (here\n// beforeDatasetsDraw) that gets direct 2D-context access to chart.ctx and\n// chart.chartArea — is a chartjs-distinctive extensibility mechanism, unlike\n// the generic bar-gap-removal trick any bar-capable library can replicate.\nconst stripesPlugin = {\n  id: \"stripes\",\n  beforeDatasetsDraw(chart) {\n    const { ctx, chartArea } = chart;\n    const { left, right, top, bottom } = chartArea;\n    const stripeWidth = (right - left) / stripeColors.length;\n    ctx.save();\n    stripeColors.forEach((color, i) => {\n      ctx.fillStyle = color;\n      ctx.fillRect(left + i * stripeWidth, top, stripeWidth + 1, bottom - top);\n    });\n    ctx.restore();\n  },\n};\nChart.register(stripesPlugin);\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"bar\",\n  data: {\n    labels: years.map(String),\n    datasets: [\n      {\n        data: years.map(() => 1),\n        backgroundColor: \"rgba(0, 0, 0, 0)\",\n        borderWidth: 0,\n        categoryPercentage: 1.0,\n        barPercentage: 1.0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 4, bottom: 4, left: 0, right: 0 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"heatmap-stripes-climate · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n        padding: { bottom: 10 },\n      },\n      legend: { display: false },\n      tooltip: {\n        callbacks: {\n          title: (items) => `Year ${years[items[0].dataIndex]}`,\n          label: (item) => `Anomaly: ${anomalies[item.dataIndex].toFixed(2)}°C`,\n        },\n      },\n    },\n    scales: {\n      x: { display: false, grid: { display: false } },\n      y: { display: false, grid: { display: false }, min: 0, max: 1 },\n    },\n  },\n});\n"}