{"spec_id":"heatmap-annotated","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// heatmap-annotated: Annotated Heatmap\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 92/100 | Created: 2026-08-05\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: pairwise correlation matrix across macroeconomic indicators -----\nconst LABELS = [\n  'Inflation', 'Unemployment', 'Interest Rate', 'GDP Growth',\n  'Consumer Conf.', 'Stock Index', 'Housing Starts',\n];\nconst N = LABELS.length;\n\n// Symmetric correlation matrix, diagonal = 1.00\nconst CORR = [\n  [ 1.00, -0.32,  0.68, -0.41, -0.55, -0.38, -0.29],\n  [-0.32,  1.00, -0.24, -0.71, -0.62, -0.58, -0.66],\n  [ 0.68, -0.24,  1.00, -0.19, -0.35, -0.44, -0.52],\n  [-0.41, -0.71, -0.19,  1.00,  0.74,  0.69,  0.61],\n  [-0.55, -0.62, -0.35,  0.74,  1.00,  0.72,  0.58],\n  [-0.38, -0.58, -0.44,  0.69,  0.72,  1.00,  0.49],\n  [-0.29, -0.66, -0.52,  0.61,  0.58,  0.49,  1.00],\n];\n\n// --- Data storytelling: surface the single strongest off-diagonal pair in\n// each direction, so the reader has a focal point beyond raw color scanning.\nfunction strongestPair(matrix, n, wantMax) {\n  let best = wantMax ? -Infinity : Infinity;\n  let pair = [0, 1];\n  for (let i = 0; i < n; i++) {\n    for (let j = i + 1; j < n; j++) {\n      const v = matrix[i][j];\n      if (wantMax ? v > best : v < best) {\n        best = v;\n        pair = [i, j];\n      }\n    }\n  }\n  return { value: best, pair };\n}\n\nconst STRONGEST_POS = strongestPair(CORR, N, true);\nconst STRONGEST_NEG = strongestPair(CORR, N, false);\n\n// --- Color: imprint_div (red -0.55, page-bg midpoint 0, blue +1) -----------\nfunction hexToRgb(hex) {\n  return [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];\n}\n\nconst DIV_LOW = hexToRgb(t.div[0]);   // #AE3030 — negative correlation\nconst DIV_MID = hexToRgb(t.div[1]);   // page bg  — zero correlation (theme-adaptive)\nconst DIV_HIGH = hexToRgb(t.div[2]);  // #4467A3 — positive correlation\n\nfunction lerp(a, b, f) {\n  return a + (b - a) * f;\n}\n\nfunction colorAt(value) {\n  const a = value <= 0 ? DIV_LOW : DIV_MID;\n  const b = value <= 0 ? DIV_MID : DIV_HIGH;\n  const f = value <= 0 ? value + 1 : value;\n  return [\n    Math.round(lerp(a[0], b[0], f)),\n    Math.round(lerp(a[1], b[1], f)),\n    Math.round(lerp(a[2], b[2], f)),\n  ];\n}\n\nfunction cellBg(value) {\n  const [r, g, b] = colorAt(value);\n  return `rgb(${r},${g},${b})`;\n}\n\n// Relative luminance — pick text color for maximum contrast against the fill\nfunction textColorFor(value) {\n  const [r, g, b] = colorAt(value);\n  const lin = (c) => {\n    const v = c / 255;\n    return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);\n  };\n  const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n  return L > 0.4 ? '#1A1A17' : '#F0EFE8';\n}\n\n// --- Title (fontsize scaled off the 67-char baseline) ----------------------\nconst TITLE_TEXT = 'Macroeconomic Indicator Correlations · heatmap-annotated · javascript · highcharts · anyplot.ai';\nconst TITLE_FS = Math.max(Math.round(22 * Math.min(1, 67 / TITLE_TEXT.length)), 14);\n\nconst drawn = [];\n\nfunction clearDrawn() {\n  drawn.forEach((el) => {\n    try { el.destroy(); } catch (_err) { /* already gone */ }\n  });\n  drawn.length = 0;\n}\n\nfunction drawAll() {\n  const ch = this;\n  clearDrawn();\n  const r = ch.renderer;\n\n  const cW = ch.plotWidth / N;\n  const cH = ch.plotHeight / N;\n\n  // Cells + value annotations\n  for (let row = 0; row < N; row++) {\n    for (let col = 0; col < N; col++) {\n      const value = CORR[row][col];\n      const x = ch.plotLeft + col * cW;\n      const y = ch.plotTop + row * cH;\n\n      drawn.push(\n        r.rect(x + 1, y + 1, cW - 2, cH - 2, 3)\n          .attr({ fill: cellBg(value), zIndex: 2 })\n          .add()\n      );\n\n      drawn.push(\n        r.text(value.toFixed(2), x + cW / 2, y + cH / 2 + 6)\n          .attr({ align: 'center', zIndex: 3 })\n          .css({ color: textColorFor(value), fontSize: '18px', fontWeight: '600' })\n          .add()\n      );\n    }\n  }\n\n  // Highlight the strongest positive and negative pair (both symmetric cells)\n  // with an amber focal outline — a storytelling device beyond raw color.\n  function outlineCell(row, col) {\n    const x = ch.plotLeft + col * cW;\n    const y = ch.plotTop + row * cH;\n    drawn.push(\n      r.rect(x + 1, y + 1, cW - 2, cH - 2, 3)\n        .attr({ fill: 'none', stroke: t.amber, 'stroke-width': 3, zIndex: 2.5 })\n        .add()\n    );\n  }\n  [STRONGEST_POS, STRONGEST_NEG].forEach(({ pair: [i, j] }) => {\n    outlineCell(i, j);\n    outlineCell(j, i);\n  });\n\n  // Caption calling out the two highlighted pairs by name and value.\n  const fmtPair = (p) => `${LABELS[p.pair[0]]} ↔ ${LABELS[p.pair[1]]}`;\n  const fmtVal = (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}`;\n  const caption =\n    `Strongest link: ${fmtPair(STRONGEST_POS)} (${fmtVal(STRONGEST_POS.value)})` +\n    `   ·   Sharpest divide: ${fmtPair(STRONGEST_NEG)} (${fmtVal(STRONGEST_NEG.value)})`;\n  drawn.push(\n    r.text(caption, ch.plotLeft + ch.plotWidth / 2, ch.plotTop - 14)\n      .attr({ align: 'center', zIndex: 3 })\n      .css({ color: t.inkSoft, fontSize: '13px', fontWeight: '500' })\n      .add()\n  );\n\n  // Column labels (bottom, rotated for readability)\n  LABELS.forEach((lbl, col) => {\n    const cx = ch.plotLeft + (col + 0.5) * cW;\n    drawn.push(\n      r.text(lbl, cx, ch.plotTop + ch.plotHeight + 14)\n        .attr({ align: 'right', rotation: -40, zIndex: 3 })\n        .css({ color: t.inkSoft, fontSize: '14px' })\n        .add()\n    );\n  });\n\n  // Row labels (left, right-aligned)\n  LABELS.forEach((lbl, row) => {\n    const cy = ch.plotTop + (row + 0.5) * cH + 5;\n    drawn.push(\n      r.text(lbl, ch.plotLeft - 12, cy)\n        .attr({ align: 'right', zIndex: 3 })\n        .css({ color: t.inkSoft, fontSize: '14px' })\n        .add()\n    );\n  });\n\n  // Diverging colorbar (right of plot area) — top = +1 (blue), bottom = -1 (red)\n  const barX = ch.plotLeft + ch.plotWidth + 26;\n  const barW = 16;\n  const nSeg = 40;\n  const segH = ch.plotHeight / nSeg;\n\n  for (let i = 0; i < nSeg; i++) {\n    const value = 1 - (2 * i) / (nSeg - 1);\n    drawn.push(\n      r.rect(barX, ch.plotTop + i * segH, barW, segH + 0.5)\n        .attr({ fill: cellBg(value), zIndex: 3 })\n        .add()\n    );\n  }\n\n  drawn.push(\n    r.rect(barX, ch.plotTop, barW, ch.plotHeight)\n      .attr({ fill: 'none', stroke: t.inkSoft, 'stroke-width': 1, zIndex: 4 })\n      .add()\n  );\n\n  [[1, 0], [0, 0.5], [-1, 1]].forEach(([value, frac]) => {\n    drawn.push(\n      r.text(value.toFixed(0), barX + barW + 6, ch.plotTop + frac * ch.plotHeight + 4)\n        .attr({ align: 'left', zIndex: 4 })\n        .css({ color: t.inkSoft, fontSize: '13px' })\n        .add()\n    );\n  });\n\n  drawn.push(\n    r.text('Correlation (r)', barX + barW / 2, ch.plotTop - 10)\n      .attr({ align: 'center', zIndex: 4 })\n      .css({ color: t.inkSoft, fontSize: '13px' })\n      .add()\n  );\n}\n\nHighcharts.chart('container', {\n  chart: {\n    backgroundColor: 'transparent',\n    animation: false,\n    style: { fontFamily: 'inherit' },\n    margin: [152, 150, 210, 190],\n    events: { load: drawAll, redraw: drawAll },\n  },\n  credits: { enabled: false },\n  title: {\n    text: TITLE_TEXT,\n    style: { color: t.ink, fontSize: TITLE_FS + 'px', fontWeight: '600' },\n  },\n  subtitle: {\n    text: 'Pairwise Pearson correlation across 7 quarterly macroeconomic indicators',\n    style: { color: t.inkSoft, fontSize: '14px' },\n  },\n  xAxis: { visible: false },\n  yAxis: { visible: false, gridLineWidth: 0 },\n  legend: { enabled: false },\n  tooltip: { enabled: false },\n  plotOptions: { series: { animation: false } },\n  series: [],\n});\n"}