I’m Kayla, and I actually used these. I needed radar charts for a team skills map and a snack-tasting game. Weird mix, I know. But that’s real life. I tried Chart.js, Apache ECharts, ApexCharts, and even a tiny D3 build. Some nights it was smooth. Some nights… not so much. Here’s what worked for me, with real code I ran. If you want a blow-by-blow narrative of that first adventure, check out I built radar charts in JavaScript so you don’t have to guess.
Why a radar chart, anyway?
I had five skills to compare for two people on my team. Later, I mapped flavor notes for chips. Sweet, salty, spicy, sour, umami. A radar chart shows that shape fast. Your eye sees gaps right away. It feels like a spiderweb story.
You know what? When it works, it clicks.
Chart.js: fast start, friendly setup
Chart.js felt like a warm hoodie. It’s simple. I used it first because I could get a chart on the page in minutes. I used version 4.x from npm once and a CDN another time.
What I liked:
- Easy data model
- Good defaults
- Decent docs, even for kids learning
If you’re new to how Chart.js draws that spider-web shape, the official radar chart guide walks through every option you can toggle—from line tension to point styling.
What bugged me:
- Tooltips got jumpy on small phones
- Legend wrap got messy
- Multi-axis tweaks on the radial scale felt tight
Need deeper control over those axis lines? The more advanced knobs live in the radial axis reference, and they saved me a few “why is this grid so dark?” moments.
Here’s a real snippet I used for a team skills map:
<canvas id="skills"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('skills');
new Chart(ctx, {
type: 'radar',
data: {
labels: ['Speed', 'Strength', 'Skill', 'Stamina', 'Teamwork'],
datasets: [
{
label: 'Player A',
data: [60, 80, 70, 50, 90],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59,130,246,0.2)',
pointRadius: 3
},
{
label: 'Player B',
data: [65, 70, 85, 60, 70],
borderColor: '#ef4444',
backgroundColor: 'rgba(239,68,68,0.2)',
pointRadius: 3
}
]
},
options: {
responsive: true,
plugins: {
legend: { position: 'top' },
tooltip: { enabled: true }
},
scales: {
r: {
suggestedMin: 0,
suggestedMax: 100,
angleLines: { color: '#e5e7eb' },
grid: { color: '#e5e7eb' },
pointLabels: { color: '#111827' }
}
}
}
});
</script>
Tip: keep pointRadius small. It looks cleaner and helps on touch screens.
ECharts: flashy and packed with features
When I needed more control—like showing max values per axis—I went with Apache ECharts. It’s rich. The radar has “indicator” items, each with a name and a max. I used this for a snack-tasting night with my kids. Yes, we charted hot chips. It was silly and fun. For a broader comparison of spider-chart libraries, I logged everything that actually worked in this deep dive.
What I liked:
- Per-axis max values
- Pretty split areas and circle shape
- Good resize behavior
What bugged me:
- Big bundle if you import the whole thing
- Options get nested and long
- Styling takes a bit of patience
Here’s my real snack chart:
<div id="snacks" style="height:380px;"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script>
const el = document.getElementById('snacks');
const chart = echarts.init(el);
const options = {
tooltip: {},
radar: {
shape: 'circle',
indicator: [
{ name: 'Sweet', max: 10 },
{ name: 'Salty', max: 10 },
{ name: 'Spicy', max: 10 },
{ name: 'Sour', max: 10 },
{ name: 'Umami', max: 10 }
],
splitArea: {
areaStyle: { color: ['#f9fafb', '#eef2ff'] }
}
},
series: [
{
type: 'radar',
data: [
{ value: [2, 6, 9, 1, 5], name: 'Hot Chips' },
{ value: [5, 3, 1, 2, 7], name: 'Seaweed Crisps' }
],
areaStyle: { opacity: 0.2 },
lineStyle: { width: 2 }
}
]
};
chart.setOption(options);
window.addEventListener('resize', () => chart.resize());
</script>
Little note: if you care about size, import from echarts/core with only the pieces you need.
ApexCharts: quick dashboards, nice polish
ApexCharts felt like a sweet spot for app dashboards. I used it in a panel with other charts. It looked modern without too much fuss.
What I liked:
- Smooth markers and fills
- Simple legend and labels
- Export image was easy
What bugged me:
- Long labels could clip
- Heavy shadows slowed an old Chromebook I keep at home
Here’s a sample I used in a Vue page, but this is plain JS:
<div id="radar"></div>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script>
const options = {
chart: { type: 'radar', height: 360 },
series: [
{ name: 'A', data: [60, 80, 70, 50, 90] },
{ name: 'B', data: [65, 70, 85, 60, 70] }
],
labels: ['Speed', 'Strength', 'Skill', 'Stamina', 'Teamwork'],
stroke: { width: 2 },
fill: { opacity: 0.2 },
markers: { size: 4 }
};
new ApexCharts(document.querySelector('#radar'), options).render();
</script>
If labels clip, I bump the chart height or trim names a bit. Not fancy, but it works.
D3: when you want full control (and time)
One late fall weekend, I wrote a tiny radar with D3. It gave me full control over axes, labels, and keyboard focus. But yes, it took a while.
What I liked:
- Total control
- Custom scales per axis
- Smooth transitions
What bugged me:
- Lots of code for simple things
- You own all the layout and resize
Here’s a tiny base I used. It’s not a full chart, but it shows the core idea:
“`html
const w = 420, h = 420, cx = w/2, cy = h/2;
const axes = [‘Speed’,’Strength’,’Skill’,’Stamina’,’Teamwork’];
const values = [60, 80, 70, 50, 90];
const max = 100;
const r = d3.scaleLinear().domain([0, max]).range([0, 160]);
const angle = d3.scaleLinear().domain([0, axes.length]).range([0, Math.PI * 2]);
const points = axes.map((a, i) => {
const ang = angle(i);
return [cx + r(values[i]) * Math.cos(ang), cy + r(values[i]) * Math.sin(ang)];
});
const svg = d3.select(‘#radarD3’);
// grid circles
[20,40,60,80,100].forEach(t => {
svg.append(‘circle’)
.attr(‘cx’, cx).attr(‘cy’, cy).attr(‘r’, r(t))
.attr(‘fill’, ‘none’).attr(‘stroke’, ‘#e5e7eb’);
});
// polygon
svg.append(‘polygon’)
.attr(‘points’, points.map(p => p.join(‘,’)).join(‘ ‘))
.attr(‘fill’, ‘rgba(59,130,246,0.2)’)
.attr(‘stroke’, ‘#3b82f6’).
