I make dashboards for small teams. I like charts that say a lot with a little space. Horizontal stacked bars do that. They show parts and the whole at the same time. Think tasks done, in progress, and blocked. All on one line. Nice and tidy. If you’re curious about the nitty-gritty decisions that shaped my own implementation, I wrote a separate teardown that walks through every step over here.
I tried three tools on real work:
- Chart.js
- Apache ECharts
- ApexCharts
I used them in Chrome on my Windows 11 laptop and on my iPhone 13. I also tested one old Lenovo in the office that wheezes when you open Figma. You know the type. I later expanded that bake-off to seven different JavaScript chart libraries, and the brutally honest results are summarized in this deeper comparison.
So, how did it go? Let me explain.
Quick read: my take
- Chart.js felt the most friendly. Great for small to mid data. Easy to theme.
- ECharts handled big data and long labels. Rich tooltips. A bit heavier.
- ApexCharts was fast to set up. Good defaults. Clean UI.
If you just want the straight-to-the-point recipe for making JavaScript charts feel painless, my condensed notes live right here.
I kept all three in my toolbox. But I reach for Chart.js first when time is tight.
Real example: team status with Chart.js
Use it when you want a simple setup with canvas. It’s light and easy. I used this for a weekly standup board. For an authoritative walkthrough of the available props and caveats, the official Chart.js horizontal bar chart documentation is a handy companion.
HTML
<canvas id="statusChart" height="300"></canvas>
JavaScript (Chart.js v4+)
const ctx = document.getElementById('statusChart');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Design', 'Frontend', 'Backend', 'QA'],
datasets: [
{ label: 'Done', data: [8, 6, 5, 7], backgroundColor: '#22c55e', stack: 'total' },
{ label: 'In Progress', data: [3, 5, 6, 2], backgroundColor: '#f59e0b', stack: 'total' },
{ label: 'Blocked', data: [1, 2, 1, 3], backgroundColor: '#ef4444', stack: 'total' }
]
},
options: {
indexAxis: 'y', // this makes it horizontal
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' },
tooltip: {
callbacks: {
label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.x}`
}
}
},
scales: {
x: { stacked: true, title: { display: true, text: 'Tickets' } },
y: { stacked: true }
}
}
});
What I liked:
- Colors looked crisp on canvas. Even on my older Lenovo.
- Stacking “just worked.” No fuss.
- The legend was easy to move.
What bugged me a bit:
- Long labels can get chopped. I had to shorten “Frontend Platform” to “FE Platform.”
- Data labels need a plugin. It’s fine, but it’s one more step.
Tiny tip:
- If you want data labels, add chartjs-plugin-datalabels and set formatter to show values on each bar.
For even more real-world Chart.js war stories, I hammered together an entire project and documented the wins and bruises in this write-up.
Real example: budget vs actual with ECharts
I used ECharts for a Q3 report. We had 40 rows and long team names. It held up well. You can also sanity-check the stacking logic against the Apache ECharts stacked bar handbook if you need to dig deeper into advanced features.
HTML
<div id="budgetChart" style="height:400px;"></div>
JavaScript
const chart = echarts.init(document.getElementById('budgetChart'));
const option = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {},
grid: { left: '2%', right: '3%', bottom: '3%', containLabel: true },
xAxis: { type: 'value', name: 'USD (k)' },
yAxis: { type: 'category', data: ['Design', 'Frontend', 'Backend', 'QA'] },
series: [
{ name: 'Planned', type: 'bar', stack: 'total', data: [120, 150, 180, 90], itemStyle: { color: '#60a5fa' } },
{ name: 'Spent', type: 'bar', stack: 'total', data: [100, 140, 170, 80], itemStyle: { color: '#34d399' } },
{ name: 'Over/Under', type: 'bar', stack: 'total', data: [20, 10, 10, 10], itemStyle: { color: '#fbbf24' } }
],
aria: { enabled: true }
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
What I liked:
- Long labels wrap. No sweat.
- Tooltips felt rich and fast.
- It has built-in aria support. My PM loved that.
What didn’t land:
- Bundle is bigger. Load time on weak Wi-Fi felt slower.
- The API has many options. Powerful, but you can get lost.
Side note:
- I used
aria.enabled = truefor screen readers. It’s not perfect, but it’s there.
For a quick visual walkthrough of drawing similar charts from scratch, you can skim the screenshots in this guide.
Real example: hiring pipeline with ApexCharts
This was for HR. We tracked candidates by stage. Clean look. Quick start.
HTML
<div id="hiringChart"></div>
JavaScript
const options = {
chart: { type: 'bar', height: 360, stacked: true, toolbar: { show: false } },
plotOptions: { bar: { horizontal: true, barHeight: '60%' } },
series: [
{ name: 'Phone Screen', data: [8, 5, 6] },
{ name: 'Onsite', data: [4, 3, 2] },
{ name: 'Offer', data: [2, 1, 1] }
],
xaxis: { categories: ['Design', 'Engineering', 'Data'] },
colors: ['#93c5fd', '#f59e0b', '#22c55e'],
legend: { position: 'bottom' },
dataLabels: { enabled: true, formatter: (v) => v }
};
const chart = new ApexCharts(document.querySelector('#hiringChart'), options);
chart.render();
What made me smile:
- Data labels were easy to show. No plugin.
- Legend and spacing looked neat by default.
Where I had to tweak:
- On tiny phones, labels can overlap. I bumped barHeight down to 50% and it felt better.
- If you push very large data, it can feel snug.
Small real-world gotchas I hit
- Colors and contrast: My first theme looked cute but failed contrast checks. I bumped text to #111 and used deeper greens and blues. Much better.
- RTL text: A teammate in Tel Aviv asked for Hebrew labels. ECharts handled it fine. For Chart.js, I had to set font and check spacing. It worked, but I had to test more.
- Printing: Canvas charts can look soft in print. SVG (like ECharts) stays sharp. I export PNGs from canvas at 2x for reports.
- Tooltips on touch: Long press works, but not everyone knows it. I added data labels to help.
- Export: I used
toBase64Image()in Chart.js for quick exports. Handy for slides.
Sharing chart snapshots sometimes means firing them off in disappearing-photo apps. If you’re tempted to drop your latest graph into a Snapchat message, this primer on snap sexting explains the platform’s privacy quirks and offers tips to make sure your images don’t stick around longer than you intend.
On a related privacy note, if you’re interested in how modern classified personals sites approach image handling, moderation, and user anonymity in a city-specific setting, you can explore [MegaPerson
