Distribution / Intermediate
Box Plot
Display statistical distribution with quartiles and outliers.
Interactive Chart Preview
JAVASCRIPTCustom Drawing: A Chart.js plugin draws the supplied quartiles, median, and whisker endpoints. It does not calculate statistics from raw observations.
Edit the column names and values to update your chart. Changes stay in this page until you reload.
| Row# | Group | Lower whisker | 25th percentile | 50th percentile | 75th percentile | Upper whisker | Remove row |
|---|---|---|---|---|---|---|---|
| 1 | |||||||
| 2 | |||||||
| 3 | |||||||
| 4 |
Supply Min ≤ Q1 ≤ Median ≤ Q3 ≤ Max. Equal values, negative numbers, and decimals are allowed. These are summaries, not raw observations.
Chart.js Code Example
JAVASCRIPTimport Chart from 'chart.js/auto';
// Illustrative data used by the live preview.
const chartData = {
"boxHeaders": [
"Label",
"Min",
"Q1",
"Median",
"Q3",
"Max"
],
"boxSummary": [
{
"label": "Product A",
"min": 5,
"q1": 15,
"median": 20,
"q3": 25,
"max": 35
},
{
"label": "Product B",
"min": 8,
"q1": 20,
"median": 25,
"q3": 30,
"max": 40
},
{
"label": "Product C",
"min": 3,
"q1": 10,
"median": 15,
"q3": 20,
"max": 28
},
{
"label": "Product D",
"min": 12,
"q1": 23,
"median": 28,
"q3": 35,
"max": 45
}
],
"datasets": []
};
function createConfig(chartData) {
const boxes = chartData.boxSummary ?? [];
const headers = chartData.boxHeaders ?? ['Label', 'Min', 'Q1', 'Median', 'Q3', 'Max'];
return {
type: 'scatter',
data: {
datasets: [
{
label: headers[0],
data: boxes.map((box, x) => ({ x, y: box.median })),
pointRadius: 0,
pointHitRadius: 25,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: { display: true, text: headers[0] },
min: -0.5,
max: boxes.length - 0.5,
afterBuildTicks: (axis) => {
axis.ticks = boxes.map((_, value) => ({ value }));
},
ticks: { stepSize: 1, callback: (value) => boxes[Number(value)]?.label ?? '' },
grid: { display: false },
},
y: {
min: Math.min(0, ...boxes.map((box) => box.min)),
max: Math.max(0, ...boxes.map((box) => box.max)) || 1,
title: { display: true, text: 'Value' },
},
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
title: (items) => boxes[items[0].dataIndex].label,
label: (item) => {
const box = boxes[item.dataIndex];
return [
`${headers[1]}: ${box.min}`,
`${headers[2]}: ${box.q1}`,
`${headers[3]}: ${box.median}`,
`${headers[4]}: ${box.q3}`,
`${headers[5]}: ${box.max}`,
];
},
},
},
},
},
// Draw supplied summaries without inferring quartiles from drawing segments.
plugins: [
{
id: 'summary-boxes',
afterDatasetsDraw(chart) {
const { ctx, scales, chartArea } = chart;
const halfWidth = Math.min(22, (chartArea.width / boxes.length) * 0.2);
ctx.save();
ctx.strokeStyle = '#4054e8';
ctx.fillStyle = '#d7dfff';
ctx.lineWidth = 2;
boxes.forEach((box, i) => {
const x = scales.x.getPixelForValue(i);
const y = (value) => scales.y.getPixelForValue(value);
ctx.beginPath();
ctx.moveTo(x, y(box.min));
ctx.lineTo(x, y(box.max));
ctx.moveTo(x - halfWidth / 2, y(box.min));
ctx.lineTo(x + halfWidth / 2, y(box.min));
ctx.moveTo(x - halfWidth / 2, y(box.max));
ctx.lineTo(x + halfWidth / 2, y(box.max));
ctx.stroke();
ctx.fillRect(x - halfWidth, y(box.q3), halfWidth * 2, y(box.q1) - y(box.q3));
ctx.strokeRect(x - halfWidth, y(box.q3), halfWidth * 2, y(box.q1) - y(box.q3));
ctx.beginPath();
ctx.moveTo(x - halfWidth, y(box.median));
ctx.lineTo(x + halfWidth, y(box.median));
ctx.stroke();
});
ctx.restore();
},
},
],
};
}
const chart = new Chart(document.getElementById('myChart'), createConfig(chartData));Preview and code follow the same library selection.
Run This Example
Install the package below in a browser project such as Vite. Put the copied code in a client JavaScript module and add this container to the page before the module runs.
npm install chart.js@4.5.1<div style="height:320px"><canvas id="myChart"></canvas></div>The example includes its sample data. When removing the chart, destroy or dispose of its instance and disconnect any resize observer.
Reading This Example
These illustrative summaries compare four products. Product A has minimum 5, first quartile 15, median 20, third quartile 25, and maximum 35. The whiskers mark the supplied minimum and maximum. No sample size or outlier test can be recovered from these five values alone.
All library tabs use these illustrative values. Rendering styles and library APIs differ, so use the table to compare exact numbers.
About this example & sample data
| Product | Min | Q1 | Median | Q3 | Max |
|---|---|---|---|---|---|
| Product A | 5 | 15 | 20 | 25 | 35 |
| Product B | 8 | 20 | 25 | 30 | 40 |
| Product C | 3 | 10 | 15 | 20 | 28 |
| Product D | 12 | 23 | 28 | 35 | 45 |
READ THE CHART
Compare the Center and Spread
A box plot summarizes a numeric distribution using its median, quartiles, and whiskers. It is useful when comparing several groups, although it hides details such as multiple peaks and the number of observations.
Read the Box
The box spans the first to the third quartile, covering the middle portion of the observations. The line inside marks the median. A taller box indicates a larger interquartile range on the same numeric scale.
Check the Whisker Rule
Whiskers may use the minimum and maximum or a rule based on the interquartile range. State the convention. Points beyond the whiskers are not automatically errors and should be investigated in context.
Keep Groups Comparable
Compare groups measured in the same units and show sample sizes when they differ. A box plot summarizes a distribution, not uncertainty in an estimated mean. Small datasets and different quartile conventions can change its appearance.
Use Supplied Summaries Carefully
The Chart.js tab draws boxes with a custom plugin. Every tab uses supplied quartiles and whisker endpoints rather than estimating them from raw observations. Use a histogram when distribution shape matters.