{"spec_id":"heatmap-correlation","library":"muix","language":"javascript","code":"// anyplot.ai\n// heatmap-correlation: Correlation Matrix Heatmap\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-18\n//# anyplot-orientation: square\n// anyplot.ai\n// heatmap-correlation: Correlation Matrix 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-18\n\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\nimport { ContinuousColorLegend } from \"@mui/x-charts/ChartsLegend\";\n\nconst tokens = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) — portfolio diversification example ---\nconst variables = [\"Equities\", \"Bonds\", \"Real Estate\", \"Commodities\", \"Gold\", \"Cash\"];\nconst correlationMatrix = [\n  [1.0, -0.35, 0.55, 0.2, -0.1, -0.05],\n  [-0.35, 1.0, 0.15, -0.2, 0.3, 0.1],\n  [0.55, 0.15, 1.0, 0.25, 0.05, -0.15],\n  [0.2, -0.2, 0.25, 1.0, 0.65, -0.1],\n  [-0.1, 0.3, 0.05, 0.65, 1.0, 0.2],\n  [-0.05, 0.1, -0.15, -0.1, 0.2, 1.0],\n];\n\n// Mask the strict upper triangle — a symmetric matrix repeats every\n// off-diagonal value, so only the diagonal and lower triangle carry ink.\nconst points = [];\nfor (let row = 0; row < variables.length; row += 1) {\n  for (let col = 0; col <= row; col += 1) {\n    points.push({\n      id: `${row}-${col}`,\n      x: variables[col],\n      y: variables[row],\n      z: correlationMatrix[row][col],\n    });\n  }\n}\n\n// --- Diverging Imprint color scale (imprint_div, theme-adaptive midpoint) ---\nfunction mixHex(hexA, hexB, ratio) {\n  const a = parseInt(hexA.slice(1), 16);\n  const b = parseInt(hexB.slice(1), 16);\n  const channel = (shift) => {\n    const av = (a >> shift) & 255;\n    const bv = (b >> shift) & 255;\n    return Math.round(av + (bv - av) * ratio);\n  };\n  return `#${[16, 8, 0].map((shift) => channel(shift).toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n// `t` arrives pre-normalized to [0, 1] by the zAxis colorMap's scaleSequential\n// (from the -1..1 domain below) — 0 is the red end, 0.5 the neutral midpoint.\nfunction divergingColor(t) {\n  const [low, mid, high] = tokens.div;\n  return t <= 0.5 ? mixHex(low, mid, t / 0.5) : mixHex(mid, high, (t - 0.5) / 0.5);\n}\n\nfunction relativeLuminance(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  const r = ((n >> 16) & 255) / 255;\n  const g = ((n >> 8) & 255) / 255;\n  const b = (n & 255) / 255;\n  return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n// Custom marker: filled square cells (matrix entries) instead of the default\n// circles, each annotated with its correlation value to 2 decimal places.\nfunction CorrelationCell(props) {\n  const { series, xScale, yScale, colorGetter, color } = props;\n  const cellWidth = xScale.bandwidth();\n  const cellHeight = yScale.bandwidth();\n  const fontSize = Math.round(Math.min(cellWidth, cellHeight) * 0.22);\n\n  return (\n    <g>\n      {series.data.map((point, i) => {\n        const x0 = xScale(point.x) ?? 0;\n        const y0 = yScale(point.y) ?? 0;\n        const fill = colorGetter ? colorGetter(i) : color;\n        const textFill = relativeLuminance(fill) > 0.5 ? \"#1A1A17\" : \"#F0EFE8\";\n        return (\n          <g key={point.id}>\n            <rect x={x0} y={y0} width={cellWidth} height={cellHeight} fill={fill} />\n            <text\n              x={x0 + cellWidth / 2}\n              y={y0 + cellHeight / 2}\n              textAnchor=\"middle\"\n              dominantBaseline=\"central\"\n              fontSize={fontSize}\n              fontFamily=\"inherit\"\n              fill={textFill}\n            >\n              {point.z.toFixed(2)}\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const TITLE_HEIGHT = 80;\n  const RIGHT_BUFFER = 40; // room for the legend's max-value label at the true edge\n  const TOP_MARGIN = 70;\n  const LEFT_MARGIN = 160;\n  const RIGHT_MARGIN = 20;\n  const LEGEND_SPACE = 70;\n\n  const chartWidth = width - RIGHT_BUFFER;\n  const chartHeight = height - TITLE_HEIGHT;\n\n  return (\n    <Box sx={{ width, height, bgcolor: tokens.pageBg, display: \"flex\", flexDirection: \"column\" }}>\n      <Typography\n        sx={{\n          color: tokens.ink,\n          fontSize: 22,\n          fontWeight: 500,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n          pt: \"16px\",\n          height: TITLE_HEIGHT,\n          fontFamily: \"inherit\",\n        }}\n      >\n        heatmap-correlation · javascript · muix · anyplot.ai\n      </Typography>\n      <Box sx={{ flex: 1, display: \"flex\", alignItems: \"flex-start\", justifyContent: \"flex-start\" }}>\n        <ScatterChart\n          width={chartWidth}\n          height={chartHeight}\n          skipAnimation\n          disableVoronoi\n          series={[\n            {\n              id: \"correlation\",\n              type: \"scatter\",\n              data: points,\n              label: \"Correlation\",\n              xAxisId: \"col\",\n              yAxisId: \"row\",\n              zAxisId: \"corr\",\n            },\n          ]}\n          xAxis={[\n            {\n              id: \"col\",\n              scaleType: \"band\",\n              data: variables,\n              categoryGapRatio: 0.08,\n              tickLabelStyle: { fontSize: 16, fill: tokens.inkSoft },\n              disableTicks: true,\n              disableLine: true,\n            },\n          ]}\n          yAxis={[\n            {\n              id: \"row\",\n              scaleType: \"band\",\n              data: variables,\n              categoryGapRatio: 0.08,\n              tickLabelStyle: { fontSize: 16, fill: tokens.inkSoft },\n              disableTicks: true,\n              disableLine: true,\n            },\n          ]}\n          zAxis={[\n            {\n              id: \"corr\",\n              min: -1,\n              max: 1,\n              colorMap: { type: \"continuous\", min: -1, max: 1, color: divergingColor },\n            },\n          ]}\n          topAxis=\"col\"\n          bottomAxis={null}\n          leftAxis=\"row\"\n          rightAxis={null}\n          margin={{ top: TOP_MARGIN, right: RIGHT_MARGIN, bottom: LEGEND_SPACE, left: LEFT_MARGIN }}\n          slots={{ scatter: CorrelationCell }}\n          slotProps={{ legend: { hidden: true } }}\n        >\n          <ContinuousColorLegend\n            axisId=\"corr\"\n            axisDirection=\"z\"\n            position={{ horizontal: \"right\", vertical: \"bottom\" }}\n            direction=\"row\"\n            length=\"50%\"\n            thickness={14}\n            labelStyle={{ fontSize: 14, fill: tokens.inkSoft, fontFamily: \"inherit\" }}\n          />\n        </ScatterChart>\n      </Box>\n    </Box>\n  );\n}\n"}