{"spec_id":"subplot-mosaic","library":"muix","language":"javascript","code":"// anyplot.ai\n// subplot-mosaic: Mosaic Subplot Layout with Varying Sizes\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-09\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { BarChart } from \"@mui/x-charts/BarChart\";\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\nimport { Gauge, gaugeClasses } from \"@mui/x-charts/Gauge\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\nlet seed = 42;\nconst nextRandom = () => {\n  seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n};\n\n// Panel A — 30-day pageview trend for the whole site (the dominant panel)\nconst days = Array.from({ length: 30 }, (_, i) => i + 1);\nlet pageviewLevel = 42000;\nconst pageviews = days.map(() => {\n  pageviewLevel += (nextRandom() - 0.4) * 2600;\n  return Math.round(Math.max(30000, pageviewLevel));\n});\n\n// Panel B — sessions by device category (Mobile leads — called out below)\nconst devices = [\"Desktop\", \"Mobile\", \"Tablet\"];\nconst sessionsByDevice = [18400, 24900, 5100];\nconst leadDeviceIndex = sessionsByDevice.indexOf(Math.max(...sessionsByDevice));\n\n// Panel C — bounce rate vs. avg. session duration, by acquisition channel\nconst channels = [\n  { label: \"Organic\", bounceBase: 38, durationBase: 210 },\n  { label: \"Paid\", bounceBase: 52, durationBase: 140 },\n  { label: \"Direct\", bounceBase: 30, durationBase: 260 },\n  { label: \"Referral\", bounceBase: 46, durationBase: 175 },\n];\nconst channelSeries = channels.map((c) => ({\n  label: c.label,\n  data: Array.from({ length: 35 }, (_, i) => ({\n    id: i,\n    x: Number(Math.max(5, Math.min(95, c.bounceBase + (nextRandom() - 0.5) * 26)).toFixed(1)),\n    y: Math.round(Math.max(20, c.durationBase + (nextRandom() - 0.5) * 100)),\n  })),\n}));\n\n// Panels D/E/F — small KPI gauges\nconst kpis = [\n  { label: \"Conversion Rate\", value: 4.8, min: 0, max: 10, unit: \"%\" },\n  { label: \"Uptime SLA\", value: 99.95, min: 95, max: 100, unit: \"%\" },\n  { label: \"Support CSAT\", value: 92, min: 0, max: 100, unit: \"%\" },\n];\n\nconst withAlpha = (hex, alpha) => {\n  const n = parseInt(hex.slice(1), 16);\n  return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;\n};\n\n// --- Mosaic layout — ASCII pattern → CSS Grid areas -------------------------\n// Mirrors matplotlib's subplot_mosaic: repeated letters span cells (\".\" would\n// mark a gap — CSS grid-template-areas treats \".\" as an empty cell natively).\n// One wide overview on top, two medium detail panels in the middle, three\n// small metric panels at the bottom.\nconst MOSAIC = [\"AAAAAA\", \"BBBCCC\", \"DDEEFF\"];\nconst ROW_FRACTION = [0.44, 0.29, 0.27]; // the wide overview gets the most height\nconst GAP = 22;\nconst CAPTION_H = 30;\nconst TITLE_H = 68;\n\nconst mosaicRect = (letter) => {\n  let r0 = Infinity;\n  let r1 = -1;\n  let c0 = Infinity;\n  let c1 = -1;\n  MOSAIC.forEach((row, r) => {\n    for (let c = 0; c < row.length; c += 1) {\n      if (row[c] === letter) {\n        r0 = Math.min(r0, r);\n        r1 = Math.max(r1, r);\n        c0 = Math.min(c0, c);\n        c1 = Math.max(c1, c);\n      }\n    }\n  });\n  return { row: r0, rowSpan: r1 - r0 + 1, col: c0, colSpan: c1 - c0 + 1 };\n};\n\nconst cols = MOSAIC[0].length;\nconst plotAreaH = height - TITLE_H;\nconst colWidth = (width - GAP * (cols - 1)) / cols;\nconst rowHeights = ROW_FRACTION.map((f) => Math.round((plotAreaH - GAP * (MOSAIC.length - 1)) * f));\nconst gridTemplateAreas = MOSAIC.map((row) => `\"${row.split(\"\").join(\" \")}\"`).join(\" \");\n\nconst cellSize = (letter) => {\n  const { row, rowSpan, colSpan } = mosaicRect(letter);\n  const cellWidth = Math.round(colWidth * colSpan + GAP * (colSpan - 1));\n  const cellHeight =\n    rowHeights.slice(row, row + rowSpan).reduce((sum, h) => sum + h, 0) + GAP * (rowSpan - 1);\n  return { width: cellWidth, height: cellHeight - CAPTION_H };\n};\n\nconst sizeA = cellSize(\"A\");\nconst sizeB = cellSize(\"B\");\nconst sizeC = cellSize(\"C\");\nconst sizeD = cellSize(\"D\");\nconst sizeE = cellSize(\"E\");\nconst sizeF = cellSize(\"F\");\n\nconst captionStyle = {\n  height: CAPTION_H,\n  fontSize: 15,\n  fontWeight: 600,\n  color: t.inkSoft,\n  fontFamily: \"Roboto, Helvetica, Arial, sans-serif\",\n  display: \"flex\",\n  alignItems: \"flex-end\",\n};\n\n// --- Title (fontsize scales with title length, see plot-generator.md) -------\nconst TITLE = \"Website Analytics Dashboard · subplot-mosaic · javascript · muix · anyplot.ai\";\nconst TITLE_FONTSIZE = Math.round(20 * (TITLE.length > 67 ? 67 / TITLE.length : 1));\n\nconst gaugeSx = {\n  [`& .${gaugeClasses.valueArc}`]: { fill: t.palette[0] },\n  [`& .${gaugeClasses.referenceArc}`]: { fill: withAlpha(t.ink, 0.12) },\n  [`& .${gaugeClasses.valueText} text`]: { fill: t.ink, fontSize: 26, fontWeight: 600 },\n};\n\nconst kpiPanel = (kpi, size) => (\n  <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\" }}>\n    <div style={{ ...captionStyle, alignItems: \"center\" }}>{kpi.label}</div>\n    <Gauge\n      width={size.width}\n      height={size.height}\n      value={kpi.value}\n      valueMin={kpi.min}\n      valueMax={kpi.max}\n      startAngle={-100}\n      endAngle={100}\n      innerRadius=\"76%\"\n      outerRadius=\"100%\"\n      cornerRadius=\"50%\"\n      text={({ value }) => `${value}${kpi.unit}`}\n      skipAnimation\n      sx={gaugeSx}\n    />\n  </div>\n);\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  return (\n    <div style={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <div\n        style={{\n          height: TITLE_H,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          fontSize: TITLE_FONTSIZE,\n          fontWeight: 600,\n          color: t.ink,\n          fontFamily: \"Roboto, Helvetica, Arial, sans-serif\",\n        }}\n      >\n        {TITLE}\n      </div>\n      <div\n        style={{\n          width,\n          height: plotAreaH,\n          display: \"grid\",\n          gridTemplateAreas,\n          gridTemplateColumns: `repeat(${cols}, ${colWidth}px)`,\n          gridTemplateRows: rowHeights.map((h) => `${h}px`).join(\" \"),\n          gap: GAP,\n        }}\n      >\n        <div style={{ gridArea: \"A\", display: \"flex\", flexDirection: \"column\" }}>\n          <div style={captionStyle}>Pageviews — Last 30 Days</div>\n          <LineChart\n            width={sizeA.width}\n            height={sizeA.height}\n            skipAnimation\n            legend={{ hidden: true }}\n            margin={{ left: 76, right: 20, top: 12, bottom: 46 }}\n            xAxis={[\n              {\n                data: days,\n                label: \"Day of Month\",\n                tickLabelStyle: { fontSize: 12 },\n              },\n            ]}\n            yAxis={[{ tickLabelStyle: { fontSize: 12 } }]}\n            series={[{ data: pageviews, color: t.palette[0], showMark: false, curve: \"monotoneX\" }]}\n          />\n        </div>\n        <div style={{ gridArea: \"B\", display: \"flex\", flexDirection: \"column\" }}>\n          <div style={captionStyle}>Sessions by Device</div>\n          <BarChart\n            width={sizeB.width}\n            height={sizeB.height}\n            skipAnimation\n            legend={{ hidden: true }}\n            margin={{ left: 70, right: 16, top: 12, bottom: 40 }}\n            xAxis={[\n              {\n                scaleType: \"band\",\n                data: devices,\n                tickLabelStyle: { fontSize: 13 },\n                // Focal-point emphasis: the leading device (Mobile) stays brand\n                // green, the others mute to inkSoft — calls out the takeaway\n                // without adding annotation infrastructure.\n                colorMap: {\n                  type: \"ordinal\",\n                  values: devices,\n                  colors: devices.map((_, i) => (i === leadDeviceIndex ? t.palette[0] : t.inkSoft)),\n                },\n              },\n            ]}\n            yAxis={[{ tickLabelStyle: { fontSize: 12 } }]}\n            series={[{ data: sessionsByDevice, color: t.palette[0] }]}\n          />\n        </div>\n        <div style={{ gridArea: \"C\", display: \"flex\", flexDirection: \"column\" }}>\n          <div style={captionStyle}>Bounce Rate vs. Session Duration, by Channel</div>\n          <ScatterChart\n            width={sizeC.width}\n            height={sizeC.height}\n            skipAnimation\n            margin={{ left: 78, right: 16, top: 12, bottom: 46 }}\n            grid={{ horizontal: true }}\n            xAxis={[{ label: \"Bounce Rate (%)\", tickLabelStyle: { fontSize: 12 } }]}\n            yAxis={[{ label: \"Duration (s)\", tickLabelStyle: { fontSize: 12 } }]}\n            series={channelSeries.map((s) => ({ ...s, markerSize: 5 }))}\n            colors={t.palette.slice(0, channelSeries.length).map((c) => withAlpha(c, 0.78))}\n            slotProps={{ legend: { labelStyle: { fontSize: 12 } } }}\n          />\n        </div>\n        <div style={{ gridArea: \"D\" }}>{kpiPanel(kpis[0], sizeD)}</div>\n        <div style={{ gridArea: \"E\" }}>{kpiPanel(kpis[1], sizeE)}</div>\n        <div style={{ gridArea: \"F\" }}>{kpiPanel(kpis[2], sizeF)}</div>\n      </div>\n    </div>\n  );\n}\n"}