{"spec_id":"density-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// density-basic: Basic Density Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-08-24\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst TITLE = \"density-basic · javascript · muix · anyplot.ai\";\nconst TITLE_HEIGHT = 56;\n\n// --- Data (in-memory, deterministic): marathon finish times, right-skewed ---\n// Small LCG so results are reproducible without a seeded Math.random().\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\n\nconst rand = lcg(42);\n\nfunction randomNormal() {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst SAMPLE_SIZE = 400;\nconst LOG_MEAN = Math.log(230); // median finish time ~230 minutes\nconst LOG_SD = 0.18; // slower-runner tail produces the right skew\n\nconst finishTimes = Array.from({ length: SAMPLE_SIZE }, () =>\n  Math.exp(LOG_MEAN + LOG_SD * randomNormal()),\n);\n\n// --- Gaussian KDE (Silverman's rule of thumb for bandwidth) -----------------\nfunction gaussianKernel(u) {\n  return Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI);\n}\n\nconst n = finishTimes.length;\nconst meanTime = finishTimes.reduce((sum, v) => sum + v, 0) / n;\nconst variance =\n  finishTimes.reduce((sum, v) => sum + (v - meanTime) ** 2, 0) / (n - 1);\n// Multiplier bumped well above the textbook 1.06 so the sparse right tail\n// (a handful of very slow finishers) decays smoothly instead of showing a\n// spurious secondary bump around individual outlier points.\nconst bandwidth = 1.8 * Math.sqrt(variance) * n ** (-1 / 5);\n\nconst dataMin = Math.min(...finishTimes);\nconst dataMax = Math.max(...finishTimes);\nconst GRID_POINTS = 200;\nconst gridStart = dataMin - 3 * bandwidth;\nconst gridStep = (dataMax + 3 * bandwidth - gridStart) / (GRID_POINTS - 1);\n\nconst grid = Array.from(\n  { length: GRID_POINTS },\n  (_, i) => gridStart + i * gridStep,\n);\n// Scaled ×1000 so y-axis ticks read as clean one-decimal numbers.\nconst density = grid.map(\n  (x) =>\n    (1000 *\n      finishTimes.reduce(\n        (sum, xi) => sum + gaussianKernel((x - xi) / bandwidth),\n        0,\n      )) /\n    (n * bandwidth),\n);\n\n// Focal-point annotation: mark the modal (peak-density) finish time.\nconst peakIndex = density.indexOf(Math.max(...density));\nconst peakFinishTime = Math.round(grid[peakIndex]);\n\nexport default function Chart() {\n  const chartHeight = window.ANYPLOT_SIZE.height - TITLE_HEIGHT;\n\n  return (\n    <div\n      style={{\n        width: window.ANYPLOT_SIZE.width,\n        height: window.ANYPLOT_SIZE.height,\n      }}\n    >\n      <div\n        style={{\n          height: TITLE_HEIGHT,\n          lineHeight: `${TITLE_HEIGHT}px`,\n          paddingLeft: 24,\n          fontSize: 22,\n          fontWeight: 600,\n          color: t.ink,\n        }}\n      >\n        {TITLE}\n      </div>\n      <LineChart\n        width={window.ANYPLOT_SIZE.width}\n        height={chartHeight}\n        margin={{ left: 120, right: 40, top: 20, bottom: 60 }}\n        skipAnimation\n        series={[\n          {\n            data: density,\n            label: \"Density\",\n            color: t.palette[0],\n            area: true,\n            curve: \"natural\",\n            showMark: false,\n          },\n        ]}\n        xAxis={[\n          {\n            data: grid,\n            scaleType: \"linear\",\n            label: \"Marathon Finish Time (minutes)\",\n            labelStyle: { fontSize: 16 },\n            tickLabelStyle: { fontSize: 14 },\n            valueFormatter: (v) => v.toFixed(0),\n          },\n        ]}\n        yAxis={[\n          {\n            label: \"Density (×10⁻³)\",\n            labelStyle: { fontSize: 16 },\n            // tickFontSize only drives the axis-label offset (see ChartsYAxis\n            // labelRefPoint) — actual tick glyphs stay at tickLabelStyle's 14px.\n            tickFontSize: 40,\n            tickLabelStyle: { fontSize: 14 },\n            valueFormatter: (v) => v.toFixed(1),\n          },\n        ]}\n        grid={{ horizontal: true }}\n        slotProps={{ legend: { hidden: true } }}\n        sx={{\n          \"& .MuiAreaElement-root\": { fillOpacity: 0.35 },\n          \"& .MuiLineElement-root\": { strokeWidth: 3 },\n        }}\n      >\n        <ChartsReferenceLine\n          x={peakFinishTime}\n          label={`Peak ≈ ${peakFinishTime} min`}\n          labelAlign=\"end\"\n          labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n          lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"4 4\" }}\n        />\n      </LineChart>\n    </div>\n  );\n}\n"}