A kernel density estimation (KDE) plot combined with rug marks along the x-axis, showing both the smoothed probability distribution and the exact location of each individual data point. This combination provides the best of both worlds: the KDE reveals the overall shape, modality, and smoothed density of the distribution, while the rug marks preserve transparency about where actual observations fall, highlighting data density and potential gaps.

// anyplot.ai
// density-rug: Density Plot with Rug Marks
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 91/100 | Created: 2026-09-02
import { LineChart } from "@mui/x-charts/LineChart";
import { useXScale, useDrawingArea } from "@mui/x-charts/hooks";
const t = window.ANYPLOT_TOKENS;
const TITLE = "density-rug · javascript · muix · anyplot.ai";
const TITLE_HEIGHT = 56;
// --- Data (in-memory, deterministic): petal lengths from two wildflower
// populations surveyed in the same meadow, merged into one sample. -----------
// Small LCG so results are reproducible without a seeded Math.random().
function lcg(seed) {
let state = seed;
return () => {
state = (state * 1664525 + 1013904223) % 4294967296;
return state / 4294967296;
};
}
const rand = lcg(42);
function randomNormal() {
const u1 = Math.max(rand(), 1e-9);
const u2 = rand();
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}
const POP_A_SIZE = 75;
const POP_A_MEAN = 4.2;
const POP_A_SD = 0.4;
const POP_B_SIZE = 65;
const POP_B_MEAN = 6.6;
const POP_B_SD = 0.5;
const petalLengths = [
...Array.from(
{ length: POP_A_SIZE },
() => POP_A_MEAN + POP_A_SD * randomNormal(),
),
...Array.from(
{ length: POP_B_SIZE },
() => POP_B_MEAN + POP_B_SD * randomNormal(),
),
];
// --- Gaussian KDE -------------------------------------------------------
function gaussianKernel(u) {
return Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI);
}
const n = petalLengths.length;
const sampleMean = petalLengths.reduce((sum, v) => sum + v, 0) / n;
const variance =
petalLengths.reduce((sum, v) => sum + (v - sampleMean) ** 2, 0) / (n - 1);
// Narrower than Silverman's rule of thumb, otherwise the two source
// populations blur into a single smoothed hump instead of staying distinct —
// the rug marks below then confirm the resulting gap is real, not a KDE artifact.
const bandwidth = 0.55 * Math.sqrt(variance) * n ** (-1 / 5);
const dataMin = Math.min(...petalLengths);
const dataMax = Math.max(...petalLengths);
const GRID_POINTS = 200;
const gridStart = dataMin - 3 * bandwidth;
const gridEnd = dataMax + 3 * bandwidth;
const gridStep = (gridEnd - gridStart) / (GRID_POINTS - 1);
const grid = Array.from(
{ length: GRID_POINTS },
(_, i) => gridStart + i * gridStep,
);
const density = grid.map(
(x) =>
petalLengths.reduce(
(sum, xi) => sum + gaussianKernel((x - xi) / bandwidth),
0,
) /
(n * bandwidth),
);
// --- Rug marks: one short tick per raw observation, anchored to the plot's
// bottom edge via the chart's own x-scale and drawing-area geometry. ---------
function RugMarks({ values, color }) {
const xScale = useXScale();
const { top, height } = useDrawingArea();
const axisY = top + height;
const tickLength = 18;
return (
<g>
{values.map((value, i) => (
<line
key={i}
x1={xScale(value)}
x2={xScale(value)}
y1={axisY}
y2={axisY - tickLength}
stroke={color}
strokeWidth={1.5}
strokeOpacity={0.55}
/>
))}
</g>
);
}
export default function Chart() {
const chartHeight = window.ANYPLOT_SIZE.height - TITLE_HEIGHT;
return (
<div
style={{
width: window.ANYPLOT_SIZE.width,
height: window.ANYPLOT_SIZE.height,
}}
>
<div
style={{
height: TITLE_HEIGHT,
lineHeight: `${TITLE_HEIGHT}px`,
paddingLeft: 24,
fontSize: 22,
fontWeight: 600,
color: t.ink,
}}
>
{TITLE}
</div>
<LineChart
width={window.ANYPLOT_SIZE.width}
height={chartHeight}
margin={{ left: 110, right: 40, top: 20, bottom: 60 }}
skipAnimation
series={[
{
data: density,
label: "Density",
color: t.palette[0],
area: true,
curve: "natural",
showMark: false,
},
]}
xAxis={[
{
data: grid,
scaleType: "linear",
label: "Petal Length (cm)",
labelStyle: { fontSize: 16 },
tickLabelStyle: { fontSize: 14 },
valueFormatter: (v) => v.toFixed(1),
},
]}
yAxis={[
{
label: "Density",
labelStyle: { fontSize: 16 },
// tickFontSize only drives the axis-label offset (see ChartsYAxis
// labelRefPoint) — actual tick glyphs stay at tickLabelStyle's 14px.
tickFontSize: 40,
tickLabelStyle: { fontSize: 14 },
valueFormatter: (v) => v.toFixed(2),
},
]}
grid={{ horizontal: true }}
slotProps={{ legend: { hidden: true } }}
sx={{
"& .MuiAreaElement-root": { fillOpacity: 0.3 },
"& .MuiLineElement-root": { strokeWidth: 3 },
}}
>
<RugMarks values={petalLengths} color={t.palette[0]} />
</LineChart>
</div>
);
}
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/density-rug/muix/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "density-rug",
"language": "javascript",
"library": "muix",
"page": "https://anyplot.ai/density-rug/javascript/muix",
"hub": "https://anyplot.ai/density-rug",
"code_json": "https://api.anyplot.ai/specs/density-rug/muix/code",
"spec_json": "https://api.anyplot.ai/specs/density-rug",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/density-rug/javascript/muix/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/density-rug/javascript/muix/plot-dark.png",
"interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/density-rug/javascript/muix/plot-light.html",
"interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/density-rug/javascript/muix/plot-dark.html",
"quality_score": 91.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Density Plot with Rug Marks on anyplot.ai.