Build a Box Plot Calculator in Pure JavaScript — No Libraries Needed
A box plot (or box-and-whisker plot) is one of the most powerful tools in exploratory data analysis. In one compact graphic, it reveals the median, spread, skewness, and outliers of a dataset — all from just five numbers.
In this tutorial, I'll walk through building a complete box plot calculator from scratch using vanilla JavaScript and SVG. No D3, no Chart.js, no dependencies. By the end, you'll understand:
- How to compute quartiles the right way
- How to detect outliers with Tukey's fences
- How to render a box plot as SVG
- Why all of this matters for real-world data analysis
If you want to skip the code and use a ready-made tool, I built aiboxplot.com — a free, no-sign-up platform that does all of this plus AI analysis, multi-dataset comparison, and one-click export to PNG/SVG/CSV. The rendering engine is open source too.
Try it now → aiboxplot.com
1. The Five-Number Summary
Every box plot is built on five values:
| Metric | Description |
|---|---|
| Minimum | The smallest value that's not an outlier |
| Q1 (first quartile) | 25th percentile — 25% of data falls below this |
| Median (Q2) | 50th percentile — the middle value |
| Q3 (third quartile) | 75th percentile |
| Maximum | The largest value that's not an outlier |
Plus one more: the Interquartile Range (IQR) = Q3 − Q1. This measures the spread of the middle 50% and is the key to detecting outliers.
2. Computing Quartiles Correctly
The most common mistake in box plot calculators is using the wrong quartile method. There are at least 9 different ways to compute percentiles (see Hyndman & Fan, 1996). I use linear interpolation (method 7), which is the default in Python's NumPy and pandas:
/**
* Compute a percentile value using linear interpolation.
* @param {number[]} sorted - Sorted array of numbers
* @param {number} p - Percentile as a fraction (0.25 = Q1, 0.5 = median, 0.75 = Q3)
*/
function percentile(sorted, p) {
if (sorted.length === 0) return 0;
if (sorted.length === 1) return sorted[0];
const n = sorted.length;
const h = (n - 1) * p; // Real-valued index
const lo = Math.floor(h);
const hi = Math.ceil(h);
if (lo === hi) return sorted[lo];
// Linear interpolation between lo and hi
return sorted[lo] + (h - lo) * (sorted[hi] - sorted[lo]);
}
Why this matters: for a dataset of [1, 2, 3, 4, 5], the median is 3. But for [1, 2, 3, 4], using simple rounding gives 2, while linear interpolation correctly gives 2.5. These small differences compound when you're analyzing real data.
3. Detecting Outliers with Tukey's Fences
John Tukey's fences method is elegant, simple, and still the industry standard:
function calculateStats(data, iqrMultiplier = 1.5) {
const sorted = [...data].sort((a, b) => a - b);
const n = sorted.length;
const mean = sorted.reduce((a, b) => a + b, 0) / n;
const q1 = percentile(sorted, 0.25);
const median = percentile(sorted, 0.5);
const q3 = percentile(sorted, 0.75);
const iqr = q3 - q1;
// Tukey's fences
const lowerFence = q1 - iqrMultiplier * iqr;
const upperFence = q3 + iqrMultiplier * iqr;
// Values outside fences are potential outliers
const outliers = sorted.filter(v => v < lowerFence || v > upperFence);
// Non-outlier range defines the whiskers
const nonOutliers = sorted.filter(v => v >= lowerFence && v <= upperFence);
const min = nonOutliers[0];
const max = nonOutliers[nonOutliers.length - 1];
return { min, q1, median, q3, max, iqr, mean, lowerFence, upperFence, outliers, count: n };
}
The 1.5 multiplier is the standard. Use 2.0 for a more conservative detection, or 3.0 for extreme outliers only. At aiboxplot.com, you can switch between these thresholds in real time.
4. Rendering as SVG
Now for the fun part — turning numbers into a chart. Here's the core SVG rendering logic:
function renderBoxPlot(stats, width = 500, height = 200) {
const { min, q1, median, q3, max, outliers } = stats;
// Scale setup
const padL = 50, padR = 30, padT = 20, padB = 40;
const plotW = width - padL - padR;
const plotH = height - padT - padB;
const dataMin = Math.min(min, ...outliers);
const dataMax = Math.max(max, ...outliers);
const range = dataMax - dataMin || 1;
const paddedMin = dataMin - range * 0.08;
const paddedMax = dataMax + range * 0.08;
const paddedRange = paddedMax - paddedMin;
// Map data value → SVG x-coordinate
const sx = (v) => padL + plotW * ((v - paddedMin) / paddedRange);
const boxY = padT + plotH * 0.2;
const boxH = plotH * 0.6;
const yc = padT + plotH / 2;
const color = "#2563eb";
return `
<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
<!-- Axis -->
<line x1="${padL}" y1="${padT + plotH}"
x2="${padL + plotW}" y2="${padT + plotH}"
stroke="#d1d5db" stroke-width="0.5"/>
<!-- Left whisker -->
<line x1="${sx(min)}" y1="${yc - boxH / 4}"
x2="${sx(min)}" y2="${yc + boxH / 4}"
stroke="${color}" stroke-width="1.5" stroke-linecap="round"/>
<line x1="${sx(min)}" y1="${yc}"
x2="${sx(q1)}" y2="${yc}"
stroke="${color}" stroke-width="0.5" stroke-dasharray="3 3"/>
<!-- Box (Q1 to Q3) -->
<rect x="${sx(q1)}" y="${boxY}"
width="${sx(q3) - sx(q1)}" height="${boxH}"
fill="${color}" opacity="0.15" stroke="${color}" stroke-width="1.5" rx="2"/>
<!-- Median line -->
<line x1="${sx(median)}" y1="${boxY}"
x2="${sx(median)}" y2="${boxY + boxH}"
stroke="${color}" stroke-width="2" stroke-linecap="round"/>
<!-- Right whisker -->
<line x1="${sx(q3)}" y1="${yc}"
x2="${sx(max)}" y2="${yc}"
stroke="${color}" stroke-width="0.5" stroke-dasharray="3 3"/>
<line x1="${sx(max)}" y1="${yc - boxH / 4}"
x2="${sx(max)}" y2="${yc + boxH / 4}"
stroke="${color}" stroke-width="1.5" stroke-linecap="round"/>
<!-- Outlier dots -->
${outliers.map(o => `<circle cx="${sx(o)}" cy="${yc}" r="4"
fill="none" stroke="${color}" stroke-width="1.5"/>`).join("")}
</svg>
`;
}
The key insight: SVG is just XML. A box plot is just <line>, <rect>, and <circle> elements — no canvas, no external libraries.
5. Putting It All Together
// Example: exam scores from two classes
const dataA = [72, 85, 78, 90, 65, 88, 76, 92, 70, 84, 79, 86, 74, 91, 68, 83, 77, 89, 71, 95];
const dataB = [60, 62, 58, 70, 55, 68, 72, 64, 68, 61, 75, 66, 53, 77, 69, 71, 59, 63, 67, 74];
const statsA = calculateStats(dataA);
const statsB = calculateStats(dataB);
console.log("Class A — Median:", statsA.median, "IQR:", statsA.iqr);
console.log("Class B — Median:", statsB.median, "IQR:", statsB.iqr);
console.log("Outliers in A:", statsA.outliers);
console.log("Outliers in B:", statsB.outliers);
document.getElementById("chart-a").innerHTML = renderBoxPlot(statsA);
document.getElementById("chart-b").innerHTML = renderBoxPlot(statsB);
Side-by-side comparison instantly reveals:
- Class A has a higher median and wider spread
- No outliers in either class
- Class B scores are more tightly clustered
This kind of insight takes seconds with a box plot — and pages of text to describe otherwise.
From Demo to Production
Building a working box plot is straightforward. Building a great one takes more:
- Multi-dataset comparison — stacking boxes vertically with independent colors
- Notched boxes — 95% confidence intervals around the median (McGill-Tukey-Kramer method)
- Jitter scatter overlays — showing every raw data point without overlap
- Responsive scaling — charts that look sharp at any screen size
- Export to PNG/SVG — for papers, presentations, and reports
- AI-powered analysis — automatic insights about distribution shape and skewness
I built aiboxplot.com to solve all of this — with 8 chart types, AI chat, and zero sign-up required. The rendering engine behind it is open source so you can see exactly how every calculation and SVG element is constructed.
Make your first box plot → aiboxplot.com
Key Takeaways
- Quartiles matter — use linear interpolation for correct results
- Tukey's fences are the standard for outlier detection
- SVG is underrated for data visualization — pure XML, no build step
- Box plots compress thousands of data points into one clear graphic
- All computation can be client-side — your data never needs to leave the browser
Did this help? Let me know in the comments, or show me what you build! And if you want a ready-made solution for your next data project, give aiboxplot.com a try.
Top comments (0)