Best JavaScript Charting Libraries in 2026
TL;DR
Chart.js for quick charts; Recharts for React-native data viz; D3 for custom, complex visualizations. Chart.js (~10M weekly downloads) is the most popular charting library with a simple config API. Recharts (~3M) wraps D3 in idiomatic React components. D3.js (~9M) is the power tool — a data transformation library, not just charting — steep learning curve but unlimited flexibility. For React apps, Recharts or Visx hits the right balance.
Key Takeaways
- Chart.js: ~10M weekly downloads — most popular, canvas-based, simple config
- D3.js: ~9M downloads — power tool for custom viz, SVG manipulation
- Recharts: ~3M downloads — React-first, built on D3 scales + SVG
- Visx: ~200K downloads — low-level React primitives from Airbnb, pairs with D3
- ECharts: ~2M downloads — Apache project, best for dashboards, mobile-friendly
Chart.js (Simple, Fast)
// Chart.js — canvas-based, config-driven
import { Chart, registerables } from 'chart.js';
import { useEffect, useRef } from 'react';
Chart.register(...registerables);
function LineChart({ data }: { data: number[] }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
if (!canvasRef.current) return;
const chart = new Chart(canvasRef.current, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Downloads',
data,
borderColor: '#3B82F6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4,
fill: true,
}],
},
options: {
responsive: true,
plugins: {
legend: { display: true },
tooltip: { mode: 'index' },
},
scales: {
y: { beginAtZero: true },
},
},
});
return () => chart.destroy(); // Cleanup on unmount
}, [data]);
return <canvas ref={canvasRef} />;
}
// react-chartjs-2 — official React wrapper
import { Line, Bar, Pie, Doughnut } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';
// Tree-shakeable imports (only register what you need)
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
function DownloadTrends({ chartData }) {
return (
<Line
data={chartData}
options={{
responsive: true,
plugins: { legend: { position: 'top' } },
}}
/>
);
}
Best for: Dashboards with standard chart types, non-React projects, simplicity over customization.
Recharts (React-Native)
// Recharts — declarative React components
import {
LineChart, Line, BarChart, Bar,
XAxis, YAxis, CartesianGrid, Tooltip, Legend,
ResponsiveContainer, Area, AreaChart,
} from 'recharts';
const data = [
{ month: 'Jan', downloads: 4000, installs: 2400 },
{ month: 'Feb', downloads: 3000, installs: 1398 },
{ month: 'Mar', downloads: 6000, installs: 5800 },
{ month: 'Apr', downloads: 8000, installs: 7000 },
];
function DownloadChart() {
return (
<ResponsiveContainer width="100%" height={400}>
<AreaChart data={data} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="colorDownloads" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3B82F6" stopOpacity={0.8} />
<stop offset="95%" stopColor="#3B82F6" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Area
type="monotone"
dataKey="downloads"
stroke="#3B82F6"
fillOpacity={1}
fill="url(#colorDownloads)"
/>
<Area
type="monotone"
dataKey="installs"
stroke="#10B981"
fillOpacity={0.3}
fill="#10B981"
/>
</AreaChart>
</ResponsiveContainer>
);
}
// Recharts — custom tooltip
const CustomTooltip = ({ active, payload, label }) => {
if (!active || !payload?.length) return null;
return (
<div className="bg-white border rounded p-3 shadow">
<p className="font-semibold">{label}</p>
{payload.map((entry) => (
<p key={entry.name} style={{ color: entry.color }}>
{entry.name}: {entry.value.toLocaleString()}
</p>
))}
</div>
);
};
<AreaChart data={data}>
<Tooltip content={<CustomTooltip />} />
{/* ... */}
</AreaChart>
Best for: React apps needing standard charts with React idioms, custom tooltips, and composability.
D3.js (Power Tool)
// D3 — data transformations (not just charts)
import * as d3 from 'd3';
// D3 scales — the core building block
const xScale = d3.scaleTime()
.domain([new Date('2026-01-01'), new Date('2026-12-31')])
.range([0, 800]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value) ?? 0])
.range([400, 0]);
// D3 line generator
const line = d3.line<DataPoint>()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
// D3 in React — using refs
import { useEffect, useRef } from 'react';
import * as d3 from 'd3';
function D3LineChart({ data }) {
const svgRef = useRef<SVGSVGElement>(null);
useEffect(() => {
if (!svgRef.current) return;
const svg = d3.select(svgRef.current);
svg.selectAll('*').remove(); // Clear on redraw
const width = 800, height = 400;
const margin = { top: 20, right: 30, bottom: 30, left: 40 };
const x = d3.scaleUtc()
.domain(d3.extent(data, d => d.date) as [Date, Date])
.range([margin.left, width - margin.right]);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value) ?? 0])
.range([height - margin.bottom, margin.top]);
const lineGen = d3.line<typeof data[0]>()
.x(d => x(d.date))
.y(d => y(d.value));
svg.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', '#3B82F6')
.attr('stroke-width', 2)
.attr('d', lineGen);
// Axes
svg.append('g')
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x));
svg.append('g')
.attr('transform', `translate(${margin.left},0)`)
.call(d3.axisLeft(y));
}, [data]);
return <svg ref={svgRef} width="100%" viewBox="0 0 800 400" />;
}
Best for: Custom, complex visualizations — force graphs, geographic maps, tree diagrams, anything not in a standard chart library.
Visx (Airbnb)
// Visx — low-level React primitives
import { LinePath } from '@visx/shape';
import { scaleLinear, scaleTime } from '@visx/scale';
import { extent, max } from '@visx/vendor/d3-array';
import { AxisBottom, AxisLeft } from '@visx/axis';
import { GridRows } from '@visx/grid';
import { Tooltip, useTooltip } from '@visx/tooltip';
function VxLineChart({ data, width = 800, height = 400 }) {
const margin = { top: 20, right: 20, bottom: 40, left: 50 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const xScale = scaleTime({
domain: extent(data, d => d.date) as [Date, Date],
range: [0, innerWidth],
});
const yScale = scaleLinear({
domain: [0, max(data, d => d.value) ?? 0],
range: [innerHeight, 0],
});
return (
<svg width={width} height={height}>
<g transform={`translate(${margin.left},${margin.top})`}>
<GridRows scale={yScale} width={innerWidth} strokeDasharray="2,3" />
<LinePath
data={data}
x={d => xScale(d.date)}
y={d => yScale(d.value)}
stroke="#3B82F6"
strokeWidth={2}
/>
<AxisBottom top={innerHeight} scale={xScale} />
<AxisLeft scale={yScale} />
</g>
</svg>
);
}
Best for: React teams who want D3's power with React's component model. Steeper learning curve than Recharts but more control.
Comparison Table
| Library | Downloads | Bundle | Approach | Learning Curve | Best Use Case |
|---|---|---|---|---|---|
| Chart.js | 10M | ~200KB | Config-driven | Easy | Quick dashboards |
| D3.js | 9M | ~500KB | Imperative | Hard | Custom viz |
| Recharts | 3M | ~300KB | React components | Medium | React apps |
| ECharts | 2M | ~1MB | Config-driven | Medium | Data-heavy dashboards |
| Visx | 200K | ~50KB* | React primitives | Hard | Custom React charts |
*Visx is modular — pay only for what you use.
When to Choose
| Scenario | Pick |
|---|---|
| Quick dashboard, any framework | Chart.js |
| React app with standard charts | Recharts |
| Custom, complex visualization | D3.js |
| React app needing D3 power | Visx |
| Mobile performance matters | ECharts |
| Heatmaps, geographic viz | D3.js |
| Financial/candlestick charts | lightweight-charts (TradingView) |
| SSR with no canvas API | Recharts or Visx (SVG-based) |
Compare charting library package health on PkgPulse.
See the live comparison
View chartjs vs. d3 on PkgPulse →