{"spec_id":"histogram-2d","library":"muix","language":"javascript","code":"// anyplot.ai\n// histogram-2d: 2D Histogram Heatmap\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 82/100 | Created: 2026-09-05\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) — joint distribution of two correlated\n// daily stock returns, the kind of dataset a scatter plot turns into an\n// unreadable smear once it grows past a few thousand points -----------------\nfunction mulberry32(seed) {\n  return function random() {\n    seed = (seed + 0x6d2b79f5) | 0;\n    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nfunction gaussian(random) {\n  let u = 0;\n  let v = 0;\n  while (u === 0) u = random();\n  while (v === 0) v = random();\n  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);\n}\n\nconst random = mulberry32(42);\nconst POINT_COUNT = 8000;\nconst CORRELATION = 0.65;\nconst STOCK_A_VOLATILITY = 1.4;\nconst STOCK_B_VOLATILITY = 1.6;\n\nconst stockAReturns = [];\nconst stockBReturns = [];\nfor (let i = 0; i < POINT_COUNT; i += 1) {\n  const z1 = gaussian(random);\n  const z2 = gaussian(random);\n  stockAReturns.push(z1 * STOCK_A_VOLATILITY);\n  stockBReturns.push((CORRELATION * z1 + Math.sqrt(1 - CORRELATION * CORRELATION) * z2) * STOCK_B_VOLATILITY);\n}\n\n// --- Bin the point cloud into a rectangular grid — this is the \"histogram\"\n// step: raw points collapse into per-cell counts before anything is drawn ---\nconst BIN_COUNT_X = 26;\nconst BIN_COUNT_Y = 18;\nconst xMin = Math.min(...stockAReturns);\nconst xMax = Math.max(...stockAReturns);\nconst yMin = Math.min(...stockBReturns);\nconst yMax = Math.max(...stockBReturns);\nconst BIN_WIDTH = (xMax - xMin) / BIN_COUNT_X;\nconst BIN_HEIGHT = (yMax - yMin) / BIN_COUNT_Y;\n\nconst binCounts = new Array(BIN_COUNT_X * BIN_COUNT_Y).fill(0);\nfor (let i = 0; i < POINT_COUNT; i += 1) {\n  const bx = Math.min(BIN_COUNT_X - 1, Math.floor((stockAReturns[i] - xMin) / BIN_WIDTH));\n  const by = Math.min(BIN_COUNT_Y - 1, Math.floor((stockBReturns[i] - yMin) / BIN_HEIGHT));\n  binCounts[by * BIN_COUNT_X + bx] += 1;\n}\n\nconst bins = [];\nlet maxCount = 0;\nfor (let by = 0; by < BIN_COUNT_Y; by += 1) {\n  for (let bx = 0; bx < BIN_COUNT_X; bx += 1) {\n    const count = binCounts[by * BIN_COUNT_X + bx];\n    if (count > 0) {\n      bins.push({\n        id: `${bx}-${by}`,\n        x: xMin + (bx + 0.5) * BIN_WIDTH,\n        y: yMin + (by + 0.5) * BIN_HEIGHT,\n        z: count,\n      });\n      maxCount = Math.max(maxCount, count);\n    }\n  }\n}\n\n// Custom marker: filled rectangular bins sized from the grid geometry above,\n// replacing ScatterChart's default circles — the community `slots.scatter`\n// override is the documented way to draw non-circular marks. `xScale`/`yScale`\n// are affine (linear), so the on-screen span of one bin is the same anywhere\n// along the axis; evaluating it once at the origin is enough.\nfunction HistogramCell(props) {\n  const { series, xScale, yScale, colorGetter, color } = props;\n  const cellWidth = Math.abs(xScale(BIN_WIDTH) - xScale(0));\n  const cellHeight = Math.abs(yScale(BIN_HEIGHT) - yScale(0));\n\n  return (\n    <g>\n      {series.data.map((point, i) => (\n        <rect\n          key={point.id}\n          x={(xScale(point.x) ?? 0) - cellWidth / 2}\n          y={(yScale(point.y) ?? 0) - cellHeight / 2}\n          width={cellWidth}\n          height={cellHeight}\n          fill={colorGetter ? colorGetter(i) : color}\n        />\n      ))}\n    </g>\n  );\n}\n\nconst TITLE = \"Correlated Stock Returns · histogram-2d · javascript · muix · anyplot.ai\";\nconst TITLE_FONT_SIZE = Math.max(15, Math.round(22 * (TITLE.length > 67 ? 67 / TITLE.length : 1)));\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const TITLE_HEIGHT = 90;\n  const chartWidth = width - 20;\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: TITLE_FONT_SIZE,\n          fontWeight: 500,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n          pt: \"16px\",\n          height: TITLE_HEIGHT,\n          fontFamily: \"inherit\",\n        }}\n      >\n        {TITLE}\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          series={[\n            {\n              id: \"density\",\n              type: \"scatter\",\n              data: bins,\n              label: \"Point density\",\n              zAxisId: \"count\",\n              valueFormatter: (value) => `${value.z} points`,\n            },\n          ]}\n          xAxis={[\n            {\n              id: \"returnA\",\n              min: xMin,\n              max: xMax,\n              label: \"Stock A Daily Return (%)\",\n              labelStyle: { fontSize: 18, fill: tokens.ink, fontFamily: \"inherit\" },\n              tickLabelStyle: { fontSize: 14, fill: tokens.inkSoft },\n            },\n          ]}\n          yAxis={[\n            {\n              id: \"returnB\",\n              min: yMin,\n              max: yMax,\n              label: \"Stock B Daily Return (%)\",\n              labelStyle: { fontSize: 18, fill: tokens.ink, fontFamily: \"inherit\" },\n              tickLabelStyle: { fontSize: 14, fill: tokens.inkSoft },\n            },\n          ]}\n          zAxis={[\n            {\n              id: \"count\",\n              min: 0,\n              max: maxCount,\n              colorMap: { type: \"continuous\", min: 0, max: maxCount, color: [tokens.seq[0], tokens.seq[1]] },\n            },\n          ]}\n          grid={{ vertical: false, horizontal: false }}\n          margin={{ top: 30, right: 40, bottom: 130, left: 110 }}\n          slots={{ scatter: HistogramCell }}\n          slotProps={{ legend: { hidden: true } }}\n        >\n          <ContinuousColorLegend\n            axisId=\"count\"\n            axisDirection=\"z\"\n            position={{ horizontal: \"middle\", vertical: \"bottom\" }}\n            direction=\"row\"\n            length=\"62%\"\n            thickness={12}\n            minLabel={({ formattedValue }) => `${formattedValue} points`}\n            maxLabel={({ formattedValue }) => `${formattedValue} points`}\n            labelStyle={{ fontSize: 14, fill: tokens.inkSoft, fontFamily: \"inherit\" }}\n          />\n        </ScatterChart>\n      </Box>\n    </Box>\n  );\n}\n"}