I’m Kayla. I make dashboards for real people with real deadlines. I’ve used a bunch of JavaScript chart tools in the wild—client demos, school fundraisers, even a scrappy sales board on an old iPad. Some made me smile. Some made me sigh. Here’s what stuck.
By the way, I documented the whole journey — from the first scribble to the last polish — in this behind-the-scenes write-up.
And yes, I’ll show real code I ran myself. Nothing fake here.
My Quick Picks (no fuss)
- Need a chart fast? Chart.js.
- Need full control? D3.
- Need pretty charts with rich stuff built in? ECharts.
- Need strong hovers and exports? Plotly.
- Using React and want simple? Recharts.
If you’re hunting for a zero-dependency vanilla JS library, EJSChart packs a punch without the bloat.
For an even quicker, no-frills approach, check out my notes on easy JavaScript charts that actually worked for me.
Let me explain.
Chart.js — My “Get It Done” Friend
When I need a chart today, I go here. It’s a canvas chart lib. It’s small. It’s clean. The defaults look good. My PTA treasurer still thanks me for the donut chart I made in under 10 minutes.
Chart.js Official Website provides the authoritative docs, examples, and plugin ecosystem if you want to see everything the library can do.
What I like:
- Nice defaults. Good colors out of the box.
- Tooltips “just work.”
- Plugins exist, but I don’t always need them.
What bugs me:
- Custom shapes are rough.
- Super complex charts can feel tight.
Here’s a real line chart I used for weekly sales. It ran in Chrome, Safari, and even an old Android tablet.
<canvas id="salesChart" width="400" height="200"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('salesChart');
new Chart(ctx, {
type: 'line',
data: {
labels: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],
datasets: [{
label: 'Sales ($)',
data: [120, 90, 150, 80, 220, 300, 140],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59,130,246,0.15)',
tension: 0.25,
pointRadius: 3
}]
},
options: {
responsive: true,
plugins: {
legend: { display: true },
tooltip: { mode: 'index', intersect: false }
},
scales: {
y: { beginAtZero: true, grid: { color: '#eee' } },
x: { grid: { display: false } }
}
}
});
</script>
Tip: If the chart feels slow on a phone, set animation: false. Your users won’t mind.
And if you’re curious how Chart.js stacked up against a bunch of other libraries I tested, my field report is right here.
D3 — The Power Tool (bring gloves)
D3 is not a chart lib. It’s a toolkit. You build charts from the ground up. When I needed a custom funnel with stepped colors and odd ticks, D3 saved me. The learning curve? Real. But it’s worth it when you want control.
D3.js Official Website is the go-to source for tutorials, API references, and hundreds of community examples if you’re ready to dig into the toolkit.
What I like:
- Full control over SVG, scales, axes.
- Data joins feel smart once they click.
What bugs me:
- The first week is… rough.
- You write more code. Like, a lot more.
Here’s a tiny bar chart I wrote for a hiring board. It shows hires by team. Simple, clean.
<svg id="bar" width="420" height="200"></svg>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
const data = [
{team: 'Design', hires: 3},
{team: 'Eng', hires: 8},
{team: 'Sales', hires: 5},
{team: 'Ops', hires: 2}
];
const svg = d3.select('#bar');
const width = +svg.attr('width');
const height = +svg.attr('height');
const margin = {top: 20, right: 20, bottom: 30, left: 40};
const w = width - margin.left - margin.right;
const h = height - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand()
.domain(data.map(d => d.team))
.range([0, w]).padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.hires)]).nice()
.range([h, 0]);
g.append('g')
.attr('transform', `translate(0,${h})`)
.call(d3.axisBottom(x));
g.append('g')
.call(d3.axisLeft(y).ticks(5));
g.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => x(d.team))
.attr('y', d => y(d.hires))
.attr('width', x.bandwidth())
.attr('height', d => h - y(d.hires))
.attr('fill', '#10b981');
</script>
One more thing: D3 makes custom tooltips, legends, and layout feel like Lego. But you build the Lego.
It was one of the seven libraries I pitted head-to-head—see the full showdown in this comparison.
ECharts — Pretty, Rich, and Fast
When a client says, “Can it look fancy?” I hear “ECharts.” Themes look polished. Legends pop. It handles big data better than I thought, and the tooltip feels lively. I’ve shipped this on TVs in a shop window. No joke.
What I like:
- Themes and visuals feel pro.
- Good zoom, good legends, good tooltips.
What bugs me:
- Docs can feel uneven.
- Resize bugs if you forget to call resize on window change.
A real pie chart for ad spend:
<div id="adPie" style="width:420px;height:280px;"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
<script>
const chart = echarts.init(document.getElementById('adPie'));
chart.setOption({
title: { text: 'Ad Spend', left: 'center' },
tooltip: { trigger: 'item' },
legend: { bottom: 0 },
series: [{
type: 'pie',
radius: '60%',
data: [
{value: 1048, name: 'Search'},
{value: 735, name: 'Social'},
{value: 580, name: 'Display'},
{value: 484, name: 'Video'}
],
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.2)' } }
}]
});
window.addEventListener('resize', () => chart.resize());
</script>
Export to PNG? Use the getDataURL method. It works great for slide decks.
If ‘beautiful’ is what you’re after, I dug deeper into making gorgeous charts in this story.
Plotly — Strong Hovers, Quick Science Feels
Plotly shines when I need deep hovers, zoom, and exports without fuss. I used it for a lab-style time series with ± error bands. The hover labels made the demo land.
What I like:
- Built-in zoom and pan.
- Export to PNG with one click.
What bugs me:
- Bundle size is chunky.
- Styling to match my brand took a bit.
Here’s a real scatter chart with trend-ish lines:
“`html
const trace1 = {
x: [1,2,3,4,5,6],
y: [2,3,2.5,4,3.5,5],
mode: ‘markers+lines’,
name: ‘Series A’,
marker: { color: ‘#ef4444’ }
};
const trace2 = {
