{"spec_id":"rug-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// rug-basic: Basic Rug Plot\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-25\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { useXScale, useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst FONT = \"Inter, system-ui, -apple-system, sans-serif\";\n\n// Seeded LCG for deterministic data generation (no seeded RNG in browser)\nlet seed = 42;\nfunction rand() {\n  seed = (Math.imul(1664525, seed) + 1013904223) | 0;\n  return (seed >>> 0) / 4294967296;\n}\nfunction randn() {\n  const u = Math.max(rand(), 1e-10);\n  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * rand());\n}\n\n// Reaction times (ms) in a visual cognitive task, two testing sessions.\n// Each observation tracks whether it is a slow-trial \"attention lapse\" so the\n// tail can be emphasized visually rather than only in a code comment.\nconst morning = Array.from({ length: 140 }, () => ({ v: 420 + 55 * randn(), outlier: false }));\nconst evening = Array.from({ length: 140 }, () => ({ v: 458 + 70 * randn(), outlier: false }));\nmorning.push({ v: 636, outlier: true });\nevening.push({ v: 688, outlier: true }, { v: 706, outlier: true }, { v: 727, outlier: true });\n\nconst LANES = [\n  { label: \"Morning session\", values: morning, color: t.palette[0], strokeLabel: false },\n  { label: \"Evening session\", values: evening, color: t.palette[1], strokeLabel: true },\n];\n\nconst allValues = LANES.flatMap((lane) => lane.values.map((d) => d.v));\nconst X_MIN = Math.floor(Math.min(...allValues) / 20) * 20 - 20;\nconst X_MAX = Math.ceil(Math.max(...allValues) / 20) * 20 + 20;\n\nconst TITLE_H = 60;\nconst NORMAL_TICK_FRAC = 0.72; // fraction of the lane band used by regular ticks\nconst OUTLIER_TICK_FRAC = 0.92; // outlier ticks read taller, at a glance\n\n// One rug lane: a subtle tinted background band plus one semi-transparent\n// tick per observation. Overlapping trials (frequent near the distribution\n// centre) darken naturally. Outlier \"attention lapse\" trials are drawn\n// taller and more opaque so the tail reads at a glance, not just on close\n// reading.\nfunction RugLane({ values, color, top, laneHeight }) {\n  const xs = useXScale();\n  const { left, width } = useDrawingArea();\n  if (!xs) return null;\n  const baselineY = top + laneHeight;\n  const normalHeight = laneHeight * NORMAL_TICK_FRAC;\n  const outlierHeight = laneHeight * OUTLIER_TICK_FRAC;\n  return (\n    <g>\n      <rect x={left} y={top} width={width} height={laneHeight} fill={color} opacity={0.05} />\n      {values.map((d, i) => (\n        <line\n          key={i}\n          x1={xs(d.v)}\n          x2={xs(d.v)}\n          y1={baselineY}\n          y2={baselineY - (d.outlier ? outlierHeight : normalHeight)}\n          stroke={color}\n          strokeWidth={d.outlier ? 2.5 : 2}\n          strokeOpacity={d.outlier ? 0.85 : 0.42}\n          strokeLinecap=\"round\"\n        />\n      ))}\n    </g>\n  );\n}\n\nfunction LaneLabel({ text, top, color, strokeLabel }) {\n  const { left } = useDrawingArea();\n  return (\n    <text\n      x={left}\n      y={top + 20}\n      fontSize={16}\n      fontFamily={FONT}\n      fontWeight={600}\n      fill={color}\n      stroke={strokeLabel ? t.ink : \"none\"}\n      strokeWidth={strokeLabel ? 0.75 : 0}\n      paintOrder=\"stroke\"\n    >\n      {text}\n    </text>\n  );\n}\n\n// Two rug lanes sharing one x-axis, evenly split within the drawing area.\n// Each lane gets its own faint tinted band instead of a dividing rule, so\n// the split reads without adding an extra line of visual clutter.\nfunction RugPlot() {\n  const { top, height } = useDrawingArea();\n  const laneHeight = height / 2;\n  const laneTops = [top, top + laneHeight];\n  return (\n    <g>\n      {LANES.map((lane, i) => (\n        <RugLane key={lane.label} values={lane.values} color={lane.color} top={laneTops[i]} laneHeight={laneHeight} />\n      ))}\n      {LANES.map((lane, i) => (\n        <LaneLabel\n          key={lane.label}\n          text={lane.label}\n          top={laneTops[i]}\n          color={lane.color}\n          strokeLabel={lane.strokeLabel}\n        />\n      ))}\n    </g>\n  );\n}\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  return (\n    <div\n      style={{\n        width: W,\n        height: H,\n        display: \"flex\",\n        flexDirection: \"column\",\n        backgroundColor: t.pageBg,\n        fontFamily: FONT,\n      }}\n    >\n      <div\n        style={{\n          padding: \"18px 36px 0\",\n          fontSize: 21,\n          fontWeight: 600,\n          color: t.ink,\n          height: TITLE_H,\n          boxSizing: \"border-box\",\n          letterSpacing: \"-0.3px\",\n        }}\n      >\n        Reaction Times by Session · rug-basic · javascript · muix · anyplot.ai\n      </div>\n      <ChartContainer\n        width={W}\n        height={H - TITLE_H}\n        skipAnimation\n        series={[]}\n        margin={{ top: 24, right: 60, bottom: 100, left: 60 }}\n        xAxis={[\n          {\n            min: X_MIN,\n            max: X_MAX,\n            scaleType: \"linear\",\n            label: \"Reaction Time (ms)\",\n            tickMinStep: 40,\n            valueFormatter: (v) => String(Math.round(v)),\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        sx={{\n          \"& .MuiChartsAxis-line\": { stroke: t.inkSoft },\n          \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft },\n          \"& .MuiChartsGrid-line\": { strokeOpacity: 0.15 },\n        }}\n      >\n        <ChartsGrid vertical />\n        <RugPlot />\n        <ChartsXAxis />\n      </ChartContainer>\n    </div>\n  );\n}\n"}