{"spec_id":"heatmap-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// heatmap-basic: Basic Heatmap\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-20\n//# anyplot-orientation: square\n// anyplot.ai\n// heatmap-basic: Basic Heatmap\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-08-20\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsText } from \"@mui/x-charts/ChartsText\";\nimport { ContinuousColorLegend } from \"@mui/x-charts/ChartsLegend\";\nimport { useXScale, useYScale, useZColorScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst SIZE = window.ANYPLOT_SIZE;\n\n// --- Data: correlation matrix between business metrics (in-memory, deterministic) ---\nconst metrics = [\n  \"Revenue\",\n  \"Marketing\",\n  \"CSAT\",\n  \"Headcount\",\n  \"R&D\",\n  \"Mkt Share\",\n  \"OpEx\",\n  \"Churn\",\n];\n\n// Upper-triangular correlation coefficients (row i vs. columns i+1..n-1); mirrored below.\nconst upperTriangle = [\n  [0.72, 0.45, 0.68, 0.55, 0.81, 0.6, -0.52],\n  [0.3, 0.4, 0.25, 0.66, 0.58, -0.2],\n  [0.35, 0.28, 0.5, -0.1, -0.78],\n  [0.62, 0.47, 0.71, -0.18],\n  [0.58, 0.33, -0.25],\n  [0.4, -0.44],\n  [0.15],\n];\n\nconst n = metrics.length;\nconst cells = [];\nfor (let row = 0; row < n; row += 1) {\n  for (let col = 0; col < n; col += 1) {\n    const value =\n      row === col\n        ? 1\n        : upperTriangle[Math.min(row, col)][\n            Math.max(row, col) - Math.min(row, col) - 1\n          ];\n    cells.push({\n      id: `${row}-${col}`,\n      x: metrics[col],\n      y: metrics[row],\n      value,\n    });\n  }\n}\n\n// --- Colour: diverging Imprint colormap (imprint_div) driven by the z-axis colorMap ---\nfunction hexToRgb(hex) {\n  const int = parseInt(hex.slice(1), 16);\n  return [(int >> 16) & 255, (int >> 8) & 255, int & 255];\n}\n\nfunction lerp(a, b, ratio) {\n  return Math.round(a + (b - a) * ratio);\n}\n\nfunction imprintDivInterpolator(stops) {\n  const [low, mid, high] = stops.map(hexToRgb);\n  return (position) => {\n    const [start, end, localRatio] =\n      position < 0.5\n        ? [low, mid, position / 0.5]\n        : [mid, high, (position - 0.5) / 0.5];\n    const [r, g, b] = [0, 1, 2].map((channel) =>\n      lerp(start[channel], end[channel], localRatio),\n    );\n    return `rgb(${r}, ${g}, ${b})`;\n  };\n}\n\n// Cell text must stay legible against that cell's own fill luminance regardless\n// of the current site theme, so pick between the two fixed Imprint ink literals\n// (light-mode ink / dark-mode ink) rather than the theme-dependent `t.ink`.\nconst CELL_TEXT_ON_LIGHT_FILL = \"#1A1A17\";\nconst CELL_TEXT_ON_DARK_FILL = \"#F0EFE8\";\n\nfunction textColorFor(fill) {\n  const [r, g, b] = fill.match(/[\\d.]+/g).map(Number);\n  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n  return luminance > 140 ? CELL_TEXT_ON_LIGHT_FILL : CELL_TEXT_ON_DARK_FILL;\n}\n\n// --- Cells (custom composition: MUI X band scales + z-axis colour scale) --------------\nfunction HeatmapCells() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const colorScale = useZColorScale();\n\n  return (\n    <g>\n      {cells.map((cell) => {\n        const x = xScale(cell.x) ?? 0;\n        const y = yScale(cell.y) ?? 0;\n        const width = xScale.bandwidth();\n        const height = yScale.bandwidth();\n        const fill = colorScale(cell.value);\n        return (\n          <g key={cell.id}>\n            <rect\n              x={x}\n              y={y}\n              width={width}\n              height={height}\n              fill={fill}\n              rx={4}\n            />\n            <ChartsText\n              text={cell.value.toFixed(2)}\n              x={x + width / 2}\n              y={y + height / 2}\n              style={{\n                fontSize: 15,\n                fill: textColorFor(fill),\n                textAnchor: \"middle\",\n                dominantBaseline: \"central\",\n              }}\n            />\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) ----------------------\nconst TITLE = \"heatmap-basic · javascript · muix · anyplot.ai\";\nconst MARGIN = { top: 150, right: 200, bottom: 150, left: 170 };\nconst LEGEND_CAPTION_X = SIZE.width - 26;\nconst LEGEND_CAPTION_Y = SIZE.height / 2;\n\nexport default function Chart() {\n  return (\n    <ChartContainer\n      width={SIZE.width}\n      height={SIZE.height}\n      series={[]}\n      margin={MARGIN}\n      skipAnimation\n      xAxis={[\n        {\n          scaleType: \"band\",\n          data: metrics,\n          categoryGapRatio: 0.08,\n          disableLine: true,\n          disableTicks: true,\n          tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n        },\n      ]}\n      yAxis={[\n        {\n          scaleType: \"band\",\n          data: metrics,\n          categoryGapRatio: 0.08,\n          disableLine: true,\n          disableTicks: true,\n          tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n        },\n      ]}\n      zAxis={[\n        {\n          colorMap: {\n            type: \"continuous\",\n            min: -1,\n            max: 1,\n            color: imprintDivInterpolator(t.div),\n          },\n        },\n      ]}\n    >\n      <HeatmapCells />\n      <ChartsXAxis />\n      <ChartsYAxis />\n      <ContinuousColorLegend\n        position={{ horizontal: \"right\", vertical: \"middle\" }}\n        direction=\"column\"\n        length=\"55%\"\n        thickness={18}\n        labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n      />\n      <ChartsText\n        text=\"Correlation coefficient\"\n        x={LEGEND_CAPTION_X}\n        y={LEGEND_CAPTION_Y}\n        style={{\n          fontSize: 12,\n          fill: t.inkSoft,\n          textAnchor: \"middle\",\n          angle: -90,\n        }}\n      />\n      <ChartsText\n        text={TITLE}\n        x={SIZE.width / 2}\n        y={50}\n        style={{\n          fontSize: 26,\n          fontWeight: 600,\n          fill: t.ink,\n          textAnchor: \"middle\",\n        }}\n      />\n    </ChartContainer>\n  );\n}\n"}