{"spec_id":"pyramid-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// pyramid-basic: Basic Pyramid Chart\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\nimport { BarChart } from \"@mui/x-charts/BarChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic): streaming-service genre preference ----\n// Survey of 400 subscribers per service, asked which genre they watch most.\n// Each category holds two independent counts (not a signed net score) --\n// Service A always extends left, Service B always extends right, sharing the\n// zero baseline as the pyramid's central axis. Sorted by combined magnitude\n// (descending) so the chart tapers into a classic pyramid silhouette.\nconst genreRows = [\n  { name: \"Action & Adventure\", streamVista: 75, waveFlix: 60 },\n  { name: \"Drama\", streamVista: 90, waveFlix: 94 },\n  { name: \"Comedy\", streamVista: 65, waveFlix: 71 },\n  { name: \"Sci-Fi & Fantasy\", streamVista: 50, waveFlix: 38 },\n  { name: \"Reality TV\", streamVista: 33, waveFlix: 46 },\n  { name: \"Anime\", streamVista: 40, waveFlix: 45 },\n  { name: \"Documentary\", streamVista: 27, waveFlix: 36 },\n  { name: \"Kids & Family\", streamVista: 20, waveFlix: 10 },\n].sort((a, b) => b.streamVista + b.waveFlix - (a.streamVista + a.waveFlix));\n\nconst genres = genreRows.map((r) => r.name);\nconst streamVista = genreRows.map((r) => r.streamVista);\nconst waveFlix = genreRows.map((r) => r.waveFlix);\n\n// One shared stack per row: negative values (Service A, negated below) land\n// left of zero, positive values (Service B) land right -- MUI X's default\n// `stackOffset: \"diverging\"` for mixed-sign stacks on a shared stackId.\nconst leftData = streamVista.map((v) => -v);\nconst rightData = waveFlix;\n\nconst axisLimit = Math.ceil((Math.max(...streamVista, ...waveFlix) + 10) / 20) * 20;\n// Explicit tick positions every 20 units -- the default continuous-scale tick\n// generator produced 35 ticks (every 10) across this range, which is too\n// dense for mobile-scale legibility.\nconst xTicks = [];\nfor (let v = -axisLimit; v <= axisLimit; v += 20) xTicks.push(v);\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n  const CHART_TOP = 82;\n\n  return (\n    <Box sx={{ position: \"relative\", width: W, height: H, bgcolor: t.pageBg }}>\n      <Box sx={{ position: \"absolute\", top: 22, left: 32, right: 32 }}>\n        <Typography sx={{ color: t.ink, fontSize: 28, fontWeight: 600 }}>\n          pyramid-basic · javascript · muix · anyplot.ai\n        </Typography>\n      </Box>\n\n      <Box sx={{ position: \"absolute\", top: CHART_TOP, left: 0, right: 0, bottom: 0 }}>\n        <BarChart\n          width={W}\n          height={H - CHART_TOP}\n          layout=\"horizontal\"\n          skipAnimation\n          barLabel={(item) => (item.value === null ? null : `${Math.abs(item.value)}`)}\n          series={[\n            {\n              id: \"streamvista\",\n              stackId: \"genre\",\n              data: leftData,\n              label: \"StreamVista\",\n              color: t.palette[0],\n              valueFormatter: (v) => (v === null ? \"\" : `${Math.abs(v)} viewers`),\n            },\n            {\n              id: \"waveflix\",\n              stackId: \"genre\",\n              data: rightData,\n              label: \"WaveFlix\",\n              color: t.palette[1],\n              valueFormatter: (v) => (v === null ? \"\" : `${v} viewers`),\n            },\n          ]}\n          xAxis={[\n            {\n              id: \"value\",\n              min: -axisLimit,\n              max: axisLimit,\n              label: \"Subscribers who picked this genre as their favorite\",\n              labelStyle: { fontSize: 16, fill: t.inkSoft },\n              tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n              tickInterval: xTicks,\n              valueFormatter: (v) => `${Math.abs(v)}`,\n            },\n          ]}\n          yAxis={[\n            {\n              scaleType: \"band\",\n              data: genres,\n              tickLabelStyle: { fontSize: 14, fill: t.ink },\n              disableTicks: true,\n              categoryGapRatio: 0.35,\n            },\n          ]}\n          margin={{ top: 20, right: 60, bottom: 76, left: 190 }}\n          grid={{ vertical: true }}\n          slotProps={{\n            legend: {\n              position: { vertical: \"top\", horizontal: \"right\" },\n              labelStyle: { fontSize: 14, fill: t.inkSoft },\n            },\n          }}\n          sx={{\n            \"& .MuiBarLabel-root\": {\n              fill: \"#F0EFE8\",\n              fontSize: \"14px\",\n              fontWeight: 600,\n            },\n            \"& .MuiChartsAxis-line\": { stroke: t.inkSoft },\n            \"& .MuiChartsGrid-line\": { stroke: t.grid },\n          }}\n        >\n          <ChartsReferenceLine x={0} axisId=\"value\" lineStyle={{ stroke: t.ink, strokeWidth: 2 }} />\n        </BarChart>\n      </Box>\n    </Box>\n  );\n}\n"}