chart.js, react-vis, recharts, and victory-chart are popular JavaScript libraries for building data visualizations in web applications. chart.js is a general-purpose canvas-based charting library with a broad feature set and extensive plugin ecosystem. The other three — react-vis, recharts, and victory-chart — are React-specific libraries that embrace declarative composition using JSX. react-vis (developed by Uber) uses SVG and provides low-level primitives for custom visualizations. recharts is also SVG-based and focuses on reusable, composable chart components with strong defaults. victory-chart, part of the Victory suite from Formidable Labs, offers a consistent API across React, React Native, and other renderers, emphasizing themeability and animation. All four support common chart types like line, bar, area, and pie charts, but differ significantly in architecture, customization model, and integration patterns.
Choosing the right charting library in 2024 isn’t just about drawing lines and bars — it’s about how well the tool fits your architecture, team workflow, and long-term maintainability. Let’s compare these four options through real engineering lenses.
Before diving into features, check project health. react-vis is officially archived by Uber as of late 2022. The GitHub repo shows “This repository has been archived by the owner. It is now read-only.” The npm page includes a deprecation notice recommending alternatives. Do not use react-vis in new projects.
The other three — chart.js, recharts, and victory-chart — are actively maintained with recent releases and responsive issue tracking.
chart.js: Imperative, Canvas-Basedchart.js renders to HTML <canvas>, giving it high performance for large datasets but limiting direct DOM access. You configure charts via JavaScript objects, not JSX.
// chart.js: imperative setup
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{
label: 'Sales',
data: [12, 19, 3],
borderColor: 'rgb(75, 192, 192)'
}]
},
options: { responsive: true }
});
In React, you’d typically wrap this in a useEffect hook, which breaks React’s declarative flow.
recharts & victory-chart: Declarative, SVG-BasedBoth use SVG and embrace React’s component model. You compose charts like any other UI.
// recharts: fully declarative
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
const data = [{ name: 'Jan', sales: 12 }, { name: 'Feb', sales: 19 }];
<LineChart width={500} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="sales" stroke="#8884d8" />
</LineChart>
// victory-chart: similar declarative style
import { VictoryLine, VictoryChart, VictoryAxis, VictoryTooltip } from 'victory';
const data = [{ x: 'Jan', y: 12 }, { x: 'Feb', y: 19 }];
<VictoryChart>
<VictoryAxis />
<VictoryAxis dependentAxis />
<VictoryLine data={data} labelComponent={<VictoryTooltip />} />
</VictoryChart>
This approach integrates naturally with React state, props, and hooks — no refs or side effects needed.
chart.js: Deep Configuration, Shallow CompositionCustomization happens through nested config objects. Want a custom tooltip? You write a function that returns HTML strings.
// chart.js tooltip callback
options: {
plugins: {
tooltip: {
callbacks: {
label: (context) => `$${context.parsed.y}`
}
}
}
}
This works but feels disconnected from React’s component model.
recharts: Composable ComponentsEverything is a component. Need a custom tooltip? Pass a React element.
// recharts custom tooltip
const CustomTooltip = ({ active, payload }) => {
if (active && payload && payload.length) {
return <div className="custom-tooltip">${payload[0].value}</div>;
}
return null;
};
<LineChart ...>
<Tooltip content={<CustomTooltip />} />
</LineChart>
Same for axes, legends, and even data points — all swappable via JSX.
victory-chart: Props-Driven with Render PropsVictory uses a mix of props and render functions. Custom tooltips use the labelComponent prop:
// victory custom tooltip
const CustomTooltip = (props) => (
<VictoryTooltip {...props} label={`${props.datum.y}`} />
);
<VictoryLine data={data} labelComponent={<CustomTooltip />} />
It also supports functional components via dataComponent, containerComponent, etc., offering flexibility without full JSX composition.
chart.jsResponsive by default (options.responsive: true), but canvas redraws can cause layout thrashing if not debounced. Large datasets (>10k points) perform well due to canvas rendering.
rechartsFully responsive via percentage-based dimensions or ResponsiveContainer. Since it uses SVG, performance degrades with very large datasets — but it includes optimizations like syncId for linked charts and lazy rendering for tooltips.
// recharts responsive wrapper
import { ResponsiveContainer } from 'recharts';
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>...</LineChart>
</ResponsiveContainer>
victory-chartAlso responsive by default. Uses D3 under the hood for scale calculations but renders SVG. Includes built-in debouncing for resize events. Animation is enabled by default, which can impact performance if not managed.
All libraries support hover, click, and zoom, but the APIs differ.
chart.jsUses event hooks in config:
options: {
onClick: (event, elements) => {
if (elements.length > 0) {
const index = elements[0].index;
console.log(data[index]);
}
}
}
rechartsPass event handlers as props to components:
<Line
dataKey="sales"
onClick={(data, index) => console.log(data)}
/>
victory-chartUses consistent event props across components:
<VictoryLine
data={data}
events={[{
target: "data",
eventHandlers: {
onClick: () => ({ mutation: () => console.log("clicked") })
}
}]}
/>
Victory’s event system is powerful but has a steeper learning curve.
chart.js: Expects { labels: string[], datasets: { data: number[] }[] }recharts: Works with flat arrays of objects: [{ name: 'Jan', value: 12 }]victory-chart: Prefers { x, y } objects or arrays: [{ x: 'Jan', y: 12 }]recharts’ format aligns best with typical REST API responses, reducing transformation overhead.
chart.js: Harder to unit test because it manipulates canvas outside React’s virtual DOM.recharts & victory-chart: Easy to snapshot test with React Testing Library since they render standard SVG elements.// Example test for recharts
render(
<LineChart data={testData}>
<Line dataKey="value" />
</LineChart>
);
expect(screen.getByText('12')).toBeInTheDocument();
| Concern | chart.js | react-vis | recharts | victory-chart |
|---|---|---|---|---|
| Maintenance | ✅ Active | ❌ Archived / Deprecated | ✅ Active | ✅ Active |
| React Integration | ⚠️ Imperative wrapper needed | ✅ Declarative (but dead) | ✅ Fully native JSX | ✅ JSX with props |
| Performance (Large Data) | ✅ Canvas scales well | ✅ SVG (but unmaintained) | ⚠️ SVG struggles >5k points | ⚠️ Similar SVG limits |
| Customization | ⚙️ Config-heavy | 🧩 Low-level SVG control | 🧱 Component composition | 🎭 Props + render functions |
| Responsiveness | ✅ Built-in | ✅ Manual | ✅ ResponsiveContainer | ✅ Default behavior |
| Learning Curve | Medium | High (and obsolete) | Low | Medium |
react-vis entirely — it’s deprecated and poses long-term risk.chart.js only if you need canvas-level performance, work outside React, or depend on its plugin ecosystem (e.g., chartjs-plugin-zoom).recharts for most React applications — it offers the best balance of simplicity, composability, and TypeScript support.victory-chart if you’re already using Victory for other visualizations, need React Native compatibility, or want opinionated theming and animations.In practice, recharts has become the go-to for React teams who want charts that feel like first-class React citizens — and for good reason.
Choose chart.js if you need a mature, highly customizable charting solution that works outside React or when you require advanced features like zoom, pan, or annotation plugins. It’s ideal when you’re okay managing imperative APIs or wrapping it in a React component yourself. Avoid it if you prefer a fully declarative, JSX-native approach or need tight integration with React state without extra abstraction layers.
Choose react-vis if you're building complex, custom visualizations that go beyond standard chart types and need fine-grained control over SVG elements. It’s well-suited for dashboards requiring bespoke interaction logic or novel visual encodings. However, note that as of 2023, Uber has archived the repository and marked it as unmaintained — so avoid it for new projects unless you can commit to long-term maintenance.
Choose recharts if you want a modern, declarative charting library built specifically for React with excellent TypeScript support, responsive design, and smooth animations out of the box. It shines in applications where charts are composed from reusable parts (e.g., shared axes, tooltips, legends) and where developer experience matters. It’s a strong default choice for most React-based data dashboards.
Choose victory-chart if you need cross-platform consistency (e.g., sharing chart logic between web and React Native) or if your design system relies heavily on theming and animated transitions. Its uniform API across chart types simplifies code reuse, and its event system supports rich interactivity. It’s particularly effective when you value convention over configuration and want animations enabled by default.
Simple yet flexible JavaScript charting for designers & developers
All the links point to the new version 4 of the lib.
In case you are looking for an older version of the docs, you will have to specify the specific version in the url like this: https://www.chartjs.org/docs/2.9.4/
Instructions on building and testing Chart.js can be found in the documentation. Before submitting an issue or a pull request, please take a moment to look over the contributing guidelines first. For support, please post questions on Stack Overflow with the chart.js tag.
Chart.js is available under the MIT license.