
I’m Kayla, and yep, I made a bunch of donut charts last month. Work dashboards. A school fundraiser page. Even my own food log. I tried five tools: Chart.js, D3, ApexCharts, ECharts, and Highcharts. For a deeper side-by-side look at these libraries, check out this comprehensive comparison of JavaScript charting libraries.
Since then I’ve also kept my eye on a newer lightweight option, EJSChart, which promises the same smooth donuts with a fraction of the setup.
If you’d like an even deeper dive—including bundle sizes, build notes, and a few extra “gotchas” I skipped here—check out the full comparison post where I document every test run.
You know what? A donut chart looks simple. But little details matter. Labels wrap. Colors clash. Center text gets wonky. I learned the hard way, so you don’t have to.
What I needed, in plain words
- Fast setup for a sales dashboard with 4–6 slices
- Clean labels that don’t pile up on mobile
- A center label that says “Total” or a short note
- Good colors that work in dark mode
- OK performance on a cheap Android phone
- Screen reader text, at least the basics
Feeling short on interesting sample data to test your next donut? I recently played with dating-app response stats pulled from PlanCulFacile — they publish anonymised match and message metrics you can download as CSV, perfect for stress-testing any of the chart libraries below with a fresh, real-world dataset. For something with a bit more geographical spice, the classifieds-style dataset over at Backpage Crystal breaks down listing counts by city and category, giving you granular numbers you can segment into eye-catching donut slices.
I also cared about bundle size a bit, but not more than speed and clarity. If you’re hunting for benchmarks to back up your own choices, there’s an in-depth analysis of the performance and scalability of various JavaScript charting libraries that’s worth a skim.
Chart.js: my quick win (and my go-to)
I used Chart.js for a sales donut in our store dashboard. I had coffee, ten minutes, and a messy CSV. It still worked. It was fast, and the look was clean.
What I liked:
- Very quick start
- Colors look nice with simple tweaks
- Animations feel smooth
- Works well with React and plain HTML
What bugged me:
- No built-in center text (you can add a tiny plugin)
- Legends can wrap odd on very small screens
Here’s the exact snippet I used for “Sales by Category” (four slices). Took me under ten minutes, start to finish.
<canvas id="sales"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('sales');
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Shoes','Hats','Bags','Socks'],
datasets: [{
data: [45, 25, 20, 10],
backgroundColor: ['#4F46E5', '#06B6D4', '#10B981', '#F59E0B'],
borderWidth: 0
}]
},
options: {
cutout: '60%',
plugins: {
legend: { position: 'bottom' },
tooltip: {
callbacks: {
label: (ctx) => {
const total = ctx.dataset.data.reduce((a,b)=>a+b, 0);
const val = ctx.parsed;
const pct = Math.round((val/total)*100);
return `${ctx.label}: ${val} (${pct}%)`;
}
}
}
},
animation: { animateRotate: true, duration: 700 }
},
plugins: [{
id: 'centerText',
beforeDraw(chart) {
const {ctx, chartArea} = chart;
if (!chartArea) return;
const x = (chartArea.left + chartArea.right) / 2;
const y = (chartArea.top + chartArea.bottom) / 2;
ctx.save();
ctx.font = '600 14px system-ui';
ctx.fillStyle = '#334155';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Sales', x, y);
ctx.restore();
}
}]
});
</script>
Speed to first chart: about 5 minutes. Not joking.
Dark mode tip: switch text to lighter gray and nudge colors up a notch.
D3: full control, more work
This one is for folks who like to tinker. I used D3 for a fitness page where I needed exact sizes and a custom arc. I wanted a label in the center and a small legend at the top. No fluff.
What I liked:
- Pixel-level control
- Great for custom labels and arcs
- Scales and color control feel pro
What bugged me:
- More code
- You must add ARIA and keyboard bits yourself
This is the “Macros” donut I built for my own meal plan:
<div id="macro"></div>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script>
const data = [
{ label: 'Protein', value: 32 },
{ label: 'Carbs', value: 48 },
{ label: 'Fat', value: 20 }
];
const width = 260, height = 260, r = 110;
const color = d3.scaleOrdinal().range(['#EF4444','#3B82F6','#10B981']);
const arc = d3.arc().innerRadius(65).outerRadius(r);
const pie = d3.pie().value(d => d.value);
const svg = d3.select('#macro').append('svg')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('role', 'img')
.attr('aria-label', 'Daily macros donut chart')
.append('g')
.attr('transform', `translate(${width/2},${height/2})`);
svg.selectAll('path')
.data(pie(data))
.enter().append('path')
.attr('d', arc)
.attr('fill', d => color(d.data.label));
svg.append('text')
.attr('text-anchor','middle')
.attr('dy','0.35em')
.style('font-weight','600')
.text('Macros');
</script>
If you need the chart to say exactly what you see in your head, D3 hits the mark. But yes, it takes more time.
ApexCharts: neat defaults, nice center labels
I used ApexCharts for a budget report because it showed a “total” in the hole with no fuss. The legends felt tidy. The default font looked sharp too.
What I liked:
- Center labels built in
- Simple config for tooltips and legends
- Handy responsive settings
What bugged me:
- Some presets look “heavy” with too many effects
- Need to tone down labels on tiny screens
Real budget example:
<div id="budget"></div>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script>
var options = {
chart: { type: 'donut', height: 260 },
series: [1200, 900, 300],
labels: ['Rent','Food','Fun'],
colors: ['#6366F1', '#22C55E', '#F97316'],
dataLabels: { enabled: false },
legend: { position: 'bottom' },
plotOptions: {
pie: {
donut: {
size: '60%',
labels: { show: true, total: { show: true, label: 'Budget' } }
}
}
},
tooltip: { y: { formatter: val => '#039; + val } },
responsive: [{
breakpoint: 480,
options: { legend: { position: 'bottom' } }
}]
};
new ApexCharts(document.querySelector('#budget'), options).render();
</script>
My boss liked this one best. The “Budget” total in the center sold it.
ECharts: rich, bold, and fast on mobile
For a school bake sale site, I used ECharts. It looked fancy with very little work, and it ran smooth on an old tablet. The theme support helped me match the school colors.
What I liked:
- Very smooth on touch devices
- Strong visuals
- Good tooltip format out of the box
What bugged me:
- The config can feel long
- Labels can get busy if you show them all
Bake sale orders by channel:
“`html
var chart = echarts.init(document.getElementById(‘orders’));
var option = {
tooltip: { trigger: ‘item’, formatter: ‘{b}: {c} ({d}%)’ },
legend: { bottom: 0

