{"spec_id":"mosaic-categorical","library":"muix","language":"javascript","code":"// anyplot.ai\n// mosaic-categorical: Mosaic Plot for Categorical Association Analysis\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-02\n\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Titanic passengers cross-tabulated by cabin class and survival outcome\n// (classic contingency-table example; crew excluded). Column widths encode\n// the marginal proportion of each class; cell heights within a column encode\n// the conditional survival rate for that class.\nconst CLASSES = [\n  { id: \"first\", label: \"1st Class\", survived: 203, died: 122 },\n  { id: \"second\", label: \"2nd Class\", survived: 118, died: 167 },\n  { id: \"third\", label: \"3rd Class\", survived: 178, died: 528 },\n];\n\nconst GAP_X = 0.018; // fraction of total width separating columns\nconst GAP_Y = 0.02; // fraction of column height separating the two outcome cells\n\nconst grandTotal = CLASSES.reduce((sum, c) => sum + c.survived + c.died, 0);\n\nlet cursor = 0;\nconst columns = CLASSES.map((c) => {\n  const classTotal = c.survived + c.died;\n  const x0raw = cursor;\n  const x1raw = cursor + classTotal / grandTotal;\n  cursor = x1raw;\n  return {\n    id: c.id,\n    label: c.label,\n    x0raw,\n    x1raw,\n    survivedShare: c.survived / classTotal,\n  };\n});\n\n// Bottom cell of each column: survived (good outcome -> brand green, the\n// mandatory first-series color, which also matches the semantic exception).\nconst survivedData = columns.map((col) => ({\n  id: `${col.id}-survived`,\n  x0: col.x0raw + GAP_X / 2,\n  x1: col.x1raw - GAP_X / 2,\n  y0: 0,\n  y1: col.survivedShare - GAP_Y / 2,\n  columnLabel: col.label,\n  columnMidX: (col.x0raw + col.x1raw) / 2,\n}));\n\n// Top cell of each column: did not survive (bad outcome -> semantic matte red).\nconst diedData = columns.map((col) => ({\n  id: `${col.id}-died`,\n  x0: col.x0raw + GAP_X / 2,\n  x1: col.x1raw - GAP_X / 2,\n  y0: col.survivedShare + GAP_Y / 2,\n  y1: 1,\n}));\n\n// Custom marker: variable-size rectangles positioned from the two linear\n// scales (0..1 domains), the composition technique the community\n// @mui/x-charts surface exposes for chart types it doesn't ship natively —\n// here a mosaic/marimekko plot. Also draws the per-column class label and,\n// on the leftmost column only, the \"Passenger Class\" axis caption.\nfunction MosaicCell(props) {\n  const { series, xScale, yScale, color } = props;\n  return (\n    <g>\n      {series.data.map((cell) => {\n        const px0 = xScale(cell.x0);\n        const px1 = xScale(cell.x1);\n        const py0 = yScale(cell.y0);\n        const py1 = yScale(cell.y1);\n        const baseline = yScale(0);\n        return (\n          <g key={cell.id}>\n            <rect\n              x={Math.min(px0, px1)}\n              y={Math.min(py0, py1)}\n              width={Math.abs(px1 - px0)}\n              height={Math.abs(py1 - py0)}\n              fill={color}\n            />\n            {cell.columnLabel ? (\n              <text\n                x={xScale(cell.columnMidX)}\n                y={baseline + 28}\n                textAnchor=\"middle\"\n                fontSize={15}\n                fontWeight={500}\n                fontFamily=\"inherit\"\n                fill={t.inkSoft}\n              >\n                {cell.columnLabel}\n              </text>\n            ) : null}\n            {cell.id === \"first-survived\" ? (\n              <text\n                x={xScale(cell.columnMidX)}\n                y={baseline + 52}\n                textAnchor=\"middle\"\n                fontSize={13}\n                fontFamily=\"inherit\"\n                fill={t.inkSoft}\n                opacity={0.75}\n              >\n                Passenger Class (width) · Survival rate (height)\n              </text>\n            ) : null}\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\nconst TITLE = \"Titanic Survival by Class · mosaic-categorical · javascript · muix · anyplot.ai\";\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const titleFontSize = Math.round(22 * Math.min(1, 67 / TITLE.length));\n  const titleHeight = 56;\n  const legendHeight = 40;\n  const chartHeight = height - titleHeight - legendHeight;\n\n  return (\n    <Box sx={{ width, height, bgcolor: t.pageBg, display: \"flex\", flexDirection: \"column\" }}>\n      <Typography\n        sx={{\n          color: t.ink,\n          fontSize: titleFontSize,\n          fontWeight: 500,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n          pt: \"16px\",\n          height: titleHeight,\n          fontFamily: \"inherit\",\n        }}\n      >\n        {TITLE}\n      </Typography>\n\n      {/* Manual legend row — the two outcome colors, kept clear of the plot\n          area so it never overlaps the tallest (rightmost) column. */}\n      <Box sx={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", gap: \"28px\", height: legendHeight }}>\n        {[\n          { label: \"Survived\", color: t.palette[0] },\n          { label: \"Did not survive\", color: t.palette[4] },\n        ].map((item) => (\n          <Box key={item.label} sx={{ display: \"flex\", alignItems: \"center\", gap: \"8px\" }}>\n            <Box sx={{ width: 14, height: 14, borderRadius: \"3px\", bgcolor: item.color }} />\n            <Typography sx={{ fontSize: 15, color: t.inkSoft, fontFamily: \"inherit\" }}>{item.label}</Typography>\n          </Box>\n        ))}\n      </Box>\n\n      <Box sx={{ flex: 1 }}>\n        <ScatterChart\n          width={width}\n          height={chartHeight}\n          skipAnimation\n          disableVoronoi\n          series={[\n            { id: \"survived\", type: \"scatter\", data: survivedData, label: \"Survived\", color: t.palette[0] },\n            { id: \"died\", type: \"scatter\", data: diedData, label: \"Did not survive\", color: t.palette[4] },\n          ]}\n          xAxis={[{ id: \"x\", min: 0, max: 1, scaleType: \"linear\" }]}\n          yAxis={[{ id: \"y\", min: 0, max: 1, scaleType: \"linear\" }]}\n          topAxis={null}\n          bottomAxis={null}\n          leftAxis={null}\n          rightAxis={null}\n          margin={{ top: 16, right: 40, bottom: 64, left: 40 }}\n          slots={{ scatter: MosaicCell }}\n          slotProps={{ legend: { hidden: true } }}\n        />\n      </Box>\n    </Box>\n  );\n}\n"}