{"spec_id":"heatmap-stripes-climate","library":"echarts","language":"javascript","code":"// anyplot.ai\n// heatmap-stripes-climate: Climate Warming Stripes\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 87/100 | Created: 2026-08-25\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Deterministic LCG so the trend + noise are reproducible without a browser RNG.\nfunction lcg(seed) {\n  let state = seed;\n  return function () {\n    state = (state * 1103515245 + 12345) & 0x7fffffff;\n    return state / 0x7fffffff;\n  };\n}\nconst rand = lcg(42);\n\nconst startYear = 1850;\nconst endYear = 2024;\nconst years = [];\nconst anomalies = [];\nfor (let year = startYear; year <= endYear; year++) {\n  const progress = (year - startYear) / (endYear - startYear);\n  // Cool, flat pre-industrial baseline that accelerates into a modern warm tail.\n  const trend = -0.35 + 1.55 * Math.pow(progress, 2.2);\n  const noise = (rand() - 0.5) * 0.25;\n  years.push(year);\n  anomalies.push(Math.round((trend + noise) * 100) / 100);\n}\n\n// Symmetric domain around zero so equal +/- anomalies get equal color intensity.\nconst maxAbsAnomaly = Math.max(...anomalies.map((a) => Math.abs(a)));\n\n// --- Diverging color ramp (Imprint imprint_div, cold -> mid -> warm) --------\nfunction hexToRgb(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\nfunction rgbToHex(rgb) {\n  return (\n    \"#\" +\n    rgb\n      .map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, \"0\"))\n      .join(\"\")\n  );\n}\nfunction lerpColor(hexA, hexB, ratio) {\n  const a = hexToRgb(hexA);\n  const b = hexToRgb(hexB);\n  return rgbToHex([\n    a[0] + (b[0] - a[0]) * ratio,\n    a[1] + (b[1] - a[1]) * ratio,\n    a[2] + (b[2] - a[2]) * ratio,\n  ]);\n}\n\n// t.div = [matte-red, midpoint, blue] (imprint_div); reorder cold -> warm for temperature.\nconst coldColor = t.div[2];\nconst warmColor = t.div[0];\n// Fixed pale-neutral midpoint (not t.div[1]): the bars fill the entire canvas\n// with no gaps, so a theme-adaptive midpoint would match the dark background\n// exactly and vanish. A constant off-white keeps the near-zero band visible\n// in both themes while staying identical across renders.\nconst midColor = \"#FAF8F1\";\n\nfunction colorForAnomaly(anomaly) {\n  const norm = Math.max(-1, Math.min(1, anomaly / maxAbsAnomaly));\n  return norm < 0 ? lerpColor(midColor, coldColor, -norm) : lerpColor(midColor, warmColor, norm);\n}\n\nconst stripeData = years.map((year, i) => ({\n  value: 1,\n  itemStyle: { color: colorForAnomaly(anomalies[i]) },\n  year,\n  anomaly: anomalies[i],\n}));\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"heatmap-stripes-climate · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  grid: { left: 0, right: 0, top: 90, bottom: 0 },\n  xAxis: {\n    type: \"category\",\n    data: years.map(String),\n    show: false,\n  },\n  yAxis: {\n    type: \"value\",\n    min: 0,\n    max: 1,\n    show: false,\n  },\n  tooltip: {\n    trigger: \"item\",\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink },\n    formatter: (params) => {\n      const anomaly = params.data.anomaly;\n      const sign = anomaly >= 0 ? \"+\" : \"\";\n      return `${params.data.year}: ${sign}${anomaly.toFixed(2)}°C`;\n    },\n  },\n  series: [\n    {\n      type: \"bar\",\n      data: stripeData,\n      barCategoryGap: \"0%\",\n      barWidth: \"100%\",\n    },\n  ],\n});\n"}