1.116 Data Visualization Libraries#
Comprehensive analysis of JavaScript data visualization libraries for web development. Covers rendering technologies (SVG, Canvas, WebGL), React integration patterns, and performance characteristics. Analyzes seven major libraries across standard charts, custom visualizations, and large dataset handling.
Explainer
Data Visualization Libraries: Domain Explainer#
What Problem Do They Solve?#
Turning raw data into visual insights is complex:
- Mapping values to positions, sizes, colors
- Creating axes, legends, labels
- Handling user interaction (hover, zoom, pan)
- Animations and transitions
- Responsive sizing
- Accessibility
Visualization libraries provide these capabilities so you can focus on your data.
Key Concepts#
Rendering Technologies#
SVG (Scalable Vector Graphics)
<svg>
<rect x="0" y="0" width="100" height="50" fill="blue" />
<text x="50" y="25">Label</text>
</svg>- DOM elements (selectable, stylable with CSS)
- Good for < 1000 elements
- Interactive (click, hover on individual elements)
- Accessible (screen readers can parse)
Canvas
const ctx = canvas.getContext('2d')
ctx.fillRect(0, 0, 100, 50)
ctx.fillText('Label', 50, 25)- Bitmap rendering (like painting)
- Handles 1000-50000 elements
- No individual element access
- Less accessible
WebGL
- GPU-accelerated 3D/2D
- Handles 50000+ elements
- Complex setup
- Used by ECharts-GL, deck.gl
Performance Rule of Thumb#
| Elements | Best Renderer |
|---|---|
| < 1000 | SVG |
| 1000 - 50000 | Canvas |
| > 50000 | WebGL |
Scales#
Scales map data values to visual values:
// Linear scale: 0-100 → 0-500px
const scale = d3.scaleLinear()
.domain([0, 100]) // Data range
.range([0, 500]) // Pixel range
scale(50) // → 250px
Types of scales:
- Linear: Continuous numeric (temperature, sales)
- Band: Discrete categories (months, products)
- Time: Dates and times
- Log: Exponential data
- Color: Values to colors
Axes#
Visual representation of scales:
- Tick marks at regular intervals
- Labels for values
- Title describing the dimension
- Grid lines for reading values
Data Binding#
The core concept: connecting data to visual elements.
// D3 approach
svg.selectAll('rect')
.data([10, 20, 30])
.join('rect')
.attr('height', d => d * 10)
// React approach
data.map(d => <rect height={d * 10} />)Marks#
Visual primitives that represent data:
- Points: Scatter plots
- Lines: Time series
- Bars: Comparisons
- Areas: Cumulative values
- Arcs: Part-to-whole (pie, donut)
Channels#
Visual properties that encode data:
- Position: x, y coordinates (most accurate)
- Length/Size: Bar height, circle radius
- Color: Hue, saturation, lightness
- Shape: Triangle, square, circle
- Angle: Pie slices (hard to compare)
Chart Types#
| Chart | Best For |
|---|---|
| Line | Trends over time |
| Bar | Comparing categories |
| Pie | Part of whole (< 6 parts) |
| Scatter | Relationship between variables |
| Area | Cumulative trends |
| Heatmap | Density, patterns |
| Treemap | Hierarchical data |
| Sankey | Flow, connections |
| Network | Relationships |
High-Level vs Low-Level Libraries#
High-Level (Recharts, Chart.js, Nivo)#
// Tell library WHAT to draw
<LineChart data={data}>
<Line dataKey="sales" />
</LineChart>Pros:
- Fast development
- Built-in interactivity
- Consistent styling
Cons:
- Limited customization
- Opinionated design
Low-Level (D3, visx)#
// Tell library HOW to draw
const scale = scaleLinear().domain([0, 100]).range([0, 500])
{data.map(d => <circle cx={scale(d.x)} cy={scale(d.y)} r={5} />)}Pros:
- Complete control
- Any visualization possible
Cons:
- More code
- Steeper learning curve
React Integration Patterns#
Pattern 1: Library handles everything#
<LineChart data={data} />Libraries: Recharts, Victory, Nivo
Pattern 2: D3 for math, React for DOM#
const scale = d3.scaleLinear().domain([0, 100]).range([0, 500])
{data.map(d => <rect height={scale(d.value)} />)}Libraries: visx
Pattern 3: D3 takes over DOM (anti-pattern in React)#
useEffect(() => {
d3.select(ref.current)
.selectAll('rect')
.data(data)
.join('rect')
}, [data])Problem: React doesn’t know about DOM changes
Responsive Design#
Charts must adapt to container size:
// Recharts
<ResponsiveContainer width="100%" height={300}>
<LineChart />
</ResponsiveContainer>
// Chart.js
options: { responsive: true }
// DIY
const { width } = useContainerSize(ref)Interactivity#
Common interactive features:
- Tooltip: Information on hover
- Legend: Show/hide series
- Zoom: Focus on region
- Pan: Navigate large datasets
- Brush: Select range
- Click: Drill down
Animation#
Smooth transitions improve comprehension:
// Recharts (built-in)
<Line animationDuration={500} />
// visx + react-spring
const spring = useSpring({ y: targetY })
<animated.rect y={spring.y} />Accessibility#
Charts should be accessible:
- Alt text for images (Canvas)
- ARIA labels for SVG elements
- Keyboard navigation
- Color contrast
- Screen reader descriptions
SVG is inherently more accessible than Canvas.
Common Patterns#
Composable Charts#
Combine multiple chart types:
<ComposedChart>
<Bar dataKey="sales" />
<Line dataKey="trend" />
<Area dataKey="forecast" />
</ComposedChart>Faceted Charts (Small Multiples)#
Same chart repeated for categories:
{categories.map(cat => (
<LineChart key={cat} data={dataByCategory[cat]} />
))}Connected Charts#
Linked brushing across charts:
// Selecting in one chart highlights in another
const [selection, setSelection] = useState(null)Common Misconceptions#
“More chart types = better library”#
Most dashboards use 3-4 chart types. Focus on those, not variety.
“D3 is the best because it’s most powerful”#
Power comes with complexity. For standard charts, high-level libraries are better.
“Canvas is always faster than SVG”#
Under 1000 elements, the difference is negligible. SVG is easier to work with.
“3D charts are impressive”#
3D usually makes data harder to read. Use sparingly and purposefully.
“Pie charts are bad”#
Pie charts are fine for 2-5 slices showing part-to-whole. They’re overused, not inherently bad.
Evolution of the Space#
2011: D3.js Released#
Mike Bostock creates D3, establishing the foundation.
2015-2018: Wrapper Libraries#
Recharts, Victory, Nivo wrap D3 for React.
2020: visx (Airbnb)#
Bridge between D3 control and React patterns.
2025 Trends#
- Recharts dominates React (9M downloads)
- Canvas/WebGL for performance
- AI-assisted chart generation emerging
- Accessibility focus increasing
Last Updated: 2025-12-12 Related Research: 1.111 (State Management), 1.113 (UI Components), 1.115 (Forms)
S1: Rapid Discovery
1.116 Data Visualization Libraries - S1 Rapid Discovery#
Quick Decision Guide#
| Situation | Recommendation |
|---|---|
| React dashboard (standard charts) | Recharts |
| Custom/complex visualizations | visx |
| Large datasets (1000+ points) | ECharts or Chart.js |
| React Native cross-platform | Victory |
| Full creative control | D3.js |
| Enterprise with support needs | Highcharts (commercial) |
2025 Landscape#
Weekly Downloads (npm)#
Recharts: ███████████████████████████████████ 9M
D3.js: █████████████████████████ 4.5M
Chart.js: ████████████████████ 3M
ECharts: ███████████ 1M
Nivo: ███ 500K
visx: ██ 350K
Victory: ██ 284KGitHub Stars#
D3.js: ████████████████████████████████████████████████ 108K
Chart.js: ██████████████████████████████████████ 64K
ECharts: ████████████████████████████████████ 61K
Recharts: █████████████████████████ 25.6K
visx: ███████████████████ 19K
Nivo: ██████████████ 13.6K
Victory: ███████████ 11.2KThe Default Choice: Recharts#
For most React dashboards, Recharts is the answer:
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
const data = [
{ name: 'Jan', value: 400 },
{ name: 'Feb', value: 300 },
{ name: 'Mar', value: 600 },
]
function Chart() {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
)
}Why Recharts wins:
- 9M weekly downloads (32x more than next React-specific library)
- Declarative JSX API
- Built on D3 (solid math)
- Great TypeScript support
- Active maintenance
When NOT to Use Recharts#
| Scenario | Better Choice |
|---|---|
| 1000+ data points | ECharts (WebGL) |
| Network graphs, force layouts | D3.js or visx |
| Highly custom animations | D3.js |
| Server-side rendering | Nivo |
| React + React Native | Victory |
| Need commercial support | Highcharts |
Library Tiers#
Tier 1: High-Level React (Easiest)#
| Library | Best For | Trade-off |
|---|---|---|
| Recharts | Dashboards | Limited customization |
| Nivo | SSR, variety | Smaller community |
| Victory | Cross-platform | Less popular |
Tier 2: Low-Level React (More Control)#
| Library | Best For | Trade-off |
|---|---|---|
| visx | Custom D3+React | Steeper learning curve |
Tier 3: Framework Agnostic (Maximum Power)#
| Library | Best For | Trade-off |
|---|---|---|
| D3.js | Anything custom | Very steep learning curve |
| ECharts | Large datasets | Complex API |
| Chart.js | Quick setup | Less flexible |
Rendering: SVG vs Canvas vs WebGL#
| Renderer | Best For | Limit |
|---|---|---|
| SVG | <1000 points, interactivity | Slows down with many elements |
| Canvas | 1K-50K points | Fast but no DOM manipulation |
| WebGL | 50K+ points | Fastest, complex setup |
Rule of thumb:
- Under 1,000 points → SVG (Recharts, Victory)
- 1,000-50,000 points → Canvas (Chart.js, ECharts)
- 50,000+ points → WebGL (ECharts with GL)
Sources#
- Best React Chart Libraries 2025 - LogRocket
- npm trends: nivo vs recharts vs victory
- visx - Airbnb
- Apache ECharts
Chart.js#
“Simple yet flexible JavaScript charting library.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 64,000 |
| npm Weekly Downloads | ~3M |
| Rendering | Canvas |
| License | MIT |
| React Wrapper | react-chartjs-2 |
Why Chart.js?#
Chart.js is the simplest way to add charts to any project:
- Tiny: ~60KB gzipped (with tree-shaking)
- Fast: Canvas rendering
- Simple API: Works in minutes
- Framework agnostic: Works anywhere
- Huge ecosystem: 100s of plugins
Core Concept: Canvas Performance#
Chart.js renders to Canvas (not SVG):
- Faster for 1000+ points
- Lower memory usage
- Smaller file size
- No DOM bloat
Trade-off: Can’t select/style individual elements with CSS.
React Integration#
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js'
import { Line } from 'react-chartjs-2'
// Register components
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
)
function LineChart() {
const data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
datasets: [
{
label: 'Sales',
data: [65, 59, 80, 81],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
},
],
}
const options = {
responsive: true,
plugins: {
legend: { position: 'top' },
title: { display: true, text: 'Monthly Sales' },
},
}
return <Line data={data} options={options} />
}Chart Types#
Built-in (8 types):
- Line, Bar, Pie, Doughnut
- Radar, Polar Area
- Bubble, Scatter
Key Features#
Responsive by Default#
options: {
responsive: true,
maintainAspectRatio: true,
}Animations#
options: {
animation: {
duration: 1000,
easing: 'easeOutQuart',
},
}Plugins#
import zoomPlugin from 'chartjs-plugin-zoom'
ChartJS.register(zoomPlugin)
options: {
plugins: {
zoom: {
zoom: { wheel: { enabled: true } },
pan: { enabled: true },
},
},
}Tree-Shaking (v3+)#
Only import what you need:
// Full bundle (~60KB)
import Chart from 'chart.js/auto'
// Tree-shaken (~20KB for simple line chart)
import { Chart, LineElement, PointElement, LineController, CategoryScale, LinearScale } from 'chart.js'
Chart.register(LineElement, PointElement, LineController, CategoryScale, LinearScale)Limitations#
- Limited chart types: 8 built-in (vs 20+ in ECharts)
- Less customizable: No arbitrary shapes
- Canvas limitations: Can’t CSS style elements
- No geographic maps: Need plugins
Chart.js vs Alternatives#
| Aspect | Chart.js | Recharts | ECharts |
|---|---|---|---|
| Setup time | Minutes | Minutes | Hours |
| Bundle | ~20-60KB | ~100KB | ~100KB+ |
| Chart types | 8 | 12 | 20+ |
| Rendering | Canvas | SVG | Canvas/WebGL |
| Customization | Moderate | Limited | High |
| React-first | No | Yes | No |
When to Use Chart.js#
Choose Chart.js when:
- Need quick, simple charts
- Bundle size matters
- Using vanilla JS or any framework
- Standard chart types sufficient
- Want lots of plugin options
Choose alternatives when:
- Need React-first → Recharts
- Need 10K+ points → ECharts
- Need custom visualizations → visx/D3
Resources#
Data Visualization Libraries - Comparison Matrix#
Quantitative Comparison#
| Library | Stars | Weekly DL | Rendering | React-First |
|---|---|---|---|---|
| D3.js | 108K | 4.5M | SVG/Canvas | No |
| Chart.js | 64K | 3M | Canvas | No |
| ECharts | 61K | 1M | Canvas/WebGL | No |
| Recharts | 25.6K | 9M | SVG | Yes |
| visx | 19K | 350K | SVG | Yes |
| Nivo | 13.6K | 500K | SVG/Canvas | Yes |
| Victory | 11.2K | 284K | SVG | Yes |
Feature Matrix#
| Feature | Recharts | Chart.js | ECharts | visx | Nivo | Victory | D3 |
|---|---|---|---|---|---|---|---|
| Standard charts | ★★★★★ | ★★★★★ | ★★★★★ | ★★★ | ★★★★★ | ★★★★ | ★★★ |
| Custom viz | ★★ | ★★ | ★★★ | ★★★★★ | ★★★ | ★★★ | ★★★★★ |
| Large datasets | ★★ | ★★★★ | ★★★★★ | ★★ | ★★★ | ★★ | ★★★★ |
| Learning curve | ★★★★★ | ★★★★★ | ★★★ | ★★★ | ★★★★ | ★★★★ | ★ |
| TypeScript | ★★★★★ | ★★★★ | ★★★ | ★★★★★ | ★★★★ | ★★★★ | ★★★ |
| React Native | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| SSR | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
| 3D charts | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Geographic | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
Rendering Technology#
| Renderer | Libraries | Data Point Limit | Interactivity |
|---|---|---|---|
| SVG | Recharts, visx, Victory, Nivo | ~1000 | Full DOM access |
| Canvas | Chart.js, ECharts | ~50,000 | Event-based |
| WebGL | ECharts-GL, D3 | 100K+ | Limited |
Performance Comparison#
Data points vs render time (approximate):
100 points: SVG ██ Canvas ██ WebGL ██ (all fast)
1,000 points: SVG ████ Canvas ██ WebGL ██
10,000 points: SVG █████████████ Canvas ████ WebGL ██
100,000 points: SVG (fails) Canvas ████████ WebGL ████Decision Matrix by Use Case#
Standard Dashboard Charts#
| Priority | Best Choice |
|---|---|
| React ecosystem | Recharts |
| Simplicity | Chart.js |
| SSR needed | Nivo |
Large Datasets (1000+ points)#
| Priority | Best Choice |
|---|---|
| Performance | ECharts |
| Simplicity | Chart.js |
| Control | D3 + Canvas |
Custom Visualizations#
| Priority | Best Choice |
|---|---|
| React project | visx |
| Any project | D3 |
| Network graphs | D3/visx |
Cross-Platform (React + React Native)#
| Priority | Best Choice |
|---|---|
| Same API | Victory |
| Only option | Victory |
Enterprise Requirements#
| Priority | Best Choice |
|---|---|
| Commercial support | Highcharts |
| Feature-rich | ECharts |
| Community | Recharts |
Bundle Size Comparison#
Approximate bundle sizes (gzipped):
D3 (full): ████████████████████████████ ~80KB
ECharts (full): ██████████████████████████ ~70KB
Recharts: ██████████████████████ ~60KB
Chart.js (full): ████████████████ ~45KB
Nivo (line): ██████████████ ~40KB
visx (bar): ████████ ~25KB
Chart.js (tree): ██████ ~20KB
Victory (line): ████████ ~25KBMigration Paths#
From Chart.js to React#
- Use
react-chartjs-2wrapper - Or switch to Recharts for React-first DX
From Recharts to More Power#
- Add visx for custom components
- Or ECharts for performance
From D3 to React#
- Use visx (D3 concepts, React components)
- Or Recharts for simplicity
Quick Reference#
“I need charts quickly”#
→ Recharts (React) or Chart.js (any)
“I have lots of data”#
→ ECharts (Canvas/WebGL)
“I need full control”#
→ D3 (direct) or visx (React)
“I’m building React + React Native”#
→ Victory
“I need server-rendered charts”#
→ Nivo
“I need commercial support”#
→ Highcharts (paid license)
D3.js#
“Data-Driven Documents: Bring data to life using SVG, Canvas, and HTML.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 108,000 |
| npm Weekly Downloads | ~4.5M |
| Rendering | SVG/Canvas/HTML |
| License | ISC |
| Creator | Mike Bostock (Observable) |
What Is D3?#
D3.js is the foundation of most visualization libraries. It’s not a charting library - it’s a toolkit for building any visualization you can imagine.
Libraries built on D3:
- Recharts, Nivo, Victory
- visx (Airbnb)
- Observable Plot
- nvd3, c3
Why D3 Is Unique#
D3 gives you complete control:
- Binds data to DOM elements
- Calculates scales, axes, layouts
- Handles transitions and animations
- Supports any output (SVG, Canvas, WebGL)
Basic Concept: Selections & Data Binding#
import * as d3 from 'd3'
// Select elements
const svg = d3.select('#chart')
// Bind data to elements
const data = [10, 20, 30, 40, 50]
svg.selectAll('rect')
.data(data)
.join('rect')
.attr('width', d => d * 10)
.attr('height', 30)
.attr('y', (d, i) => i * 35)
.attr('fill', 'steelblue')D3’s Power: Scales & Axes#
// Create scale
const xScale = d3.scaleLinear()
.domain([0, 100]) // Data range
.range([0, 500]) // Pixel range
// Create axis
const xAxis = d3.axisBottom(xScale)
// Apply axis
svg.append('g')
.attr('transform', 'translate(0, 200)')
.call(xAxis)Common D3 Modules#
| Module | Purpose |
|---|---|
| d3-scale | Map data to visual values |
| d3-axis | Create axes |
| d3-shape | Lines, areas, pies, arcs |
| d3-hierarchy | Trees, treemaps, pack |
| d3-force | Force-directed layouts |
| d3-geo | Geographic projections |
| d3-transition | Animations |
| d3-selection | DOM manipulation |
D3 + React: The Challenge#
D3 and React both want to control the DOM:
// WRONG: D3 manipulates DOM, React doesn't know
useEffect(() => {
d3.select(ref.current)
.selectAll('rect')
.data(data)
.join('rect')
// React can't track these changes
}, [data])Solutions:#
1. D3 for math, React for DOM (Recommended)
// D3 calculates, React renders
const scale = d3.scaleLinear().domain([0, 100]).range([0, 500])
return (
<svg>
{data.map((d, i) => (
<rect key={i} width={scale(d)} height={30} y={i * 35} />
))}
</svg>
)2. Use visx (Best of both worlds)
import { scaleLinear } from '@visx/scale'
import { AxisBottom } from '@visx/axis'
// visx wraps D3 in React componentsWhen to Use D3 Directly#
Use D3 when:
- Need custom visualization (no library covers it)
- Building your own chart library
- Need maximum performance control
- Creating interactive data art
- Force-directed graphs, network diagrams
Don’t use D3 when:
- Need standard charts (bar, line, pie) → Recharts
- Want fast development → Any high-level library
- Team isn’t D3-experienced → visx
Learning Curve#
D3 has a steep learning curve:
| Level | Time | What You Can Build |
|---|---|---|
| Beginner | 1-2 weeks | Basic bar chart |
| Intermediate | 1-2 months | Custom interactive charts |
| Advanced | 6+ months | Complex animations, custom layouts |
Resources#
Apache ECharts#
“An open-source JavaScript visualization library.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 61,000 |
| npm Weekly Downloads | ~1M |
| Rendering | Canvas/SVG/WebGL |
| License | Apache 2.0 |
| Maintainer | Apache Foundation |
Why ECharts?#
ECharts is the performance champion for large datasets:
- Canvas/WebGL: Handles 10K-100K+ data points
- Feature-rich: 20+ chart types, maps, 3D
- Enterprise-ready: Used by major companies
- Mobile-optimized: Small, modular bundle
When to Choose ECharts#
| Scenario | Why ECharts |
|---|---|
| 1000+ data points | Canvas beats SVG |
| 50000+ data points | WebGL support |
| Real-time dashboards | Efficient updates |
| Geographic maps | Built-in support |
| 3D visualizations | ECharts-GL |
React Integration#
// Using echarts-for-react wrapper
import ReactECharts from 'echarts-for-react'
function Chart() {
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [120, 200, 150, 80, 70],
type: 'bar',
},
],
}
return <ReactECharts option={option} />
}Note: The echarts-for-react wrapper is somewhat outdated. For production, consider a custom wrapper:
import { useEffect, useRef } from 'react'
import * as echarts from 'echarts'
function EChart({ option }) {
const ref = useRef(null)
const chartRef = useRef(null)
useEffect(() => {
chartRef.current = echarts.init(ref.current)
return () => chartRef.current?.dispose()
}, [])
useEffect(() => {
chartRef.current?.setOption(option)
}, [option])
return <div ref={ref} style={{ width: '100%', height: 400 }} />
}Configuration-Based API#
ECharts uses a declarative options object:
const option = {
title: { text: 'Sales Report' },
tooltip: { trigger: 'axis' },
legend: { data: ['Sales', 'Returns'] },
xAxis: { type: 'category', data: months },
yAxis: { type: 'value' },
series: [
{ name: 'Sales', type: 'line', data: salesData },
{ name: 'Returns', type: 'bar', data: returnsData },
],
}Chart Types#
ECharts supports 20+ chart types:
Standard:
- Line, Bar, Pie, Scatter
- Radar, Heatmap, Treemap
- Candlestick, Boxplot
Advanced:
- Sankey, Sunburst, Graph (network)
- Map (geographic)
- Parallel coordinates
- Funnel, Gauge
3D (with ECharts-GL):
- 3D Bar, Line, Scatter
- Surface, Globe
- Flow
Performance: Large Datasets#
// Enable large mode for 10K+ points
series: [{
type: 'scatter',
large: true,
largeThreshold: 2000,
data: largeDataset // 100K points OK
}]
// For 100K+ points, use WebGL
series: [{
type: 'scatterGL', // Requires echarts-gl
data: massiveDataset
}]Limitations#
- Complex API: Many options to learn
- React wrapper outdated: May need custom integration
- Bundle size: Full library is large (use modular imports)
- Styling: Less flexible than D3/visx
ECharts vs Alternatives#
| Aspect | ECharts | Recharts | D3 |
|---|---|---|---|
| Performance | Excellent | Good | Depends |
| Chart variety | 20+ | 10 | Unlimited |
| Learning curve | Moderate | Easy | Steep |
| Customization | Good | Limited | Maximum |
| React integration | OK | Excellent | Manual |
Resources#
Nivo#
“Supercharged React dataviz components, built on top of D3.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 13,600 |
| npm Weekly Downloads | ~500K |
| Rendering | SVG/Canvas/HTML |
| License | MIT |
What Sets Nivo Apart?#
Nivo offers unique features not found in other React chart libraries:
- Server-Side Rendering: API for rendering charts on server
- Multiple renderers: SVG, Canvas, and HTML
- Theming: Deep theme customization
- Motion: Built-in smooth animations
- Patterns & gradients: Decorative fills
Server-Side Rendering (Unique Feature)#
Nivo can render charts on the server via HTTP API:
# Generate chart image via API
curl "https://nivo.rocks/r/line/svg?width=800&height=400&data=[...]"Use cases:
- Email reports with embedded charts
- Static site generation
- PDF generation
- Social media previews
Chart Types#
Nivo has excellent variety:
Standard:
- Line, Bar, Pie, Area
- Scatter, Heatmap
Advanced:
- Chord diagram
- Sankey
- Network (force-directed)
- Treemap, Sunburst
- Waffle
- Bump chart
- Calendar heatmap
- Radar, Funnel
Basic Usage#
import { ResponsiveLine } from '@nivo/line'
const data = [
{
id: 'sales',
data: [
{ x: 'Jan', y: 100 },
{ x: 'Feb', y: 200 },
{ x: 'Mar', y: 150 },
],
},
]
function Chart() {
return (
<div style={{ height: 400 }}>
<ResponsiveLine
data={data}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ type: 'point' }}
yScale={{ type: 'linear', min: 'auto', max: 'auto' }}
axisBottom={{ legend: 'Month' }}
axisLeft={{ legend: 'Sales' }}
pointSize={10}
useMesh={true}
legends={[
{
anchor: 'bottom-right',
direction: 'column',
translateX: 100,
itemWidth: 80,
itemHeight: 20,
},
]}
/>
</div>
)
}Theming#
Nivo supports deep theming:
const theme = {
background: '#1a1a2e',
textColor: '#eee',
fontSize: 12,
axis: {
domain: { line: { stroke: '#777' } },
ticks: { text: { fill: '#eee' } },
},
grid: { line: { stroke: '#333' } },
legends: { text: { fill: '#eee' } },
}
<ResponsiveLine theme={theme} {...props} />Patterns & Gradients#
Unique decorative features:
<ResponsiveBar
defs={[
{
id: 'dots',
type: 'patternDots',
background: 'inherit',
color: '#38bcb2',
size: 4,
},
]}
fill={[{ match: { id: 'sales' }, id: 'dots' }]}
/>Renderer Options#
Choose based on your needs:
| Renderer | Best For |
|---|---|
| SVG | Interactivity, small datasets |
| Canvas | Large datasets, performance |
| HTML | Accessibility, SEO |
import { ResponsiveBar } from '@nivo/bar' // SVG
import { ResponsiveBarCanvas } from '@nivo/bar' // Canvas
Limitations#
- Smaller community: Less tutorials, fewer Stack Overflow answers
- Bundle size: Larger than Recharts
- Learning curve: Many options to configure
- React only: No vanilla JS option
Nivo vs Recharts#
| Aspect | Nivo | Recharts |
|---|---|---|
| Downloads | 500K/week | 9M/week |
| SSR | Built-in API | Manual |
| Chart variety | 20+ | 12 |
| Theming | Deep | Basic |
| Learning curve | Moderate | Easy |
| Community | Smaller | Larger |
When to Use Nivo#
Choose Nivo when:
- Need server-side chart rendering
- Want advanced chart types (chord, sankey, bump)
- Need deep theming
- Want patterns/gradients
- Building data-heavy applications
Choose Recharts when:
- Want larger community support
- Need simple, quick setup
- Standard charts sufficient
Resources#
Recharts#
“A composable charting library built on React components.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 25,600 |
| npm Weekly Downloads | ~9M |
| Rendering | SVG |
| License | MIT |
| React Requirement | Yes |
Why Recharts Dominates#
Recharts is the default choice for React charts:
- Most popular: 9M weekly downloads (32x more than Nivo)
- Declarative: JSX-based API feels like React
- D3 foundation: Solid math under the hood
- TypeScript: Excellent type support
- Maintained: Active development
Core Concept: Composable Components#
Each chart element is a React component:
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts'
const data = [
{ month: 'Jan', sales: 400, returns: 24 },
{ month: 'Feb', sales: 300, returns: 13 },
{ month: 'Mar', sales: 600, returns: 42 },
]
function SalesChart() {
return (
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="sales" stroke="#8884d8" />
<Line type="monotone" dataKey="returns" stroke="#82ca9d" />
</LineChart>
</ResponsiveContainer>
)
}Chart Types#
Recharts includes all common chart types:
- LineChart, AreaChart
- BarChart (horizontal/vertical)
- PieChart, RadarChart
- ScatterChart
- ComposedChart (mix types)
- Treemap, Sankey
Key Components#
ResponsiveContainer#
Always wrap charts for responsiveness:
<ResponsiveContainer width="100%" height={300}>
<LineChart>...</LineChart>
</ResponsiveContainer>Tooltip#
Built-in hover information:
<Tooltip
formatter={(value, name) => [`$${value}`, name]}
labelFormatter={(label) => `Month: ${label}`}
/>Legend#
Chart legend with click-to-hide:
<Legend onClick={(data) => console.log(data)} />Reference Lines/Areas#
Annotate charts:
<ReferenceLine y={500} stroke="red" label="Target" />
<ReferenceArea y1={200} y2={300} fill="yellow" fillOpacity={0.3} />Customization#
Custom Shapes#
const CustomDot = ({ cx, cy, value }) => (
<circle cx={cx} cy={cy} r={value > 500 ? 8 : 4} fill="#8884d8" />
)
<Line dot={<CustomDot />} />Custom Tooltips#
const CustomTooltip = ({ active, payload, label }) => {
if (!active || !payload) return null
return (
<div className="tooltip">
<p>{label}: ${payload[0].value}</p>
</div>
)
}
<Tooltip content={<CustomTooltip />} />Limitations#
- SVG only: Slows with 1000+ data points
- Limited chart types: No network graphs, force layouts
- Animation limits: Less control than D3
- No 3D: Flat charts only
When to Use Recharts#
Choose Recharts when:
- Building React dashboards
- Need standard chart types
- Data points < 1000
- Want fast development
Choose alternatives when:
- Need 1000+ data points → ECharts
- Need custom visualizations → visx/D3
- Need cross-platform → Victory
- Need 3D → Plotly
Resources#
Data Visualization Library Recommendation Guide#
Quick Decision Tree#
Start Here
│
├─ What framework?
│ ├─ React → Continue below
│ ├─ React Native → Victory
│ ├─ Vue/Angular/Vanilla → Chart.js or ECharts
│ └─ Any (maximum control) → D3.js
│
├─ React: How many data points?
│ ├─ < 500 points → Recharts ✓
│ ├─ 500 - 5000 → Chart.js or Recharts
│ └─ > 5000 → ECharts (Canvas/WebGL)
│
├─ React: Standard or custom charts?
│ ├─ Standard (bar, line, pie) → Recharts ✓
│ ├─ Custom (network, force) → visx
│ └─ Many chart types → Nivo
│
└─ Special requirements?
├─ Server-side rendering → Nivo
├─ Geographic maps → ECharts or visx
├─ 3D charts → ECharts-GL
└─ Commercial support → HighchartsThe Default: Recharts#
For most React dashboards, use Recharts:
npm install rechartsWhy:
- 9M weekly downloads (dominant)
- Declarative JSX API
- Great TypeScript support
- Built on D3 (solid math)
- Active maintenance
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line dataKey="value" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>Recommendations by Scenario#
1. Standard React Dashboard#
Recommended: Recharts
- Line charts, bar charts, pie charts
- Interactive tooltips, legends
- Responsive design
npm install recharts2. Large Datasets (1000+ data points)#
Recommended: ECharts
SVG-based libraries (Recharts, Victory) struggle with 1000+ points. ECharts uses Canvas/WebGL:
npm install echarts echarts-for-reactOr for 50K+ points, use WebGL:
series: [{ type: 'scatterGL', data: massiveDataset }]3. Custom Visualizations#
Recommended: visx
Need network graphs, custom force layouts, or unusual charts? visx gives D3 power with React idioms:
npm install @visx/scale @visx/shape @visx/axisPick only packages you need (excellent tree-shaking).
4. Cross-Platform (React + React Native)#
Recommended: Victory
Same API works on web and mobile:
// Web
npm install victory
// Mobile
npm install victory-native react-native-svgSame code, both platforms.
5. Server-Side Rendered Charts#
Recommended: Nivo
Unique SSR API for generating charts on server:
npm install @nivo/line @nivo/barUse case: Email reports, PDFs, static sites.
6. Simple Charts, Any Framework#
Recommended: Chart.js
Fastest setup, works everywhere:
npm install chart.jsFor React: npm install react-chartjs-2
7. Enterprise with Support Requirements#
Recommended: Highcharts (Commercial)
- Professional support
- Accessibility certified
- Long-term maintenance
Requires commercial license for business use.
What NOT to Do#
Don’t Use D3 for Standard Charts#
D3 is a low-level toolkit. For bar/line/pie charts, use Recharts - it uses D3 under the hood anyway.
Don’t Use SVG Libraries for Large Data#
SVG struggles past ~1000 data points. Switch to Canvas (Chart.js, ECharts).
Don’t Build Your Own Charting Library#
Unless you have specific needs, existing libraries handle edge cases you haven’t thought of.
Library Tiers#
Tier 1: Start Here#
| Library | Use For |
|---|---|
| Recharts | React dashboards |
| Chart.js | Quick charts, any framework |
Tier 2: Specific Needs#
| Library | Use For |
|---|---|
| ECharts | Large datasets, 3D, maps |
| visx | Custom visualizations |
| Victory | React + React Native |
| Nivo | SSR, variety |
Tier 3: Special Cases#
| Library | Use For |
|---|---|
| D3.js | Maximum control, custom everything |
| Highcharts | Enterprise, commercial support |
| Plotly | Scientific, 3D |
Performance Summary#
| Data Size | Recommended | Why |
|---|---|---|
| < 500 | Recharts | Simplicity |
| 500-5000 | Chart.js | Canvas faster |
| 5000-50000 | ECharts | Optimized Canvas |
| 50000+ | ECharts-GL | WebGL required |
Final Recommendations#
For Most React Projects#
Recharts - Dominant, simple, effective
For Large Datasets#
ECharts - Canvas/WebGL performance
For Custom Visualizations#
visx - D3 power, React idioms
For Cross-Platform#
Victory - Only real option
For Enterprise#
Highcharts - Commercial support (or ECharts)
Victory#
“React.js components for modular charting and data visualization.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 11,200 |
| npm Weekly Downloads | ~284K |
| Rendering | SVG |
| License | MIT |
| Maintainer | Nearform (Formidable) |
Why Victory?#
Victory’s unique value: Cross-platform (React + React Native)
Same API works on web and mobile:
// Web
import { VictoryLine } from 'victory'
// React Native
import { VictoryLine } from 'victory-native'When to Choose Victory#
| Scenario | Why Victory |
|---|---|
| React + React Native | Same charting code |
| Composable charts | Modular component system |
| Themeable | Built-in theme support |
Basic Usage#
import {
VictoryChart,
VictoryLine,
VictoryAxis,
VictoryTheme,
} from 'victory'
const data = [
{ x: 1, y: 2 },
{ x: 2, y: 3 },
{ x: 3, y: 5 },
{ x: 4, y: 4 },
]
function Chart() {
return (
<VictoryChart theme={VictoryTheme.material}>
<VictoryLine data={data} />
<VictoryAxis />
</VictoryChart>
)
}Chart Types#
Standard charts:
- Line, Bar, Area, Scatter
- Pie, Polar
- Candlestick, ErrorBar
- Histogram, BoxPlot
Composable Architecture#
Victory’s strength is composition:
<VictoryChart>
{/* Combine any elements */}
<VictoryBar data={barData} />
<VictoryLine data={lineData} />
<VictoryScatter data={scatterData} />
{/* Add annotations */}
<VictoryAxis label="X Axis" />
<VictoryAxis dependentAxis label="Y Axis" />
{/* Add interactivity */}
<VictoryTooltip />
<VictoryZoomContainer />
</VictoryChart>Theming#
Built-in themes + custom:
import { VictoryTheme } from 'victory'
// Built-in
<VictoryChart theme={VictoryTheme.material} />
<VictoryChart theme={VictoryTheme.grayscale} />
// Custom
const customTheme = {
axis: {
style: {
axis: { stroke: '#ccc' },
tickLabels: { fill: '#666' },
},
},
line: {
style: {
data: { stroke: '#c43a31' },
},
},
}
<VictoryChart theme={customTheme} />Animations#
Built-in smooth transitions:
<VictoryBar
animate={{
duration: 500,
onLoad: { duration: 500 },
}}
data={data}
/>React Native Support#
// Install
npm install victory-native react-native-svg
// Use
import { VictoryPie } from 'victory-native'
function MobileChart() {
return (
<VictoryPie
data={[
{ x: 'Cats', y: 35 },
{ x: 'Dogs', y: 40 },
{ x: 'Birds', y: 25 },
]}
/>
)
}Limitations#
- SVG only: Can slow with large datasets
- Smaller ecosystem: Fewer plugins than Chart.js
- Less popular: 284K vs 9M (Recharts)
- Learning curve: API different from other libraries
Victory vs Alternatives#
| Aspect | Victory | Recharts | Chart.js |
|---|---|---|---|
| React Native | ✅ | ❌ | ❌ |
| Composability | Excellent | Good | Limited |
| Community | Smaller | Large | Huge |
| Performance | SVG | SVG | Canvas |
When to Use Victory#
Choose Victory when:
- Building React + React Native app
- Want same charting code everywhere
- Need composable chart system
Choose alternatives when:
- Web only → Recharts (more popular)
- Large datasets → ECharts/Chart.js
- Custom visualizations → visx/D3
Resources#
visx#
“A collection of expressive, low-level visualization primitives for React.”
Quick Facts#
| Metric | Value |
|---|---|
| GitHub Stars | 19,000 |
| npm Weekly Downloads | ~350K |
| Rendering | SVG |
| License | MIT |
| Maintainer | Airbnb |
| Production Use | 3+ years at Airbnb |
What Is visx?#
visx (formerly vx) is Airbnb’s answer to “D3 + React”: it wraps D3’s calculation power in React components, giving you the best of both worlds.
Key insight: D3 for math, React for DOM.
Why visx?#
| Problem | visx Solution |
|---|---|
| D3 + React DOM conflict | D3 calculates, React renders |
| D3 learning curve | React component API |
| High-level libraries too limiting | Low-level primitives |
| Bundle size concerns | Pick only packages you need |
The visx Philosophy#
- Un-opinionated: Bring your own styles, animations, state
- Modular: 30+ packages, use what you need
- Low-level: Primitives, not pre-made charts
- D3 hidden: Use D3 power without learning D3
Package Structure#
@visx/axis - Axes (bottom, left, top, right)
@visx/grid - Grid lines
@visx/group - SVG group element
@visx/scale - D3 scales wrapped
@visx/shape - Lines, bars, areas, arcs
@visx/tooltip - Tooltip primitives
@visx/zoom - Zoom behavior
@visx/brush - Selection brush
@visx/geo - Geographic maps
@visx/hierarchy - Trees, treemaps
@visx/network - Force-directed graphs
@visx/heatmap - Heatmaps
...and moreBasic Example#
import { scaleLinear, scaleBand } from '@visx/scale'
import { Bar } from '@visx/shape'
import { Group } from '@visx/group'
import { AxisBottom, AxisLeft } from '@visx/axis'
const data = [
{ letter: 'A', frequency: 0.08 },
{ letter: 'B', frequency: 0.15 },
{ letter: 'C', frequency: 0.12 },
]
const width = 500
const height = 300
const margin = { top: 20, right: 20, bottom: 40, left: 40 }
function BarChart() {
const xMax = width - margin.left - margin.right
const yMax = height - margin.top - margin.bottom
const xScale = scaleBand({
domain: data.map(d => d.letter),
range: [0, xMax],
padding: 0.4,
})
const yScale = scaleLinear({
domain: [0, Math.max(...data.map(d => d.frequency))],
range: [yMax, 0],
})
return (
<svg width={width} height={height}>
<Group left={margin.left} top={margin.top}>
{data.map(d => (
<Bar
key={d.letter}
x={xScale(d.letter)}
y={yScale(d.frequency)}
width={xScale.bandwidth()}
height={yMax - yScale(d.frequency)}
fill="#6c5ce7"
/>
))}
<AxisBottom scale={xScale} top={yMax} />
<AxisLeft scale={yScale} />
</Group>
</svg>
)
}When to Choose visx#
Choose visx when:
- Need custom visualizations beyond Recharts
- Want D3 power with React idioms
- Building a reusable chart library
- Need network graphs, hierarchies, geo maps
- Care about bundle size (pick only what you need)
Choose Recharts instead when:
- Need standard charts quickly
- Team less experienced with visualization
- Don’t need customization
visx vs D3 vs Recharts#
| Aspect | D3 | visx | Recharts |
|---|---|---|---|
| Flexibility | Maximum | High | Limited |
| Learning curve | Steep | Moderate | Easy |
| React integration | Manual | Native | Native |
| Pre-made charts | None | None | Many |
| Bundle control | Good | Excellent | All-or-nothing |
Resources#
S2: Comprehensive
S2-comprehensive: Technical Deep-Dive#
Focus#
This pass analyzes the internal architecture, algorithms, and implementation details of data visualization libraries. While S1 answered “WHICH library?”, S2 answers “HOW does it work?”
Analysis Framework#
For each library, we examine:
1. Architecture#
- Rendering pipeline (SVG/Canvas/WebGL)
- Component hierarchy and composition
- Data flow and transformation
- Memory management
2. Algorithms#
- Scale calculations (linear, log, time, band)
- Layout algorithms (force-directed, tree, arc)
- Interpolation methods
- Animation timing functions
3. Performance#
- Rendering benchmarks (elements/second)
- Bundle size and tree-shaking
- Memory usage patterns
- Optimization strategies
4. API Design#
- Programming paradigms (declarative vs imperative)
- Type safety and TypeScript support
- Extension points and customization
- Error handling
5. Integration Patterns#
- Framework-specific bindings (React, Vue, Angular)
- SSR compatibility
- Build tool requirements
- Testing strategies
Libraries Analyzed#
- Recharts - Composable React components
- D3.js - Low-level DOM manipulation
- Chart.js - Imperative Canvas API
- ECharts - Configuration-driven rendering
- Nivo - React wrapper with SSR
- visx - React primitives for D3
- Victory - Cross-platform React Native
Key Insights from S2#
Rendering Pipeline Trade-offs#
SVG Pipeline (Recharts, Victory, Nivo)
Data → Scale → React Component → SVG Element → DOM- Each data point becomes a DOM node
- Accessible, inspectable, stylable
- Hits performance wall at ~1000 elements
Canvas Pipeline (Chart.js, ECharts)
Data → Scale → Canvas Context → Bitmap- All points rendered as pixels
- No individual element access
- Handles 1000-50000 points smoothly
WebGL Pipeline (ECharts-GL)
Data → Scale → Shader → GPU → Frame Buffer- GPU-accelerated geometry
- Handles 50000+ points
- Complex setup, harder debugging
React Integration Patterns#
Pattern 1: Library Owns DOM (Recharts, Nivo)
<LineChart data={data}>
<Line dataKey="value" />
</LineChart>- Library renders SVG elements
- React controls when to render
- Easy to use, limited flexibility
Pattern 2: D3 Math, React Rendering (visx)
const scale = scaleLinear().domain([0, 100]).range([0, 500])
return data.map(d => <circle cx={scale(d.x)} r={5} />)- D3 for calculations only
- React renders DOM
- More control, more code
Pattern 3: D3 Controls DOM (Anti-pattern)
useEffect(() => {
d3.select(ref.current).selectAll('rect').data(data).join('rect')
}, [data])- D3 mutates DOM directly
- React unaware of changes
- Breaks React’s model
Performance Bottlenecks#
- SVG Layout Thrashing - Reading/writing layout repeatedly
- Animation Frame Budget - Exceeding 16ms per frame
- Memory Leaks - Not cleaning up event listeners
- Re-render Cascades - Unnecessary recalculations
Comparison by Dimension#
See feature-comparison.md for detailed side-by-side analysis.
Chart.js - Technical Architecture#
Core Philosophy#
Chart.js is an imperative, configuration-driven Canvas charting library. You provide a config object, it renders to Canvas.
new Chart(ctx, {
type: 'line',
data: { /* ... */ },
options: { /* ... */ }
})Rendering Architecture#
Canvas-First Design#
Unlike SVG libraries (Recharts, D3), Chart.js renders to Canvas:
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// Chart.js draws pixels directly
ctx.fillRect(x, y, width, height)
ctx.stroke()Implications:
- Fast for 1000-50000 data points
- No individual element access (all pixels)
- Less accessible (no DOM nodes for screen readers)
- Harder to customize (no CSS styling)
Rendering Pipeline#
Config Object → Chart Instance → Rendering Engine → Canvas Context → PixelsDetailed steps:
- Parse config - Validate chart type, data, options
- Calculate layout - Determine axis positions, label spacing
- Generate scales - Map data values to pixel coordinates
- Render elements - Draw axes, grid, data points, labels
- Register interactions - Set up hover/click handlers
- Animation loop - Interpolate values over time
Chart Types#
Built-in chart types (extensible):
Basic#
line- Time series, trendsbar- Comparisons (vertical/horizontal)pie/doughnut- Part-to-wholeradar- Multivariate datapolarArea- Circular bar chartscatter- X-Y relationshipsbubble- X-Y-Z (size as 3rd dimension)
Mixed#
{
type: 'line',
data: {
datasets: [
{ type: 'line', data: [...] },
{ type: 'bar', data: [...] }
]
}
}Configuration Structure#
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar'], // X-axis labels
datasets: [{
label: 'Sales',
data: [10, 20, 15],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1 // Line curvature
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 2, // width:height ratio
scales: {
x: {
type: 'linear', // or 'category', 'time', 'logarithmic'
title: { display: true, text: 'Month' }
},
y: {
beginAtZero: true,
ticks: { callback: (value) => `$${value}` }
}
},
plugins: {
legend: { display: true, position: 'top' },
tooltip: { enabled: true, mode: 'index' },
title: { display: true, text: 'Sales Dashboard' }
},
interaction: {
mode: 'nearest', // or 'index', 'point', 'dataset'
intersect: false
},
animation: {
duration: 1000,
easing: 'easeInOutQuart'
}
}
})Scale System#
Chart.js has its own scale implementations (not D3).
Scale Types#
1. Linear Scale
scales: {
y: {
type: 'linear',
min: 0,
max: 100,
ticks: {
stepSize: 10,
callback: (value) => value + '%'
}
}
}2. Logarithmic Scale
scales: {
y: { type: 'logarithmic' }
}3. Category Scale (discrete)
scales: {
x: {
type: 'category',
labels: ['A', 'B', 'C']
}
}4. Time Scale (requires date-fns or moment.js)
import 'chartjs-adapter-date-fns'
scales: {
x: {
type: 'time',
time: {
unit: 'day',
displayFormats: { day: 'MMM d' }
}
}
}Performance Characteristics#
Benchmark Results#
| Data Points | Render Time | Frame Rate |
|---|---|---|
| 100 | ~3ms | 333 FPS |
| 1,000 | ~12ms | 83 FPS |
| 10,000 | ~80ms | 12 FPS |
| 50,000 | ~450ms | 2 FPS |
Performance ceiling: ~10,000 points for smooth interaction
Why Canvas is Faster than SVG#
- No DOM overhead - Single Canvas element, not 1000s of SVG nodes
- Direct pixel manipulation - Bypasses layout/paint pipeline
- GPU acceleration - Modern browsers use GPU for Canvas
Optimization Strategies#
1. Data Decimation (built-in)
options: {
parsing: false, // Assume data is pre-parsed
decimation: {
enabled: true,
algorithm: 'lttb', // Largest-Triangle-Three-Buckets
samples: 500
}
}LTTB Algorithm:
- Downsamples 10,000 points → 500 points
- Preserves visual shape
- ~10x performance improvement
2. Disable Animations
options: { animation: false }3. Reduce Update Frequency
chart.update('none') // No animation
// vs
chart.update() // Default animation
Bundle Size#
chart.js: 242 KB (uncompressed)
chart.js: ~60 KB (gzipped)Tree-shaking: Not supported well
- Must import entire library
- Use auto-tree-shaking bundler (Webpack 5+)
Comparison:
- Recharts: 130 KB gzipped
- Chart.js: 60 KB gzipped ✓ (smaller)
Plugin System#
Chart.js is highly extensible via plugins.
Built-in Plugins#
import {
Chart,
LineController,
LineElement,
PointElement,
LinearScale,
Title,
Tooltip,
Legend
} from 'chart.js'
Chart.register(
LineController,
LineElement,
PointElement,
LinearScale,
Title,
Tooltip,
Legend
)Custom Plugin#
const customPlugin = {
id: 'customPlugin',
beforeDraw(chart, args, options) {
const { ctx, chartArea } = chart
ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'
ctx.fillRect(chartArea.left, chartArea.top, chartArea.width, chartArea.height)
},
afterDatasetDraw(chart, args, options) {
// Called after each dataset renders
}
}
Chart.register(customPlugin)Plugin hooks:
beforeInit,afterInitbeforeUpdate,afterUpdatebeforeDraw,afterDrawbeforeDatasetDraw,afterDatasetDraw- 20+ hooks total
Animation System#
Configuration#
options: {
animation: {
duration: 1000,
easing: 'easeOutQuart',
delay: (context) => context.dataIndex * 50, // Stagger
loop: false,
// Specific animations
x: { duration: 2000 }, // X-axis animation
y: { duration: 1000 } // Y-axis animation
}
}Easing functions:
- Linear:
linear - Ease:
easeInQuad,easeOutQuad,easeInOutQuad - Cubic, Quart, Quint, Expo, Circ, Back, Elastic
Animation Events#
options: {
onProgress(animation) {
console.log(`Progress: ${animation.currentStep / animation.numSteps}`)
},
onComplete(animation) {
console.log('Animation finished')
}
}Responsive Design#
Auto-Resize#
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 2 // width:height
}Implementation:
- Uses
ResizeObserver(with polyfill) - Redraws chart on container resize
- Debounced to avoid excessive redraws
Manual Size Control#
chart.resize(width, height)Interaction Modes#
Hover/Click Behavior#
options: {
interaction: {
mode: 'index', // 'point', 'nearest', 'dataset', 'x', 'y'
intersect: false, // Trigger on axis, not just point
axis: 'x' // Only consider x-axis distance
}
}Modes:
point- Hover directly over pointnearest- Closest point to cursorindex- All points at same x-indexdataset- All points in datasetx/y- Points along axis
Custom Interactions#
canvas.onclick = (evt) => {
const points = chart.getElementsAtEventForMode(
evt,
'nearest',
{ intersect: true },
false
)
if (points.length) {
const { datasetIndex, index } = points[0]
console.log(chart.data.datasets[datasetIndex].data[index])
}
}Accessibility#
Limitations#
Canvas is inherently less accessible:
- No DOM elements for screen readers
- No keyboard navigation
- Difficult to provide alt text for individual data points
Mitigations#
<canvas aria-label="Sales chart showing upward trend">
Fallback: Sales increased from $100 in Jan to $300 in Mar
</canvas>Accessibility plugins:
- chartjs-plugin-a11y-legend
- Adds keyboard navigation
- ARIA labels for data points
React Integration#
react-chartjs-2 (Official Wrapper)#
import { Line } from 'react-chartjs-2'
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
} from 'chart.js'
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
)
function App() {
const data = {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{ data: [10, 20, 15] }]
}
return <Line data={data} options={{ responsive: true }} />
}Wrapper handles:
- Canvas element creation
- Chart instance management
- Update/destroy lifecycle
- TypeScript types
TypeScript Support#
Type Coverage: Excellent
import { ChartConfiguration, ChartType } from 'chart.js'
const config: ChartConfiguration<'line'> = {
type: 'line',
data: {
labels: ['A', 'B'],
datasets: [{
label: 'Dataset',
data: [1, 2]
}]
}
}
// Custom types
interface CustomDataPoint {
x: number
y: number
label: string
}
const scatterConfig: ChartConfiguration<'scatter', CustomDataPoint[]> = {
type: 'scatter',
data: {
datasets: [{
data: [
{ x: 1, y: 2, label: 'Point A' },
{ x: 3, y: 4, label: 'Point B' }
]
}]
}
}Key Strengths#
- Canvas performance - Handles 10K points smoothly
- Simple API - Config object easier than D3
- Small bundle - 60 KB gzipped
- Great docs - Comprehensive, well-organized
- Active maintenance - Regular releases
Key Limitations#
- Less flexible than D3 - Opinionated chart types
- Accessibility - Canvas harder for screen readers
- Customization limits - Harder to style than SVG
- No React-native API - Imperative, not declarative
When to Use Chart.js#
Ideal for:
- 1,000-10,000 data points
- Framework-agnostic projects (Vue, Angular, vanilla JS)
- Performance-critical dashboards
- Teams prioritizing simplicity
Not ideal for:
- Custom visualizations (use D3)
- React-first projects (Recharts more idiomatic)
- Heavy accessibility requirements (use SVG libraries)
D3.js - Technical Architecture#
Core Philosophy#
D3 = Data-Driven Documents
Unlike other libraries that render charts, D3 provides primitives for binding data to DOM and applying transformations.
// D3 doesn't "render a bar chart"
// D3 gives you tools to create one yourself
d3.select('svg')
.selectAll('rect')
.data([10, 20, 30])
.join('rect')
.attr('x', (d, i) => i * 25)
.attr('height', d => d * 10)Module Architecture#
D3 v7+ is modular - 30+ independent packages:
Data Manipulation#
d3-array- Statistics (min, max, mean, quantile, bisect)d3-collection- Maps, sets, nestsd3-random- Random number generators
Scales and Axes#
d3-scale- Map data → visual valuesd3-axis- Generate axis elementsd3-time- Time intervals, formatting
Shapes and Layouts#
d3-shape- Line, area, arc, pie generatorsd3-chord- Chord diagramsd3-hierarchy- Tree layouts, treemaps, partitionsd3-force- Force-directed graph layoutsd3-sankey- Flow diagrams
DOM Manipulation#
d3-selection- Select and manipulate elementsd3-transition- Animated transitionsd3-drag- Drag-and-dropd3-zoom- Pan and zoom
Geographic#
d3-geo- Map projectionsd3-geo-projection- Extended projections
Utilities#
d3-fetch- Load CSV, JSON, etc.d3-format- Number formattingd3-interpolate- Interpolation functionsd3-ease- Easing functions
Selection API (Core Abstraction)#
The Join Pattern#
D3’s fundamental operation: data joins
const data = [10, 20, 30, 40]
d3.select('svg')
.selectAll('circle') // Select all circles (even if none exist)
.data(data) // Bind data
.join('circle') // Create/update/remove elements
.attr('cx', (d, i) => i * 50)
.attr('cy', 100)
.attr('r', d => d)What .join() does:
- Enter: Create missing elements (4 data points, 0 circles → create 4)
- Update: Update existing elements (4 data points, 4 circles → update 4)
- Exit: Remove extra elements (4 data points, 6 circles → remove 2)
Method Chaining#
D3 uses fluent interface pattern:
selection
.attr('width', 100)
.attr('height', 200)
.style('fill', 'blue')
.on('click', handler)Each method returns the selection for chaining.
Scale System#
Scales are pure functions that map input → output.
Scale Types#
1. Continuous Scales
// Linear: y = mx + b
const linear = d3.scaleLinear()
.domain([0, 100]) // Data space
.range([0, 500]) // Pixel space
linear(50) // → 250
// Log: y = log(x)
const log = d3.scaleLog()
.domain([1, 1000])
.range([0, 500])
// Power: y = x^k
const power = d3.scalePow().exponent(2)
// Time: handles date arithmetic
const time = d3.scaleTime()
.domain([new Date(2024, 0, 1), new Date(2024, 11, 31)])
.range([0, 1000])2. Sequential Scales (continuous color)
const color = d3.scaleSequential(d3.interpolateViridis)
.domain([0, 100])
color(50) // → "rgb(72, 130, 145)"
3. Ordinal Scales (discrete)
const category = d3.scaleOrdinal()
.domain(['A', 'B', 'C'])
.range(['red', 'green', 'blue'])
category('A') // → "red"
4. Band Scales (for bar charts)
const x = d3.scaleBand()
.domain(['Jan', 'Feb', 'Mar'])
.range([0, 300])
.padding(0.1)
x('Jan') // → 0
x.bandwidth() // → 96 (width of each band)
Shape Generators#
Generators create SVG path d attributes.
Line Generator#
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX) // Smoothing
const pathData = line(data)
// → "M 0,100 L 50,150 L 100,120 ..."
Curve algorithms:
curveLinear- Straight lines (default)curveBasis- B-spline (smooth)curveMonotoneX- Monotone cubic (no overshoots)curveStep- Step function- 10+ more curves
Area Generator#
const area = d3.area()
.x(d => xScale(d.date))
.y0(height) // Baseline
.y1(d => yScale(d.value)) // Top line
// Creates filled area under curve
Arc Generator (Pie/Donut)#
const arc = d3.arc()
.innerRadius(0) // 0 = pie, > 0 = donut
.outerRadius(100)
const pie = d3.pie()
.value(d => d.value)
const arcs = pie(data) // Calculates angles
arcs.forEach((d, i) => {
svg.append('path')
.attr('d', arc(d)) // arc generates path
.attr('fill', colors[i])
})Transition System#
Smooth animations between states.
d3.selectAll('circle')
.transition() // Start transition
.duration(1000) // 1 second
.delay((d, i) => i * 100) // Stagger
.ease(d3.easeCubicInOut) // Easing function
.attr('r', d => newRadius(d))Easing Functions:
- Linear:
easeLinear - Quadratic:
easeQuadIn,easeQuadOut,easeQuadInOut - Cubic, Quartic, Quintic, Exponential
- Bounce:
easeBounce - Elastic:
easeElastic - Back:
easeBack(overshoots then settles)
Transition Chaining:
selection
.transition().duration(500).attr('opacity', 0)
.transition().duration(500).attr('height', 0)
.remove() // Remove after transitions
Force-Directed Layouts#
For network graphs, node-link diagrams.
const simulation = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().strength(-30)) // Repulsion
.force('link', d3.forceLink(links).distance(50)) // Attraction
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(10)) // No overlap
simulation.on('tick', () => {
// Update node/link positions on each tick
linkElements.attr('x1', d => d.source.x)
nodeElements.attr('cx', d => d.x)
})Forces available:
forceManyBody- N-body charge (repulsion/attraction)forceLink- Spring between linked nodesforceCenter- Pull toward center pointforceCollide- Collision detectionforceX/forceY- Pull toward axisforceRadial- Circular arrangement
Hierarchy Layouts#
For tree structures.
const data = {
name: 'root',
children: [
{ name: 'child1', children: [...] },
{ name: 'child2' }
]
}
const hierarchy = d3.hierarchy(data)
// Tree layout
const tree = d3.tree().size([width, height])
tree(hierarchy) // Assigns x, y to each node
// Treemap layout
const treemap = d3.treemap().size([width, height])
treemap(hierarchy.sum(d => d.value)) // Assigns x0, y0, x1, y1
// Partition (sunburst)
const partition = d3.partition()Performance Characteristics#
Benchmark Results#
Rendering 10,000 circles:
- D3 + SVG: ~150ms (choppy)
- D3 + Canvas: ~15ms (smooth)
- D3 + WebGL: ~2ms (very smooth)
Key insight: D3 itself is fast - SVG is the bottleneck.
Canvas Rendering#
D3 can target Canvas instead of SVG:
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
data.forEach(d => {
ctx.beginPath()
ctx.arc(xScale(d.x), yScale(d.y), 5, 0, 2 * Math.PI)
ctx.fill()
})Trade-off: No DOM elements = no hover events on individual points.
Bundle Size#
d3 (full): 250 KB (uncompressed), ~70 KB gzipped
Modular imports (tree-shaking):
d3-selection: 45 KB → 15 KB gzipped
d3-scale: 85 KB → 25 KB gzipped
d3-shape: 73 KB → 22 KB gzippedBest practice: Import only what you need
import { scaleLinear } from 'd3-scale'
import { line } from 'd3-shape'TypeScript Support#
Type Coverage: Excellent (@types/d3)
import { ScaleLinear, Selection } from 'd3'
const scale: ScaleLinear<number, number> = d3.scaleLinear()
.domain([0, 100])
.range([0, 500])
const svg: Selection<SVGSVGElement, unknown, HTMLElement, any> =
d3.select<SVGSVGElement, unknown>('svg')Challenge: Complex generic types for selections.
React Integration Challenges#
Anti-Pattern: D3 Controls DOM#
// DON'T DO THIS
useEffect(() => {
d3.select(ref.current)
.selectAll('rect')
.data(data)
.join('rect') // D3 creates/removes elements
}, [data])Problem: React doesn’t know about DOM changes.
Best Practice: D3 for Math, React for DOM#
// DO THIS
const xScale = scaleLinear().domain([0, 100]).range([0, width])
const yScale = scaleLinear().domain([0, 100]).range([height, 0])
return (
<svg>
{data.map(d => (
<circle
key={d.id}
cx={xScale(d.x)}
cy={yScale(d.y)}
r={5}
/>
))}
</svg>
)This is exactly what visx does.
Key Strengths#
- Maximum flexibility - Can create ANY visualization
- Modular - Import only what you need
- Battle-tested - 13 years, 108K GitHub stars
- Excellent docs - Observable notebooks, examples
Key Limitations#
- Steep learning curve - Takes weeks to master
- Verbose - More code than high-level libraries
- No built-in charts - You build everything from scratch
- React friction - Doesn’t fit React’s declarative model
When to Use D3#
Ideal for:
- Custom, novel visualizations
- Full creative control needed
- Complex interactions (brushing, linked views)
- Network graphs, force layouts, geo maps
Not ideal for:
- Standard charts (use Recharts)
- Tight deadlines (high-level libs faster)
- Teams new to D3 (steep learning curve)
- React projects (consider visx)
ECharts - Technical Architecture#
Core Philosophy#
ECharts (from Apache/Baidu) is a configuration-driven, enterprise-grade charting library with Canvas/WebGL rendering for massive datasets.
const chart = echarts.init(document.getElementById('container'))
chart.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'line' }]
})Rendering Architecture#
Multi-Renderer Support#
ECharts supports Canvas (default) and SVG rendering:
// Canvas (better for large datasets)
const chart = echarts.init(dom, null, { renderer: 'canvas' })
// SVG (better for small datasets, accessibility)
const chart = echarts.init(dom, null, { renderer: 'svg' })ZRender (Internal Rendering Engine)#
ECharts uses ZRender - a lightweight 2D drawing library:
ECharts Config → ZRender Elements → Canvas/SVG Context → Pixels/DOMZRender features:
- Shape primitives (rect, circle, path, text, image)
- Event system (hover, click, drag)
- Animation engine
- Layer management (for performance)
Performance: The Big Data Champion#
Benchmark Results#
| Renderer | Data Points | Render Time | Interactive |
|---|---|---|---|
| Canvas | 10,000 | ~50ms | Yes (smooth) |
| Canvas | 100,000 | ~200ms | Yes (60 FPS) |
| Canvas | 1,000,000 | ~1.5s | Laggy |
| WebGL | 1,000,000 | ~300ms | Yes (smooth) |
| WebGL | 10,000,000 | ~2s | Yes (60 FPS) |
ECharts GL (WebGL extension):
import 'echarts-gl'
series: [{
type: 'scatter3D', // 3D scatter plot
data: millionPoints,
symbolSize: 2
}]Performance Optimizations#
1. Progressive Rendering
series: [{
type: 'line',
data: hugeDataset,
progressive: 5000, // Render 5000 points per frame
progressiveThreshold: 10000 // Enable if > 10K points
}]2. Sampling
series: [{
type: 'line',
data: hugeDataset,
sampling: 'lttb' // Largest-Triangle-Three-Buckets
}]3. Data Zoom (Virtual Scrolling)
dataZoom: [{
type: 'slider',
start: 0,
end: 10 // Show only 10% of data initially
}]Only visible data is rendered = massive performance gain.
4. Large Mode
series: [{
type: 'scatter',
large: true, // Enable large mode
largeThreshold: 2000, // Activate if > 2000 points
data: points
}]Disables hover on individual points, renders as batch.
Configuration System#
Option Object Structure#
{
// Title
title: {
text: 'Sales Dashboard',
subtext: 'Q1 2025',
left: 'center'
},
// Tooltip
tooltip: {
trigger: 'axis', // or 'item', 'none'
formatter: '{b}: {c}' // Template or function
},
// Legend
legend: {
data: ['Sales', 'Profit'],
bottom: 0
},
// Grid (chart area)
grid: {
left: '10%',
right: '10%',
bottom: '15%',
containLabel: true
},
// Axes
xAxis: {
type: 'category', // or 'value', 'time', 'log'
data: ['Mon', 'Tue', 'Wed'],
axisLine: { lineStyle: { color: '#666' } },
axisTick: { show: false }
},
yAxis: {
type: 'value',
axisLabel: { formatter: '{value} K' },
splitLine: { lineStyle: { type: 'dashed' } }
},
// Data Zoom (pan/zoom controls)
dataZoom: [
{ type: 'slider', start: 0, end: 100 },
{ type: 'inside' } // Mouse wheel zoom
],
// Toolbox (export, zoom, restore)
toolbox: {
feature: {
saveAsImage: {}, // Export as PNG
dataZoom: {}, // Box zoom
restore: {}, // Reset
dataView: {} // Show data table
}
},
// Series (the actual data)
series: [{
name: 'Sales',
type: 'line', // or 'bar', 'pie', 'scatter', 'heatmap', etc.
data: [120, 200, 150, 80, 70, 110],
smooth: true, // Bezier curve smoothing
areaStyle: {}, // Fill area under line
emphasis: {
focus: 'series' // Highlight on hover
}
}]
}Chart Types (100+)#
Basic Charts#
- Line, Area, Bar (stacked, grouped)
- Pie, Donut, Rose (Nightingale)
- Scatter, Bubble
- Candlestick (stock charts)
- Radar, Gauge, Funnel
Advanced Charts#
- Heatmap, Treemap
- Sankey (flow diagrams)
- Graph (network, force-directed)
- Parallel coordinates
- Boxplot, K-line
3D Charts (with echarts-gl)#
- Scatter3D, Bar3D, Line3D
- Surface3D (3D surface plots)
- Globe (geographic 3D)
Geographic#
- Map (GeoJSON support)
- Heatmap on map
- Flow lines on map
Coordinate Systems#
ECharts supports multiple coordinate systems in one chart:
{
xAxis: { /* Cartesian X */ },
yAxis: { /* Cartesian Y */ },
polar: { /* Polar coordinates */ },
geo: { /* Geographic */ },
series: [
{ type: 'bar', coordinateSystem: 'cartesian2d' },
{ type: 'line', coordinateSystem: 'polar' },
{ type: 'scatter', coordinateSystem: 'geo' }
]
}Visual Encoding#
Map data to visual properties:
visualMap: {
type: 'continuous', // or 'piecewise'
min: 0,
max: 100,
dimension: 2, // Which data dimension
inRange: {
color: ['#50a3ba', '#eac736', '#d94e5d'] // Gradient
}
}Automatically colors data points based on value.
Responsive Design#
Container Size#
// Auto-resize on window resize
window.addEventListener('resize', () => {
chart.resize()
})Media Queries#
option: {
baseOption: { /* Default config */ },
media: [
{
query: { maxWidth: 500 },
option: {
legend: { bottom: 0 },
grid: { left: 50 }
}
},
{
query: { minWidth: 500 },
option: {
legend: { right: 0 },
grid: { left: 100 }
}
}
]
}Animation#
Built-in Animations#
animation: true,
animationDuration: 1000,
animationEasing: 'cubicOut',
animationDelay: (idx) => idx * 50 // Stagger
Easing functions:
- Linear, quadratic, cubic, quartic
- Elastic, bounce, back
Update Animations#
chart.setOption(newOption, {
notMerge: false, // Merge with existing option
lazyUpdate: false,
silent: false
})Automatically animates transitions between states.
Bundle Size#
echarts (full): 1.1 MB (uncompressed), ~320 KB gzipped
Tree-shaking (modular):
import * as echarts from 'echarts/core'
import { LineChart, BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([LineChart, BarChart, GridComponent, TooltipComponent, CanvasRenderer])
Result: ~150 KB gzipped (with only needed components)Comparison:
- ECharts (modular): 150 KB gzipped
- Recharts: 130 KB gzipped
- Chart.js: 60 KB gzipped ✓
ECharts is heavier, but handles 100x more data.
TypeScript Support#
Type Coverage: Good (ships with types)
import * as echarts from 'echarts/core'
import type { EChartsOption } from 'echarts'
const option: EChartsOption = {
xAxis: { type: 'category', data: ['Mon', 'Tue'] },
yAxis: { type: 'value' },
series: [{ type: 'line', data: [120, 200] }]
}
const chart = echarts.init(document.getElementById('main')!)
chart.setOption(option)React Integration#
echarts-for-react (Community)#
import ReactECharts from 'echarts-for-react'
function App() {
const option = {
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ type: 'line', data: [120, 200, 150] }]
}
return <ReactECharts option={option} />
}Wrapper handles:
- Chart initialization
- Option updates
- Resize on container change
- Cleanup on unmount
Accessibility#
SVG Renderer#
const chart = echarts.init(dom, null, { renderer: 'svg' })SVG output is more accessible than Canvas:
- Screen readers can parse SVG elements
- Each element selectable/inspectable
ARIA Support#
aria: {
enabled: true,
label: 'Sales chart showing upward trend',
decal: {
show: true // Adds patterns for colorblind users
}
}Decal patterns help distinguish series without color.
Internationalization#
Built-in language packs:
import 'echarts/i18n/langEN'
import 'echarts/i18n/langZH'
const chart = echarts.init(dom, 'en') // or 'zh'
Key Strengths#
- Massive datasets - 10M points with WebGL
- 100+ chart types - Most comprehensive library
- Enterprise features - Data zoom, export, theming
- Great docs - Chinese + English, many examples
- Active development - Apache Foundation backing
Key Limitations#
- Large bundle - 320 KB full, 150 KB tree-shaken
- Complex API - Steep learning curve
- Imperative - Not React-idiomatic (config objects)
- Overkill for simple charts - Use Recharts instead
When to Use ECharts#
Ideal for:
- 10,000+ data points
- Enterprise dashboards
- Need many chart types
- Geographic visualizations
- 3D charts
Not ideal for:
- Simple charts with < 1000 points (use Recharts)
- React-first projects preferring JSX (use Recharts)
- Bundle size critical (use Chart.js)
- Custom novel visualizations (use D3)
Feature Comparison Matrix#
Rendering Technology#
| Library | Renderer | Max Elements | Frame Rate @ 10K |
|---|---|---|---|
| Recharts | SVG | ~1000 | 8 FPS (choppy) |
| D3 | SVG/Canvas | ~1000 SVG, ~50K Canvas | 12 FPS (Canvas) |
| Chart.js | Canvas | ~10,000 | 12 FPS |
| ECharts | Canvas/SVG/WebGL | 10M+ (WebGL) | 60 FPS (WebGL) |
| Nivo | SVG | ~1000 | 8 FPS |
| visx | SVG | ~1000 | 8 FPS |
| Victory | SVG | ~1000 | 8 FPS |
Key insight: ECharts is the only library that handles massive datasets smoothly.
Bundle Size (gzipped)#
| Library | Full Bundle | Tree-Shaken | Notes |
|---|---|---|---|
| Chart.js | 60 KB | N/A | No tree-shaking |
| visx | 60 KB | 30-60 KB | Excellent tree-shaking |
| D3 | 70 KB | 15-50 KB | Excellent tree-shaking |
| Recharts | 130 KB | 100-130 KB | Limited tree-shaking |
| ECharts | 320 KB | 150-200 KB | Moderate tree-shaking |
| Nivo | 180 KB | 150 KB | Limited tree-shaking |
| Victory | 210 KB | 180 KB | Limited tree-shaking |
Winner: Chart.js (smallest) and visx (best tree-shaking)
API Paradigm#
| Library | Paradigm | Example |
|---|---|---|
| Recharts | Declarative (JSX) | <LineChart><Line /></LineChart> |
| Chart.js | Imperative (config) | new Chart(ctx, { type: 'line' }) |
| ECharts | Imperative (config) | chart.setOption({ series: [...] }) |
| D3 | Imperative (method chain) | d3.select().data().join() |
| visx | Declarative (JSX) | <LinePath data={data} /> |
| Nivo | Declarative (JSX) | <ResponsiveLine data={data} /> |
| Victory | Declarative (JSX) | <VictoryLine data={data} /> |
React projects: Prefer declarative (Recharts, visx, Nivo, Victory) Framework-agnostic: Use imperative (Chart.js, ECharts, D3)
TypeScript Support#
| Library | Type Coverage | Notes |
|---|---|---|
| Recharts | ⭐⭐⭐⭐⭐ | Ships with types, excellent |
| Chart.js | ⭐⭐⭐⭐⭐ | Ships with types, excellent |
| ECharts | ⭐⭐⭐⭐ | Ships with types, good |
| D3 | ⭐⭐⭐⭐⭐ | @types/d3, complex generics |
| visx | ⭐⭐⭐⭐⭐ | Ships with types, excellent |
| Nivo | ⭐⭐⭐⭐ | Ships with types, good |
| Victory | ⭐⭐⭐ | @types/victory, partial coverage |
Winner: Recharts, Chart.js, visx, D3 (all excellent)
Chart Type Coverage#
| Chart Type | Recharts | Chart.js | ECharts | D3 | visx | Nivo | Victory |
|---|---|---|---|---|---|---|---|
| Line | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Bar | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Pie/Donut | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Scatter | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Area | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Radar | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Heatmap | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Treemap | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Sankey | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Network | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ |
| 3D | ❌ | ❌ | ✅ (GL) | ❌ | ❌ | ❌ | ❌ |
| Geographic | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
Winner: ECharts (most comprehensive), D3 (fully customizable)
Interactivity Features#
| Feature | Recharts | Chart.js | ECharts | D3 | visx | Nivo | Victory |
|---|---|---|---|---|---|---|---|
| Tooltips | ✅ Built-in | ✅ Built-in | ✅ Built-in | ❌ DIY | ❌ DIY | ✅ Built-in | ✅ Built-in |
| Legend | ✅ Built-in | ✅ Built-in | ✅ Built-in | ❌ DIY | ❌ DIY | ✅ Built-in | ✅ Built-in |
| Zoom/Pan | ❌ | ✅ Built-in | ✅ Built-in | ✅ DIY | ❌ DIY | ❌ | ❌ |
| Brush Selection | ✅ Built-in | ❌ | ✅ Built-in | ✅ DIY | ❌ DIY | ❌ | ✅ Built-in |
| Export Image | ❌ | ✅ Built-in | ✅ Built-in | ❌ DIY | ❌ DIY | ❌ | ❌ |
| Data Table View | ❌ | ❌ | ✅ Built-in | ❌ | ❌ | ❌ | ❌ |
Winner: ECharts (most features out-of-the-box)
Animation System#
| Library | Engine | Easing Functions | Stagger Support |
|---|---|---|---|
| Recharts | CSS/JS | 5 | ✅ |
| Chart.js | Custom | 15+ | ✅ |
| ECharts | Custom | 10+ | ✅ |
| D3 | Custom (transitions) | 15+ | ✅ |
| visx | ❌ (use external) | N/A | N/A |
| Nivo | react-spring | Many | ✅ |
| Victory | Custom | 5 | ✅ |
Winner: D3 (most powerful), Nivo (smoothest with react-spring)
Responsive Design#
| Library | Auto-Resize | Container Query | Adaptive Layout |
|---|---|---|---|
| Recharts | ✅ <ResponsiveContainer> | ❌ | ❌ |
| Chart.js | ✅ responsive: true | ❌ | ❌ |
| ECharts | ✅ chart.resize() | ✅ Media queries | ✅ |
| D3 | ❌ Manual | ❌ | ❌ |
| visx | ✅ <ParentSize> | ❌ | ❌ |
| Nivo | ✅ <Responsive*> | ❌ | ❌ |
| Victory | ✅ <VictoryContainer> | ❌ | ❌ |
Winner: ECharts (media query support)
Accessibility#
| Library | SVG Output | ARIA Support | Screen Reader | Keyboard Nav |
|---|---|---|---|---|
| Recharts | ✅ | ❌ Manual | Partial | ❌ Manual |
| Chart.js | ❌ Canvas | ❌ | ❌ | ❌ |
| ECharts | ✅ (SVG mode) | ✅ Built-in | ✅ | ❌ |
| D3 | ✅ | ❌ Manual | ✅ | ❌ Manual |
| visx | ✅ | ❌ Manual | ✅ | ❌ Manual |
| Nivo | ✅ | ❌ Manual | Partial | ❌ Manual |
| Victory | ✅ | ✅ Built-in | ✅ | ❌ |
Winner: Victory, ECharts (best ARIA support)
Cross-Platform Support#
| Library | React | React Native | Vue | Angular | Vanilla JS |
|---|---|---|---|---|---|
| Recharts | ✅ | ❌ | ❌ | ❌ | ❌ |
| Chart.js | ✅ Wrapper | ❌ | ✅ Wrapper | ✅ Wrapper | ✅ |
| ECharts | ✅ Wrapper | ❌ | ✅ Wrapper | ✅ Wrapper | ✅ |
| D3 | ✅ | ❌ | ✅ | ✅ | ✅ |
| visx | ✅ | ❌ | ❌ | ❌ | ❌ |
| Nivo | ✅ | ❌ | ❌ | ❌ | ❌ |
| Victory | ✅ | ✅ | ❌ | ❌ | ❌ |
Winner: Chart.js, ECharts, D3 (framework-agnostic)
Server-Side Rendering (SSR)#
| Library | SSR Support | Hydration | Notes |
|---|---|---|---|
| Recharts | ⚠️ Partial | Issues | Needs isomorphic-style-loader |
| Chart.js | ❌ | N/A | Canvas requires browser |
| ECharts | ✅ | Good | Server-side Canvas via node-canvas |
| D3 | ✅ | Good | Can generate SVG server-side |
| visx | ✅ | Good | Pure SVG, works well |
| Nivo | ✅ | Excellent | Built for SSR |
| Victory | ⚠️ Partial | Issues | Complex setup |
Winner: Nivo (built for SSR), visx, D3
Testing Support#
| Library | Unit Testing | Visual Regression | Snapshot Testing |
|---|---|---|---|
| Recharts | ✅ RTL | ✅ | ✅ |
| Chart.js | ⚠️ (Canvas) | ✅ | ❌ |
| ECharts | ⚠️ (Canvas) | ✅ | ❌ |
| D3 | ✅ RTL | ✅ | ✅ |
| visx | ✅ RTL | ✅ | ✅ |
| Nivo | ✅ RTL | ✅ | ✅ |
| Victory | ✅ RTL | ✅ | ✅ |
Note: Canvas-based libraries harder to unit test (pixels, not DOM)
Developer Experience#
| Aspect | Recharts | Chart.js | ECharts | D3 | visx | Nivo | Victory |
|---|---|---|---|---|---|---|---|
| Learning Curve | ⭐⭐ Easy | ⭐⭐ Easy | ⭐⭐⭐ Medium | ⭐⭐⭐⭐⭐ Hard | ⭐⭐⭐⭐ Hard | ⭐⭐ Easy | ⭐⭐⭐ Medium |
| Documentation | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Examples | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Community | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| Active Dev | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
Winner: Chart.js, ECharts, D3 (best docs and examples)
Summary: Best Library by Scenario#
| Scenario | Best Choice | Runner-Up |
|---|---|---|
| React dashboard (< 1K points) | Recharts | Nivo |
| Large datasets (10K+ points) | ECharts | Chart.js |
| Custom visualization | D3 | visx |
| Minimal bundle size | Chart.js | visx |
| Server-side rendering | Nivo | visx |
| React Native | Victory | - |
| Framework-agnostic | Chart.js | ECharts |
| TypeScript project | Recharts | visx |
| Accessibility priority | Victory | ECharts |
| Maximum chart variety | ECharts | D3 |
Recharts - Technical Architecture#
Rendering Architecture#
Component Hierarchy#
ResponsiveContainer
└─ LineChart (Coordinate System)
├─ CartesianGrid (Background Layer)
├─ XAxis / YAxis (Axis Layer)
├─ Tooltip (Interaction Layer)
├─ Legend (Meta Layer)
└─ Line (Graph Layer)
└─ Curve (Geometry)
└─ <path> (SVG Element)Rendering Pipeline#
// 1. Data normalization
data → validateData() → normalizedData
// 2. Scale generation (uses D3 scales)
domain: [minValue, maxValue]
range: [0, chartWidth]
scale = d3.scaleLinear().domain(domain).range(range)
// 3. Coordinate calculation
dataPoint.x → scale(dataPoint.x) → pixelX
// 4. Path generation (uses D3 shape generators)
d3.line()
.x(d => scale(d.x))
.y(d => scale(d.y))
.curve(d3.curveMonotoneX) // smoothing algorithm
// 5. SVG rendering
<path d="M 0,100 L 50,200 L 100,150" />D3 Integration#
Recharts uses D3 for math only, not DOM manipulation:
D3 modules used:
d3-scale- Scale generation (linear, band, time, log)d3-shape- Path generators (line, area, arc, pie)d3-interpolate- Animation interpolationd3-array- Data statistics (min, max, extent)
NOT used:
d3-selection- DOM selection/manipulation (React handles this)d3-transition- DOM animations (Recharts uses CSS/JS)
Performance Characteristics#
Benchmark Results#
| Data Points | Render Time | Frame Rate |
|---|---|---|
| 100 | ~5ms | 200 FPS |
| 500 | ~20ms | 50 FPS |
| 1000 | ~50ms | 20 FPS |
| 2000 | ~120ms | 8 FPS (choppy) |
| 5000 | ~400ms | Unusable |
Performance wall: ~1000 SVG elements
Why SVG Slows Down#
- Layout thrashing: Browser recalculates layout for each element
- Paint complexity: Each element painted separately
- Memory overhead: Each DOM node has event listeners, properties
- GC pressure: Creating/destroying many objects
Optimization Strategies#
1. Data Sampling
const sampledData = data.length > 1000
? sampleData(data, 1000)
: data2. Virtualization (not built-in) Only render visible portion of chart
3. Disable Animations
<Line isAnimationActive={false} />4. Debounce Updates
const debouncedData = useDebounce(data, 300)Bundle Size Analysis#
recharts: 417 KB (uncompressed)
├─ d3-scale: 85 KB
├─ d3-shape: 73 KB
├─ d3-interpolate: 45 KB
├─ recharts core: 214 KB
└─ Total gzipped: ~130 KBTree-shaking: Partial support
- Can import specific charts:
import { LineChart } from 'recharts' - D3 dependencies bundle together
- Typical real-world gzipped: 110-130 KB
TypeScript Support#
Type Coverage: Excellent (ships with types)
interface DataPoint {
name: string
value: number
category?: string
}
interface CustomTooltipProps {
active?: boolean
payload?: Array<{
name: string
value: number
dataKey: string
color: string
}>
label?: string
}
const CustomTooltip: React.FC<CustomTooltipProps> = ({
active,
payload,
label
}) => {
// Fully typed
}Animation System#
Built-in Animations#
Recharts uses CSS/JS animations (not D3 transitions):
<Line
animationDuration={1000} // Duration in ms
animationEasing="ease-in-out" // CSS easing function
animationBegin={0} // Delay before start
/>Easing options:
ease,ease-in,ease-out,ease-in-outlinear- Custom cubic-bezier
Animation Implementation#
// Simplified internals
const animate = (from, to, duration, easing) => {
const startTime = Date.now()
const frame = () => {
const elapsed = Date.now() - startTime
const progress = Math.min(elapsed / duration, 1)
const easedProgress = easingFunctions[easing](progress)
const current = from + (to - from) * easedProgress
render(current)
if (progress < 1) {
requestAnimationFrame(frame)
}
}
requestAnimationFrame(frame)
}Responsive Design#
Container Size Detection#
Recharts uses ResizeObserver (with polyfill):
<ResponsiveContainer width="100%" height={300}>
<LineChart>...</LineChart>
</ResponsiveContainer>Implementation:
// Simplified
const observer = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect
setDimensions({ width, height })
})
observer.observe(containerRef.current)Adaptive Rendering#
Charts adjust based on size:
- < 400px: Hide legend, reduce tick labels
- 400-800px: Normal rendering
- > 800px: Show all features
Customization Architecture#
Extension Points#
- Custom Shapes
const CustomBar = (props) => {
const { x, y, width, height, fill } = props
return <rect x={x} y={y} width={width} height={height} fill={fill} rx={5} />
}
<Bar shape={<CustomBar />} />- Custom Labels
const CustomLabel = ({ x, y, value }) => (
<text x={x} y={y} textAnchor="middle">{value}</text>
)
<Line label={<CustomLabel />} />Custom Tooltips (as seen earlier)
Custom Legends
const CustomLegend = ({ payload }) => (
<ul>
{payload.map(entry => (
<li key={entry.value} style={{ color: entry.color }}>
{entry.value}
</li>
))}
</ul>
)
<Legend content={<CustomLegend />} />Accessibility#
Built-in Features#
- SVG elements are inherently accessible (DOM nodes)
- Screen readers can access SVG text elements
- Keyboard navigation requires custom implementation
Accessibility Gaps#
Missing:
- ARIA labels on chart elements
- Keyboard shortcuts for data point navigation
- High contrast mode support
- Sonification (audio representation)
Recommended additions:
<LineChart aria-label="Sales data over time">
<Line aria-label="Monthly sales" />
</LineChart>Memory Management#
Potential Leaks#
- Event Listeners
// Recharts handles this internally
useEffect(() => {
const handler = () => {}
element.addEventListener('mousemove', handler)
return () => element.removeEventListener('mousemove', handler)
}, [])- Animation Frames
- Recharts cleans up on unmount
- Manual animations need cleanup
- D3 Scales
- Recreated on each render (GC pressure)
- Consider memoization for large datasets
Testing Strategy#
Unit Testing (Jest + React Testing Library)#
import { render, screen } from '@testing-library/react'
import { LineChart, Line, XAxis, YAxis } from 'recharts'
test('renders line chart', () => {
const data = [{ x: 1, y: 2 }, { x: 2, y: 4 }]
render(
<LineChart width={500} height={300} data={data}>
<XAxis dataKey="x" />
<YAxis />
<Line dataKey="y" />
</LineChart>
)
// SVG elements rendered
expect(screen.getByRole('img')).toBeInTheDocument()
})Visual Regression Testing (Chromatic, Percy)#
- Capture screenshots of charts
- Detect unintended visual changes
- Critical for chart libraries
Build Requirements#
Peer Dependencies:
react>=16.8.0react-dom>=16.8.0
Bundler Support:
- Webpack: Works out of the box
- Vite: Works with default config
- Next.js: Requires transpilation (not ESM)
Next.js Config:
module.exports = {
transpilePackages: ['recharts']
}Key Strengths#
- React-native API - JSX feels natural
- D3 math - Solid calculations
- TypeScript - Full type safety
- Composition - Mix chart types easily
Key Limitations#
- SVG only - Performance ceiling at 1000 points
- Limited chart types - No network graphs, Sankey diagrams
- Animation control - Less flexible than D3
- Bundle size - 130KB gzipped (large)
When to Use Recharts#
Ideal for:
- React dashboards with standard charts
- < 1000 data points
- Teams prioritizing developer experience
- TypeScript projects
Not ideal for:
- Large datasets (use ECharts)
- Custom visualizations (use visx/D3)
- Minimal bundle size (use lightweight alternatives)
S2 Recommendation: Technical Architecture Insights#
Key Technical Findings#
1. Rendering Pipeline is the Performance Bottleneck#
SVG ceiling: ~1000 elements
- Recharts, Nivo, visx, Victory: All hit same wall
- DOM overhead (layout, paint, GC) becomes prohibitive
- Solution: Switch to Canvas (Chart.js, ECharts)
Canvas scales to ~10,000 elements
- Chart.js: 10K points at 12 FPS (acceptable)
- ECharts: 10K points at 60 FPS (smooth)
- Trade-off: Lose DOM access, harder accessibility
WebGL handles millions
- ECharts-GL: 10M+ points smoothly
- GPU-accelerated geometry
- Complex setup, debugging harder
2. API Paradigm Affects Developer Productivity#
Declarative (Recharts, visx, Nivo) = Faster for React
// 5 lines, clear intent
<LineChart data={data}>
<Line dataKey="value" />
</LineChart>Imperative (Chart.js, ECharts) = More powerful
// More verbose, but handles edge cases
chart.setOption({
series: [{ type: 'line', data: [...] }]
})Low-level (D3, visx) = Maximum control
// 20+ lines, but pixel-perfect
<svg>
{data.map(d => <circle cx={scale(d.x)} />)}
</svg>Recommendation: Match paradigm to team skillset and project needs.
3. Bundle Size vs Features Trade-off#
Minimal (Chart.js: 60 KB) → Few features, great performance Medium (Recharts: 130 KB) → Good balance for dashboards Large (ECharts: 150-320 KB) → Enterprise features justify size
Tree-shaking winners:
- visx: Excellent (30-60 KB for custom builds)
- D3: Excellent (15-50 KB for custom builds)
- ECharts: Moderate (150 KB minimum)
4. TypeScript Coverage is Uniformly Excellent#
All modern libraries ship with types or have high-quality @types/* packages.
Exception: Victory has partial coverage.
5. Accessibility Requires SVG Rendering#
SVG libraries (Recharts, D3, visx) → Screen readers can parse Canvas libraries (Chart.js, ECharts) → Need ARIA workarounds
Best accessibility: Victory (built-in ARIA), ECharts SVG mode
6. React Integration Patterns#
Pattern 1: Library owns DOM (Recharts, Nivo)
- Easiest, least flexible
Pattern 2: D3 math, React rendering (visx)
- More code, full control
Pattern 3: D3 controls DOM (anti-pattern)
- Don’t do this in React
7. Animation Systems Vary Widely#
Built-in (Recharts, Chart.js, ECharts, Victory)
- Convenient, limited customization
Bring your own (visx)
- Pair with react-spring or framer-motion
- More work, smoother results
D3 transitions
- Most powerful, but imperative API
Decision Matrix by Technical Requirements#
Performance Requirements#
< 1000 points:
- Any SVG library works (Recharts, visx, Nivo)
- Choose based on API preference
1000-10,000 points:
- Chart.js (simple) or ECharts (features)
10,000+ points:
- ECharts (Canvas or WebGL)
- Consider data decimation/sampling
Bundle Size Constraints#
< 100 KB budget:
- Chart.js (60 KB) or visx (30-60 KB)
100-200 KB budget:
- Recharts (130 KB) or ECharts tree-shaken (150 KB)
No budget constraint:
- ECharts full (320 KB) for maximum features
Customization Needs#
Standard charts only:
- Recharts (React) or Chart.js (agnostic)
Pixel-perfect control:
- visx (React) or D3 (agnostic)
Novel visualizations:
- D3 only (or visx for React)
Framework Constraints#
React-only:
- Recharts, visx, Nivo (declarative)
- Chart.js, ECharts (wrappers)
Framework-agnostic:
- Chart.js, ECharts, D3
React Native:
- Victory (only option)
Accessibility Requirements#
Critical:
- Victory (best built-in support)
- ECharts SVG mode (good ARIA)
- Avoid Canvas-only libraries
Nice-to-have:
- Any SVG library + manual ARIA
SSR Requirements#
Must work:
- Nivo (designed for SSR)
- visx (pure SVG, no issues)
- D3 (can generate server-side)
Problematic:
- Chart.js (Canvas requires browser)
- Recharts (hydration issues)
Architecture Recommendations#
For Standard React Dashboards#
Stack:
- Recharts (< 1K points) or ECharts (> 1K points)
- TypeScript for type safety
- Visual regression testing (Chromatic, Percy)
Why:
- Recharts: Easiest React API, covers 80% of cases
- ECharts: Handles performance edge cases
- TypeScript: Catches data shape errors early
- Visual regression: Charts are visual, test them visually
For Custom React Visualizations#
Stack:
- visx for React primitives
- D3 scales and shape generators
- react-spring for animations
- Memoize scales to prevent re-renders
Why:
- visx: Best of D3 power + React patterns
- react-spring: Smoothest animations
- Memoization: Critical for performance with low-level APIs
For High-Performance Dashboards#
Stack:
- ECharts with Canvas renderer
- Data decimation/sampling
- Virtual scrolling (data zoom)
- WebGL for > 100K points
Why:
- ECharts: Only library handling massive datasets
- Decimation: Preserve visual shape, gain 10x performance
- Virtual scrolling: Render only visible data
- WebGL: GPU acceleration for millions of points
For Framework-Agnostic Libraries#
Stack:
- Chart.js for simplicity
- ECharts for features
- D3 for custom work
Why:
- All three work in any framework or vanilla JS
- Chart.js: Smallest, easiest
- ECharts: Most features
- D3: Most powerful
Technical Red Flags to Avoid#
1. D3 Controlling DOM in React#
// ❌ DON'T
useEffect(() => {
d3.select(ref.current).selectAll('rect').data(data).join('rect')
}, [data])React doesn’t know about D3’s DOM changes = hydration errors, race conditions.
2. Not Memoizing Scales#
// ❌ DON'T
function Chart({ data }) {
const scale = scaleLinear() // New scale every render!
return <LinePath x={d => scale(d.x)} />
}
// ✅ DO
const scale = useMemo(() => scaleLinear(), [width])3. Rendering Too Many SVG Elements#
// ❌ DON'T (10K circles)
{data.map(d => <circle cx={d.x} cy={d.y} />)}
// ✅ DO (sample or use Canvas)
{sampleData(data, 1000).map(d => <circle />)}4. Blocking Animations#
// ❌ DON'T (blocks main thread)
for (let i = 0; i < 1000; i++) {
chart.update() // Synchronous update
}
// ✅ DO (use requestAnimationFrame)
chart.update() // Library handles async
5. Not Cleaning Up Event Listeners#
// ❌ DON'T
useEffect(() => {
element.addEventListener('mousemove', handler)
// Missing cleanup!
}, [])
// ✅ DO
useEffect(() => {
element.addEventListener('mousemove', handler)
return () => element.removeEventListener('mousemove', handler)
}, [])Final Recommendation#
Default choice for most teams: Recharts
- React-idiomatic, well-documented, covers common cases
- Switch to ECharts only when hitting performance limits
Advanced teams: visx
- Full D3 power in React
- Worth the learning curve for custom work
Enterprise dashboards: ECharts
- Handles massive datasets
- Rich feature set justifies bundle size
Framework-agnostic: Chart.js
- Smallest, simplest, works everywhere
- Upgrade to ECharts if you need more chart types
Novel visualizations: D3
- Only tool that can create anything
- Use visx wrapper if in React
Next steps after S2:
- S3: Identify specific use cases and user personas
- S4: Evaluate long-term ecosystem viability
visx - Technical Architecture#
Core Philosophy#
visx (from Airbnb) provides low-level React primitives for building custom visualizations. It’s the bridge between D3’s power and React’s declarative model.
// visx: D3 for math, React for rendering
import { scaleLinear } from '@visx/scale'
import { LinePath } from '@visx/shape'
const xScale = scaleLinear({ domain: [0, 100], range: [0, width] })
const yScale = scaleLinear({ domain: [0, 100], range: [height, 0] })
<LinePath
data={data}
x={d => xScale(d.x)}
y={d => yScale(d.y)}
/>Key insight: visx is not a charting library - it’s a component library for building charts.
Architecture#
Package Structure#
visx is modular - 30+ independent packages:
import { scaleLinear } from '@visx/scale'
import { AxisBottom, AxisLeft } from '@visx/axis'
import { LinePath, Bar } from '@visx/shape'
import { Group } from '@visx/group'
import { GridRows, GridColumns } from '@visx/grid'
import { localPoint } from '@visx/event'
import { useTooltip } from '@visx/tooltip'Core Packages#
Scales (@visx/scale)
- Wrappers around D3 scales
- Same API as D3:
scaleLinear,scaleBand,scaleTime, etc.
Shapes (@visx/shape)
- React components for SVG elements
LinePath,Bar,AreaClosed,Pie,Arc,Circle
Axes (@visx/axis)
AxisBottom,AxisTop,AxisLeft,AxisRight- Customizable tick formatting, orientation
Grid (@visx/grid)
GridRows,GridColumns- Background grid lines
Tooltip (@visx/tooltip)
useTooltiphook,Tooltipcomponent,TooltipWithBounds
Event (@visx/event)
localPoint- Get mouse coordinates relative to SVG- Touch event helpers
Responsive (@visx/responsive)
ParentSize- Measure parent containerScaleSVG- Scale SVG to fit
The visx Pattern#
Build, Don’t Configure#
Unlike Recharts (declarative) or Chart.js (config), visx is compositional:
function LineChart({ data, width, height }) {
// 1. Create scales
const xScale = scaleLinear({
domain: [0, data.length - 1],
range: [0, width]
})
const yScale = scaleLinear({
domain: [0, Math.max(...data.map(d => d.value))],
range: [height, 0]
})
return (
<svg width={width} height={height}>
{/* 2. Render grid */}
<GridRows scale={yScale} width={width} />
<GridColumns scale={xScale} height={height} />
{/* 3. Render axes */}
<AxisBottom top={height} scale={xScale} />
<AxisLeft scale={yScale} />
{/* 4. Render data */}
<LinePath
data={data}
x={(d, i) => xScale(i)}
y={d => yScale(d.value)}
stroke="#000"
/>
</svg>
)
}Pros:
- Full control over every element
- Can create any visualization
- React-idiomatic (components, hooks)
Cons:
- More code than Recharts
- No built-in interactivity
- Steeper learning curve
Component API#
LinePath#
import { LinePath } from '@visx/shape'
import { curveMonotoneX } from '@visx/curve'
<LinePath
data={data}
x={d => xScale(d.x)} // Accessor function
y={d => yScale(d.y)}
stroke="blue"
strokeWidth={2}
curve={curveMonotoneX} // D3 curve algorithm
/>Bar#
import { Bar } from '@visx/shape'
data.map((d, i) => (
<Bar
key={i}
x={xScale(i)}
y={yScale(d.value)}
width={xScale.bandwidth()}
height={height - yScale(d.value)}
fill="steelblue"
onClick={() => console.log(d)}
/>
))AreaClosed#
import { AreaClosed } from '@visx/shape'
<AreaClosed
data={data}
x={d => xScale(d.x)}
y={d => yScale(d.y)}
yScale={yScale} // Used for baseline
fill="url(#gradient)"
curve={curveBasis}
/>Pie#
import { Pie } from '@visx/shape'
<Pie
data={data}
pieValue={d => d.value}
outerRadius={100}
innerRadius={50} // 0 = pie, > 0 = donut
>
{pie => {
return pie.arcs.map((arc, i) => (
<g key={i}>
<path d={pie.path(arc)} fill={colors[i]} />
<text {...pie.getArcLabel(arc)}>
{arc.data.label}
</text>
</g>
))
}}
</Pie>Scales (D3 Wrappers)#
visx re-exports D3 scales with better defaults:
import { scaleLinear, scaleBand, scaleTime, scaleOrdinal } from '@visx/scale'
// Linear scale
const yScale = scaleLinear({
domain: [0, 100],
range: [height, 0],
nice: true, // Round domain to nice numbers
clamp: true // Clamp output to range
})
// Band scale (for bar charts)
const xScale = scaleBand({
domain: categories,
range: [0, width],
padding: 0.2 // 20% padding between bars
})
// Time scale
const timeScale = scaleTime({
domain: [startDate, endDate],
range: [0, width]
})
// Ordinal scale (for colors)
const colorScale = scaleOrdinal({
domain: ['A', 'B', 'C'],
range: ['red', 'green', 'blue']
})Responsive Patterns#
ParentSize#
import { ParentSize } from '@visx/responsive'
function ResponsiveChart() {
return (
<ParentSize>
{({ width, height }) => (
<LineChart width={width} height={height} />
)}
</ParentSize>
)
}Implementation:
- Measures parent container using
ResizeObserver - Passes dimensions to child render function
- Re-renders on resize
ScaleSVG#
import { ScaleSVG } from '@visx/responsive'
<ScaleSVG width={400} height={300}>
<LineChart width={400} height={300} />
</ScaleSVG>Scales SVG to fit container while maintaining aspect ratio.
Tooltip Pattern#
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
import { localPoint } from '@visx/event'
function Chart() {
const {
showTooltip,
hideTooltip,
tooltipData,
tooltipLeft,
tooltipTop
} = useTooltip()
const handleMouseMove = (event) => {
const point = localPoint(event)
const x = xScale.invert(point.x) // Convert pixels → data
const y = yScale.invert(point.y)
showTooltip({
tooltipData: { x, y },
tooltipLeft: point.x,
tooltipTop: point.y
})
}
return (
<>
<svg onMouseMove={handleMouseMove} onMouseLeave={hideTooltip}>
{/* Chart elements */}
</svg>
{tooltipData && (
<TooltipWithBounds left={tooltipLeft} top={tooltipTop}>
x: {tooltipData.x}, y: {tooltipData.y}
</TooltipWithBounds>
)}
</>
)
}Animation#
visx doesn’t include animations - integrate with React animation libraries:
react-spring#
import { useSpring, animated } from 'react-spring'
function AnimatedBar({ x, y, width, height }) {
const props = useSpring({
from: { height: 0 },
to: { height }
})
return (
<animated.rect
x={x}
y={props.height.to(h => y + height - h)}
width={width}
height={props.height}
/>
)
}react-transition-group#
import { TransitionGroup, CSSTransition } from 'react-transition-group'
<TransitionGroup component="g">
{data.map(d => (
<CSSTransition key={d.id} timeout={500} classNames="bar">
<Bar ... />
</CSSTransition>
))}
</TransitionGroup>Performance#
visx renders SVG - same performance as Recharts:
| Data Points | Render Time |
|---|---|
| 100 | ~5ms |
| 500 | ~20ms |
| 1000 | ~50ms |
| 2000 | ~120ms (choppy) |
Performance ceiling: ~1000 SVG elements
Optimization: Memoization#
import { useMemo } from 'react'
const xScale = useMemo(
() => scaleLinear({ domain: [0, 100], range: [0, width] }),
[width] // Recreate only when width changes
)
const pathData = useMemo(
() => data.map(d => ({ x: xScale(d.x), y: yScale(d.y) })),
[data, xScale, yScale]
)Bundle Size#
@visx/scale: 85 KB → 25 KB gzipped
@visx/shape: 45 KB → 14 KB gzipped
@visx/axis: 30 KB → 9 KB gzipped
@visx/tooltip: 15 KB → 5 KB gzipped
Typical bundle (for basic chart): ~60 KB gzippedComparison:
- visx (basic chart): 60 KB gzipped
- Recharts: 130 KB gzipped ✓ (visx is smaller)
- Chart.js: 60 KB gzipped (same)
visx is lighter because you only import what you need.
TypeScript Support#
Type Coverage: Excellent (ships with types)
import { scaleLinear, ScaleLinear } from '@visx/scale'
import { LinePath } from '@visx/shape'
interface DataPoint {
x: number
y: number
}
const xScale: ScaleLinear<number, number> = scaleLinear({
domain: [0, 100],
range: [0, width]
})
<LinePath<DataPoint>
data={data}
x={d => xScale(d.x)} // d is typed as DataPoint
y={d => yScale(d.y)}
/>Accessibility#
visx renders SVG - inherently more accessible than Canvas:
<svg aria-label="Sales trend over time">
<g aria-label="X axis">
<AxisBottom ... />
</g>
<g aria-label="Sales data">
<LinePath ... />
</g>
</svg>Best practices:
- Add ARIA labels to chart sections
- Include alt text for key insights
- Use semantic grouping (
<g>elements) - Ensure color contrast
Use Cases#
Custom Dashboards#
visx excels when you need pixel-perfect control:
// Recharts: limited customization
<LineChart><Line /></LineChart>
// visx: full control
<svg>
<defs>
<linearGradient id="gradient">...</linearGradient>
</defs>
<GridRows />
<LinePath fill="url(#gradient)" />
<circle cx={x} cy={y} r={5} /> {/* Custom marker */}
<text x={x} y={y}>Custom label</text>
</svg>Unusual Chart Types#
visx can create anything D3 can, but in React:
- Heatmaps
- Network graphs (with force simulation)
- Hierarchical visualizations (trees, treemaps)
- Geographic maps
- Custom statistical plots
Learning D3 in React#
visx is a gateway to D3:
- Scales work like D3
- Shapes map to D3 shape generators
- Easier than raw D3 (no
.join(),.enter(),.exit()) - React lifecycle instead of D3 selections
Key Strengths#
- React-native - Components, not config objects
- Flexible - Build any visualization
- Modular - Tree-shakeable, small bundle
- Type-safe - Excellent TypeScript support
- D3 power - All D3 capabilities available
Key Limitations#
- More code - Requires building charts from scratch
- No built-in interactivity - Must implement tooltips, legends, etc.
- Steeper learning curve - Need D3 knowledge
- SVG performance - Same 1000-point ceiling as Recharts
When to Use visx#
Ideal for:
- Custom, pixel-perfect visualizations
- Teams comfortable with D3
- React projects prioritizing flexibility
- Learning D3 in a React context
Not ideal for:
- Standard charts (use Recharts)
- Tight deadlines (use Recharts)
- Large datasets (use ECharts)
- Teams new to D3 (use Recharts)
visx vs Recharts#
| Aspect | visx | Recharts |
|---|---|---|
| Abstraction | Low-level primitives | High-level components |
| Code | More verbose | More concise |
| Flexibility | Maximum | Limited |
| Learning curve | Steeper | Gentler |
| Bundle size | 60 KB | 130 KB |
| Built-in features | Minimal | Tooltips, legends, etc. |
| Best for | Custom charts | Standard charts |
Rule of thumb:
- Standard dashboard → Recharts
- Custom visualization → visx
S3: Need-Driven
S3-need-driven: Use Cases and User Personas#
Focus#
This pass identifies WHO needs data visualization libraries and WHY they need them. While S1 answered “WHICH library?” and S2 answered “HOW does it work?”, S3 answers “WHO is this for?”
Analysis Framework#
For each use case, we examine:
1. User Persona#
- Role and experience level
- Technical background
- Team constraints
- Business context
2. Core Needs#
- Primary visualization goals
- Data characteristics (size, update frequency)
- Performance requirements
- Customization needs
3. Pain Points#
- Current challenges without a library
- Technical blockers
- Time/budget constraints
4. Success Criteria#
- What constitutes success for this persona
- Key metrics (development speed, performance, maintainability)
5. Recommended Solution#
- Which library best fits their needs
- Why it’s the right choice
- What trade-offs they accept
User Personas Identified#
- React Dashboard Developers - Building internal analytics tools
- Data Scientists - Visualizing large datasets
- Custom Visualization Designers - Creating novel, branded charts
- Mobile App Developers - Cross-platform React Native charts
- Full-Stack Developers - SSR-first web applications
Key Insights from S3#
Different Users, Different Priorities#
Dashboard developers prioritize:
- Fast development (time to first chart)
- React integration (JSX components)
- Standard chart types (line, bar, pie)
- Minimal customization needed
→ Solution: Recharts, Nivo
Data scientists prioritize:
- Performance with large datasets
- Interactive exploration (zoom, pan)
- Many chart types (heatmaps, 3D)
- Framework-agnostic (not tied to React)
→ Solution: ECharts, D3
Designers prioritize:
- Pixel-perfect control
- Custom animations
- Brand consistency
- Unique visualizations
→ Solution: D3, visx
Experience Level Matters#
Beginner developers:
- Need quick wins
- Prefer declarative APIs
- Want built-in features (tooltips, legends)
→ Solution: Recharts, Chart.js
Advanced developers:
- Comfortable with complexity
- Want full control
- Can build features themselves
→ Solution: D3, visx
Team Constraints Shape Decisions#
Small teams:
- Limited maintenance capacity
- Need stable, well-documented libraries
- Avoid custom code
→ Solution: Recharts, Chart.js (mature, stable)
Large teams:
- Can handle complex libraries
- Benefit from flexibility
- Have time for custom work
→ Solution: D3, visx (worth the investment)
Use Case Categories#
1. Internal Tools#
- Analytics dashboards, admin panels
- Standard charts, known data shapes
- Internal users (forgiving of rough edges)
2. Customer-Facing Products#
- Marketing sites, SaaS dashboards
- Must be polished, branded, performant
- External users (high quality bar)
3. Data Exploration Tools#
- Research platforms, BI tools
- Large datasets, unknown data shapes
- Power users (expect rich interactivity)
4. Mobile Applications#
- React Native apps
- Touch interactions, offline support
- Resource-constrained devices
5. Content Sites#
- Blogs, news, documentation
- SEO critical, fast initial load
- Server-side rendering
Common Anti-Patterns#
1. Choosing D3 for Standard Charts#
Problem: Team picks D3 because “it’s the most powerful” Reality: Spends weeks building what Recharts provides in minutes Solution: Use D3 only when you need custom visualizations
2. Premature Optimization#
Problem: Choose ECharts “in case we get big data later” Reality: Pay bundle size cost for features never used Solution: Start simple (Recharts), upgrade when needed
3. Ignoring Team Skills#
Problem: Pick visx when team doesn’t know D3 Reality: Steep learning curve slows development Solution: Match library complexity to team experience
4. Not Considering Mobile#
Problem: Build desktop-only charts Reality: 50%+ users on mobile, charts don’t work Solution: Test responsiveness early, consider touch interactions
5. Accessibility Afterthought#
Problem: Pick Canvas library, add ARIA labels later Reality: Hard to retrofit accessibility Solution: Use SVG libraries if accessibility matters
See Also#
Individual use cases in this directory provide detailed personas and recommendations.
S3 Recommendation: Matching Libraries to User Needs#
Key Insight: One Size Does NOT Fit All#
The “best” data visualization library depends entirely on who you are and what you need.
The Anti-Pattern: Choosing by Popularity#
Common mistake:
- See Recharts has 9M downloads
- Assume “most popular = best for me”
- Pick Recharts without evaluating needs
Reality:
- Recharts great for React dashboards (< 1K points)
- Terrible for 100K point datasets (use ECharts)
- Terrible for custom charts (use visx or D3)
Lesson: Match library to your specific needs, not just popularity.
Decision Tree by User Type#
Are you building a React dashboard?#
YES → Continue to React decision tree NO → Jump to framework-agnostic options
React Dashboard Decision Tree#
1. Data size?
- < 1000 points → Standard charts? (YES: Recharts, NO: visx)
- 1000-10K points → ECharts (Canvas)
- 10K+ points → ECharts (WebGL)
2. Standard vs Custom?
- Standard charts (line, bar, pie) → Recharts
- Custom designs → visx
- Novel visualizations → D3
3. Team experience?
- Junior team → Recharts (easier)
- Senior team + D3 knowledge → visx (more control)
4. SSR required?
- YES → Nivo (best SSR), visx (good SSR)
- NO → Recharts (simpler)
Are you building a React Native app?#
YES → Victory Native (only viable option)
Are you framework-agnostic or using Vue/Angular?#
1. Data size?
- < 10K points → Chart.js (simplest, smallest)
- 10K+ points → ECharts (performance)
2. Feature richness?
- Need 100+ chart types → ECharts
- Standard charts sufficient → Chart.js
3. Custom visualizations?
- YES → D3 (maximum power)
- NO → Chart.js or ECharts
User Persona Summary#
| Persona | Primary Need | Recommended Library | Why |
|---|---|---|---|
| React Dashboard Developer | Fast development, standard charts | Recharts | JSX API, built-in features, 9M downloads |
| Data Scientist | Large datasets, interactivity | ECharts | 100K+ points, Canvas/WebGL, data zoom |
| Custom Designer | Pixel-perfect, unique charts | visx | D3 + React, full control, small bundle |
| Mobile Developer | React Native, touch gestures | Victory Native | Native support, touch interactions |
| Full-Stack (SSR) | Server-side rendering | Nivo | Built for SSR, beautiful defaults |
| Agency (Multi-framework) | Works everywhere | Chart.js | 60 KB, framework-agnostic, simple |
| Data Journalist | Custom storytelling | D3 | Novel visualizations, maximum flexibility |
Common Scenarios#
Scenario 1: Internal Analytics Dashboard#
Context: Small startup, internal tools, 2 frontend developers
Needs:
- Quick development (ship weekly)
- Standard charts (revenue, users, funnels)
- < 1000 data points
- React stack
Recommendation: Recharts
Why:
- Fastest time to first chart (< 1 hour)
- JSX feels natural to React developers
- Built-in tooltips, legends (don’t rebuild)
- 9M downloads = mature, stable
Not ECharts: Overkill for small datasets, config API slower to learn Not D3/visx: Too much code for standard charts
Scenario 2: Real-Time Monitoring Dashboard#
Context: DevOps team, monitoring metrics, 10K-100K data points
Needs:
- Handle 10K+ points smoothly
- Real-time updates (every second)
- Zoom/pan (explore time windows)
- Framework-agnostic (Vue stack)
Recommendation: ECharts
Why:
- Canvas rendering handles 100K points
- Built-in data zoom, pan controls
- Progressive rendering for real-time
- Works in any framework
Not Recharts: SVG chokes at 2K points Not Chart.js: Ceiling at 10K points, missing features
Scenario 3: Marketing Website Infographic#
Context: Design agency, client work, custom branded visualization
Needs:
- Pixel-perfect match to Figma design
- Custom animations (staggered entry)
- Unique chart type (not standard)
- React stack
Recommendation: visx + react-spring
Why:
- Full control over every pixel
- D3 scales (don’t reimplement math)
- React components (not DOM mutations)
- react-spring for smooth animations
Not Recharts: Can’t match custom designs Not D3 alone: Imperative API conflicts with React
Scenario 4: Fitness Tracking Mobile App#
Context: Solo mobile developer, React Native, iOS + Android
Needs:
- React Native compatibility
- Touch-optimized (no hover)
- Offline-capable
- Cross-platform (single codebase)
Recommendation: Victory Native
Why:
- Built for React Native (not web port)
- Touch gestures built-in
- Works offline (no web dependencies)
- iOS + Android support
Not Recharts: Web-only, doesn’t work in RN Not Chart.js: Requires Canvas polyfill
Scenario 5: Scientific Research Platform#
Context: Research team, Python + web frontend, large datasets
Needs:
- 100K-1M data points (sensor data)
- 3D visualizations (parameter spaces)
- Export charts (publications)
- Framework-agnostic (minimal JS)
Recommendation: ECharts + echarts-gl
Why:
- WebGL handles 1M+ points
- 3D charts built-in (scatter3D, surface3D)
- Export to PNG (publication-ready)
- Simple JavaScript (minimal learning)
Not Plotly: Performance ceiling at 50K points Not D3: Too much JavaScript expertise required
Red Flags: When You’ve Chosen Wrong#
You picked Recharts, but…#
Red flag 1: Charts lag with 2K points → Fix: Switch to ECharts (Canvas)
Red flag 2: Design team wants custom aesthetics → Fix: Switch to visx (more control)
Red flag 3: Need React Native version → Fix: Switch to Victory Native
You picked D3, but…#
Red flag 1: Spending weeks on standard bar chart → Fix: Use Recharts for standard charts, D3 for custom
Red flag 2: Hydration errors in React → Fix: Use visx (D3 math, React rendering)
Red flag 3: Junior team struggling with API → Fix: Use Recharts (gentler learning curve)
You picked ECharts, but…#
Red flag 1: Only have 500 data points → Fix: Use Recharts or Chart.js (simpler, smaller)
Red flag 2: Bundle size is critical (mobile) → Fix: Use Chart.js (60 KB vs 150 KB)
Red flag 3: Need pixel-perfect custom design → Fix: Use D3 or visx (more control)
Migration Paths#
From Recharts to ECharts (performance)#
When: Hitting 1K+ point performance wall
Effort: 1-2 weeks (API change: JSX → config)
Strategy:
- Migrate one chart at a time
- Run both libraries temporarily
- Use ECharts only for large datasets
From Recharts to visx (customization)#
When: Design requirements exceed Recharts capabilities
Effort: 2-4 weeks (learning D3 + visx)
Strategy:
- Keep Recharts for standard charts
- Use visx only for custom visualizations
- Share reusable visx components
From vanilla D3 to visx (React integration)#
When: D3 DOM mutations causing React bugs
Effort: 1-2 weeks (same D3 concepts, different rendering)
Strategy:
- Keep D3 scales and math
- Replace
.select().join()with JSX - Migrate chart by chart
Final Recommendations by Priority#
Priority 1: Match Your Data Size#
| Data Points | Library |
|---|---|
| < 1000 | Any SVG library (Recharts, visx, Nivo) |
| 1K-10K | Canvas library (Chart.js, ECharts) |
| 10K+ | ECharts (Canvas or WebGL) |
Most common mistake: Using SVG for large datasets.
Priority 2: Match Your Framework#
| Framework | Library |
|---|---|
| React (standard charts) | Recharts |
| React (custom charts) | visx |
| React Native | Victory Native |
| Vue/Angular/None | Chart.js or ECharts |
Most common mistake: Fighting framework integration.
Priority 3: Match Your Team Skills#
| Experience | Library |
|---|---|
| Junior (< 2 years) | Recharts, Chart.js |
| Mid (2-5 years) | ECharts, Nivo |
| Senior (5+ years) | visx, D3 |
Most common mistake: Picking advanced library for junior team.
Priority 4: Match Your Timeline#
| Timeline | Library |
|---|---|
| Days | Recharts, Chart.js |
| Weeks | ECharts, Nivo, Victory |
| Months | visx, D3 |
Most common mistake: Picking D3 with tight deadline.
Conclusion#
The “best” library is the one that:
- Handles your data size
- Works in your framework
- Matches your team’s skills
- Fits your timeline
- Meets your customization needs
Default recommendations:
- React dashboard: Recharts (until you hit limits)
- Large datasets: ECharts (performance matters)
- Custom designs: visx (control matters)
- Mobile: Victory Native (only choice)
- Framework-agnostic: Chart.js (simplest)
When in doubt: Start simple (Recharts, Chart.js), upgrade when needed.
Next step: S4 (strategic analysis) evaluates long-term viability and ecosystem health.
Use Case: Custom Visualization Designer#
Who Needs This#
Persona: Maya Rodriguez, Senior Frontend Engineer at Design Agency
Background:
- 8 years experience, React + D3 expertise
- Builds bespoke data visualizations for client websites
- Works with designers on pixel-perfect implementations
- Portfolio-focused (unique work, not templates)
Technical Context:
- React + TypeScript
- Figma designs from design team (precise specs)
- Client sites (brand-focused, custom aesthetics)
- < 1000 data points (storytelling, not big data)
Why They Need It#
Core Need#
Create unique, brand-aligned data visualizations that match exact design specifications.
Visualization requirements:
- Custom chart types (not standard bar/line/pie)
- Pixel-perfect match to Figma designs
- Complex animations (staggered, choreographed)
- Unusual interactions (drag, scroll-driven)
- Brand-specific aesthetics (custom colors, fonts, shapes)
Pain Points Without a Library#
1. High-level libraries too limiting
- Recharts: Can’t match custom designs
- Chart.js: Opinionated styling, hard to override
- Result: Fighting the library, not creating
2. Building from scratch too slow
- Implementing scales: 2-3 days
- Handling edge cases: 1 week
- Browser testing: 3-4 days
- Result: 2-3 weeks per custom chart
3. D3 alone doesn’t fit React
- D3 mutations conflict with React
.select().data().join()pattern awkward- Mixing imperative + declarative code
- Result: Bugs, hydration errors, complexity
4. Design-dev handoff friction
- Designers specify exact pixels
- Pre-built charts approximate
- Result: Endless back-and-forth
What Success Looks Like#
Design fidelity:
- Pixel-perfect match to Figma (< 2px tolerance)
- Custom animations as specified
- Exact brand colors, fonts, spacing
Development speed:
- First custom chart: 2-3 days
- Iteration on design: < 2 hours
- Additional similar charts: 1 day
Code quality:
- React-idiomatic (declarative, not imperative)
- Reusable components (not one-offs)
- Maintainable (client can update data)
Requirements Analysis#
Must-Have#
- Full control over SVG elements
- D3 scales and shape generators (not reimplementing)
- React component API (JSX, not DOM manipulation)
- Custom animation support
- TypeScript support
- Small bundle (tree-shakeable)
Nice-to-Have#
- Pre-built axis components (customizable)
- Tooltip primitives (to extend)
- Responsive helpers
Don’t Need#
- Built-in chart types (building custom)
- Large dataset support (< 1000 points)
- Canvas rendering (SVG is fine)
- Framework-agnostic (React-specific is fine)
Library Evaluation#
Option 1: visx ⭐ RECOMMENDED#
Pros:
- ✅ Low-level React primitives (full control)
- ✅ D3 scales + shape generators (don’t reimplement)
- ✅ Declarative API (JSX, not DOM mutations)
- ✅ Modular (tree-shakeable, 30-60 KB)
- ✅ TypeScript first-class
- ✅ From Airbnb (well-maintained)
Cons:
- ❌ No built-in tooltips/legends (must build)
- ❌ More code than Recharts
- ❌ D3 knowledge required
Code example:
import { scaleLinear, scaleBand } from '@visx/scale'
import { Bar } from '@visx/shape'
import { AxisBottom, AxisLeft } from '@visx/axis'
import { GridRows } from '@visx/grid'
interface DataPoint {
category: string
value: number
}
function CustomBarChart({ data, width, height }: Props) {
// 1. Create scales (D3 math)
const xScale = scaleBand<string>({
domain: data.map(d => d.category),
range: [0, width],
padding: 0.3
})
const yScale = scaleLinear<number>({
domain: [0, Math.max(...data.map(d => d.value))],
range: [height, 0],
nice: true
})
return (
<svg width={width} height={height}>
{/* Custom gradient (exact brand colors) */}
<defs>
<linearGradient id="brandGradient">
<stop offset="0%" stopColor="#FF6B6B" />
<stop offset="100%" stopColor="#4ECDC4" />
</linearGradient>
</defs>
{/* Grid (customized) */}
<GridRows scale={yScale} width={width} stroke="#E0E0E0" strokeDasharray="4,4" />
{/* Bars (full control) */}
{data.map((d, i) => (
<Bar
key={i}
x={xScale(d.category)}
y={yScale(d.value)}
width={xScale.bandwidth()}
height={height - yScale(d.value)}
fill="url(#brandGradient)"
rx={8} // Rounded corners (per design)
opacity={0.9}
onMouseEnter={() => {/* Custom hover */}}
/>
))}
{/* Axes (customized) */}
<AxisBottom
top={height}
scale={xScale}
tickLabelProps={() => ({
fontFamily: 'Brand Font',
fontSize: 14,
fill: '#333'
})}
/>
<AxisLeft scale={yScale} />
</svg>
)
}Why it’s perfect:
- D3 scales: Don’t reimplement math
- React components: Declarative, fits React model
- Full control: Every pixel customizable
- TypeScript: Catch errors early
Time to first chart: 1-2 days (learning visx + implementing design)
Option 2: D3 (Raw)#
Pros:
- ✅ Maximum power
- ✅ Can do anything
Cons:
- ❌ Imperative API (
.select().join()) - ❌ Conflicts with React (DOM mutations)
- ❌ Hydration errors
- ❌ More complex
Code example:
useEffect(() => {
d3.select(svgRef.current)
.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => xScale(d.category))
// D3 controls DOM, React unaware
}, [data])Why not chosen: Anti-pattern in React, causes bugs.
Option 3: Recharts#
Pros:
- ✅ Fast development
- ✅ Built-in features
Cons:
- ❌ Can’t match custom designs
- ❌ Limited styling hooks
- ❌ Opinionated component structure
Why not chosen: Not flexible enough for pixel-perfect designs.
Option 4: Nivo#
Pros:
- ✅ Beautiful defaults
- ✅ React components
Cons:
- ❌ Higher-level than visx (less control)
- ❌ Harder to customize deeply
Why not chosen: Middle ground between Recharts and visx, but visx offers more control.
Recommended Solution#
Library: visx + react-spring (for animations)
Why:
- Full control - Every SVG element customizable
- React-native - JSX, not D3 DOM mutations
- D3 power - Scales and shapes without reimplementing
- Small bundle - Tree-shakeable (30-60 KB)
- TypeScript - Type-safe custom components
Trade-offs accepted:
- More code than Recharts - Worth it for control
- Build tooltips ourselves - Enables custom designs
- D3 knowledge required - Team has expertise
Implementation Plan#
Project: Custom Annual Report Visualizations
Week 1: Foundation
- Set up visx + react-spring
- Create reusable scale utilities
- Build custom tooltip primitive
- Responsive container helper
Week 2: Chart 1 (Custom Spiral Timeline)
- Implement spiral layout algorithm
- Map data to spiral coordinates (visx scales)
- Animated entry (react-spring)
- Hover interactions
Week 3: Chart 2 (Radial Progress Comparison)
- Radial layout (not built-in)
- Custom arcs (visx shape generators)
- Staggered animation
- Click interactions
Week 4: Chart 3 (Flow Sankey Variant)
- Custom layout algorithm
- Curved paths (visx curves)
- Interactive filtering
- Polish + testing
Expected outcome: 3 unique charts in 4 weeks, vs impossible with Recharts.
Success Metrics#
Design fidelity:
- Pixel-perfect: < 2px deviation ✓
- Animation timing: Matches design specs ✓
- Brand colors: Exact hex values ✓
Performance:
- Bundle size: 45 KB (visx + react-spring)
- Render time: < 16ms (60 FPS)
- Animation smoothness: 60 FPS
Development:
- First chart: 2 days
- Subsequent charts: 1-1.5 days
- Design iterations: < 2 hours
Real-World Example#
Project: Nike Annual Report (Hypothetical)
Requirement: Custom “shoe-shaped” progress chart
With visx:
import { scaleLinear } from '@visx/scale'
import { curveBasis } from '@visx/curve'
import { LinePath } from '@visx/shape'
// Define shoe outline path
const shoeOutline = "M 10,50 Q 30,20 80,30 L 100,50 ..."
// Use visx scales for data positioning
const progressScale = scaleLinear({
domain: [0, 100],
range: [0, shoePathLength]
})
// Fill shoe outline to match progress %
<clipPath id="progressClip">
<rect width={progressScale(progressPercent)} height="100" />
</clipPath>
<path d={shoeOutline} clipPath="url(#progressClip)" fill="#FF6B6B" />Result: Unique, brand-aligned chart that Recharts couldn’t create.
Animation Patterns with visx + react-spring#
import { useSpring, animated } from 'react-spring'
function AnimatedBar({ x, y, width, height }: Props) {
const props = useSpring({
from: { height: 0, opacity: 0 },
to: { height, opacity: 1 },
config: { tension: 280, friction: 60 }
})
return (
<animated.rect
x={x}
y={props.height.to(h => y + height - h)}
width={width}
height={props.height}
opacity={props.opacity}
/>
)
}Lessons Learned#
What worked:
- visx scales: Saved weeks vs manual math
- React-spring: Smoothest animations
- Modular imports: Kept bundle small
- TypeScript: Caught coordinate math errors
What didn’t:
- Initial learning curve: visx + D3 concepts take time
- Tooltip positioning: More complex than expected
Would do differently:
- Build tooltip library earlier (reuse across projects)
- Create design system for visx components
- Document coordinate systems better
Related Personas#
- Data Journalism Team - Custom storytelling visualizations
- Marketing Agency - Brand-specific infographics
- Product Designer - Custom dashboard components
Use Case: Data Scientist with Large Datasets#
Who Needs This#
Persona: Dr. Alex Chen, Research Data Scientist at FinTech Company
Background:
- PhD in Statistics, 5 years industry experience
- Analyzes trading patterns, fraud detection
- Works in Python (Jupyter) + web interface for stakeholders
- Publishes findings to executives monthly
Technical Context:
- Python backend (pandas, NumPy)
- Web frontend for interactive exploration
- Datasets: 10K-1M data points (stock prices, transactions)
- Needs: zoom, pan, filter, export
Why They Need It#
Core Need#
Visualize large time-series datasets for pattern discovery and stakeholder communication.
Chart requirements:
- Time series (stock prices, anomaly detection)
- Heatmaps (correlation matrices)
- Scatter plots (clustering, outliers)
- 3D surface plots (parameter optimization)
- Interactive controls (zoom, pan, brush selection)
Pain Points Without a Library#
1. Performance with large datasets
- Plotting 100K points in Jupyter: slow
- Web visualization of 100K points: browser crashes
- Result: Forced to downsample, lose detail
2. Static vs Interactive trade-off
- Matplotlib/Seaborn: Static images, no interaction
- Plotly: Interactive but slow with 50K+ points
- Result: Can’t explore data fluidly
3. Stakeholder communication
- Executives need web dashboards, not Jupyter notebooks
- Need professional-looking, interactive charts
- Must export to PowerPoint/PDF
4. Framework knowledge gap
- Expert in Python, not JavaScript/React
- Don’t want to learn complex frontend frameworks
- Result: Struggle with web visualization tools
What Success Looks Like#
Performance:
- 100K points: Smooth 60 FPS interaction
- 1M points: Usable (30 FPS) with WebGL
Workflow:
- Analyze in Python → Export data
- Build web dashboard with minimal JS knowledge
- Stakeholders explore interactively
Features:
- Zoom/pan (explore time windows)
- Export charts (PNG, SVG for reports)
- Data zoom slider (virtual scrolling)
- Multiple coordinated views (linked brushing)
Requirements Analysis#
Must-Have#
- Handle 10K-1M data points smoothly
- Canvas or WebGL rendering (not SVG)
- Interactive controls (zoom, pan, brush)
- Export functionality (PNG, PDF)
- Time series optimizations
- Framework-agnostic (or simple wrapper)
Nice-to-Have#
- 3D chart support
- Heatmap support
- Geographic visualizations
- Chinese + English docs (team is multilingual)
Don’t Need#
- React-specific API (not a React expert)
- Mobile responsiveness (desktop tool)
- Server-side rendering
- Tiny bundle size (rich features more important)
Library Evaluation#
Option 1: ECharts ⭐ RECOMMENDED#
Pros:
- ✅ Handles 100K points smoothly (Canvas)
- ✅ WebGL extension for 1M+ points (echarts-gl)
- ✅ Built-in data zoom, pan controls
- ✅ Export to PNG built-in
- ✅ 100+ chart types (heatmaps, 3D, geo)
- ✅ Framework-agnostic (simple JavaScript)
- ✅ Excellent Chinese + English documentation
Cons:
- ❌ 320 KB full bundle (150 KB tree-shaken)
- ❌ Config-driven API (learning curve)
Code example:
const chart = echarts.init(document.getElementById('main'))
chart.setOption({
xAxis: { type: 'time' },
yAxis: { type: 'value' },
// Data zoom for interactive exploration
dataZoom: [
{ type: 'slider', start: 0, end: 10 },
{ type: 'inside' } // Mouse wheel zoom
],
// Toolbox for export
toolbox: {
feature: {
dataZoom: {},
saveAsImage: {} // Export PNG
}
},
series: [{
type: 'line',
data: largeDataset, // 100K points
sampling: 'lttb', // Downsample visually
large: true // Large dataset mode
}]
})Performance:
- 10K points: 60 FPS
- 100K points: 60 FPS (with sampling)
- 1M points: 60 FPS (with WebGL)
Time to first chart: 2-3 hours (learning config structure)
Option 2: Plotly.js#
Pros:
- ✅ Python integration (plotly.py)
- ✅ 40+ chart types (scientific charts)
- ✅ 3D charts built-in
- ✅ WebGL support
Cons:
- ❌ Performance ceiling ~50K points
- ❌ Large bundle (> 3 MB uncompressed)
- ❌ Slower than ECharts at scale
Why not chosen: Performance doesn’t match ECharts for large datasets.
Option 3: Chart.js#
Pros:
- ✅ Handles 10K points smoothly
- ✅ Small bundle (60 KB)
- ✅ Simple API
Cons:
- ❌ Performance ceiling ~10K points
- ❌ Limited chart types (no heatmap, 3D)
- ❌ No built-in data zoom controls
Why not chosen: Can’t handle 100K+ point datasets.
Option 4: D3#
Pros:
- ✅ Maximum flexibility
- ✅ Can use Canvas for performance
Cons:
- ❌ Steep learning curve (JavaScript + D3 API)
- ❌ Must build zoom/pan controls yourself
- ❌ No export functionality built-in
- ❌ Weeks to build what ECharts provides
Why not chosen: Too much JavaScript expertise required.
Recommended Solution#
Library: ECharts (with echarts-gl for 3D)
Why:
- Performance at scale - 100K points smoothly, 1M+ with WebGL
- Built-in controls - Data zoom, pan, export (saves weeks)
- Rich chart library - Heatmaps, 3D, geo maps all built-in
- Framework-agnostic - Minimal JavaScript knowledge needed
- Enterprise backing - Apache Foundation, active development
Trade-offs accepted:
- Bundle size (150-320 KB) - Features justify size
- Config API learning curve - Worth it for built-in features
- Not Python-native - But simple JavaScript integration
Implementation Plan#
Week 1: Setup & Learning
- Install ECharts, echarts-gl
- Build first time series with data zoom
- Test performance with real datasets (100K points)
- Set up export workflow
Week 2: Core Charts
- Time series anomaly detection chart
- Correlation heatmap
- Scatter plot for clustering
- 3D surface for parameter space
Week 3: Dashboard Integration
- Create reusable chart templates
- Wire up to Python data pipeline
- Add responsive container
- Stakeholder review
Week 4: Polish
- Custom theme (match company brand)
- Tooltip customization
- Export to PDF workflow
- Documentation
Expected outcome: Production dashboard in 1 month, vs 3+ months building custom.
Success Metrics#
Performance:
- 100K points: 60 FPS ✓
- 1M points (WebGL): 30+ FPS ✓
- Initial load: < 2 seconds
Workflow:
- Python → JSON → ECharts: Automated pipeline
- Stakeholder self-serve: Interactive exploration
- Report generation: PNG export → PowerPoint
Adoption:
- Executives use dashboard monthly
- Reduced ad-hoc chart requests by 80%
- Faster insight discovery (interactive vs static)
Real-World Example#
Fraud Detection Dashboard:
Dataset: 500K transactions per day
Charts:
- Time series: Transaction volume (500K points, data zoom)
- Heatmap: Fraud score by hour/day
- Scatter: Amount vs velocity (anomaly highlighting)
- Geo map: Transactions by location
Performance:
// Enable large mode for 500K points
series: [{
type: 'scatter',
large: true,
largeThreshold: 2000,
data: transactions, // 500K points
symbolSize: 2
}]Result: Smooth interaction, fraud analysts zoom into suspicious patterns, catch fraud 2 days faster on average.
Lessons Learned#
What worked:
- Data zoom slider: Stakeholders love interactive exploration
- Export PNG: Easy to include charts in reports
- LTTB sampling: Visually identical, 10x faster
What didn’t:
- Initial learning curve: Config structure takes time
- WebGL setup: More complex than expected
Would do differently:
- Start with ECharts examples gallery (copy-paste-modify)
- Use tree-shaking earlier (reduce bundle size)
- Test 3D charts earlier (some were unnecessary)
Related Personas#
- Financial Analyst - Similar needs (time series, large data)
- IoT Engineer - Sensor data visualization (time series, heatmaps)
- Bioinformatics Researcher - Genomic data (large datasets, specialized charts)
Use Case: Mobile App Developer (React Native)#
Who Needs This#
Persona: Jordan Kim, Mobile Developer at Health Tech Startup
Background:
- 4 years React Native experience
- Building fitness tracking app
- Solo mobile developer (small team)
- Shipping on iOS + Android simultaneously
Technical Context:
- React Native 0.72, Expo
- Existing UI: React Native Paper
- Offline-first architecture (local SQLite)
- Health metrics (steps, heart rate, sleep)
Why They Need It#
Core Need#
Display health metrics in mobile fitness app with touch interactions.
Chart requirements:
- Line charts (daily steps, heart rate trends)
- Bar charts (weekly comparisons)
- Progress arcs (goal completion)
- Touch-friendly (finger targets, not mouse precision)
- Works offline (no web dependencies)
- iOS + Android (single codebase)
Pain Points Without a Library#
1. Web charts don’t work in React Native
- Recharts: Uses SVG (Web SVG ≠ React Native SVG)
- Chart.js: Uses Canvas (needs react-native-canvas)
- D3: Requires DOM (React Native has no DOM)
- Result: Most charting libraries unusable
2. React Native SVG differences
// Web SVG
<svg><path d="M 0,0 L 100,100" /></svg>
// React Native SVG
import Svg, { Path } from 'react-native-svg'
<Svg><Path d="M 0,0 L 100,100" /></Svg>Different imports, different APIs.
3. Touch interactions different
- Web: Mouse events (hover, click)
- Mobile: Touch events (press, long-press, swipe)
- Tooltips: Can’t “hover” on touch screens
- Result: Gesture system must be rethought
4. Performance constraints
- Mobile CPUs slower than desktop
- Battery life concerns
- Memory limits tighter
- Result: Need lightweight, efficient charts
What Success Looks Like#
Cross-platform:
- Single codebase for iOS + Android
- Native feel on both platforms
- No platform-specific code
Touch-optimized:
- Large tap targets (44x44 pts minimum)
- Swipe to navigate time periods
- Long-press for details (not hover)
- Haptic feedback
Performance:
- 60 FPS on mid-range phones
- < 50 MB memory footprint
- Minimal battery impact
Requirements Analysis#
Must-Have#
- React Native compatibility (not web charts)
- Touch gestures (pan, zoom, press)
- iOS + Android support
- Offline-capable (no web dependencies)
- Lightweight bundle (< 100 KB)
- Active maintenance
Nice-to-Have#
- Animations (smooth transitions)
- Customization (match app brand)
- TypeScript support
Don’t Need#
- Large datasets (< 1000 points)
- Server-side rendering
- Web browser support
- Accessibility (mobile screen readers less critical for this app)
Library Evaluation#
Option 1: Victory Native ⭐ RECOMMENDED#
Pros:
- ✅ Built for React Native (Web + Native)
- ✅ Touch gestures built-in
- ✅ iOS + Android support
- ✅ Similar API to web (Victory)
- ✅ TypeScript support
- ✅ Active development (Formidable Labs)
Cons:
- ❌ 284K weekly downloads (less than web libs)
- ❌ 210 KB bundle (larger)
- ❌ Fewer examples than Recharts
Code example:
import { VictoryLine, VictoryChart, VictoryAxis } from 'victory-native'
function StepsChart({ data }: { data: StepsData[] }) {
return (
<VictoryChart>
<VictoryAxis />
<VictoryLine
data={data}
x="date"
y="steps"
style={{ data: { stroke: "#4ECDC4" } }}
/>
</VictoryChart>
)
}Touch interactions:
import { VictoryZoomContainer } from 'victory-native'
<VictoryChart containerComponent={<VictoryZoomContainer />}>
{/* Pinch to zoom, pan to navigate */}
</VictoryChart>Time to first chart: 2-3 hours
Option 2: react-native-chart-kit#
Pros:
- ✅ Designed for React Native
- ✅ Simple API
- ✅ Bezier curves (smooth lines)
Cons:
- ❌ Limited chart types (no radar, area)
- ❌ Less customizable
- ❌ Limited gesture support
- ❌ Smaller community
Why not chosen: Less flexible, fewer features than Victory.
Option 3: react-native-svg-charts#
Pros:
- ✅ Uses react-native-svg directly
- ✅ Lightweight
Cons:
- ❌ Archived (no longer maintained)
- ❌ No TypeScript
- ❌ Breaking changes in React Native 0.70+
Why not chosen: No longer maintained (critical for mobile).
Option 4: Recharts (Web Version)#
Pros:
- ✅ Familiar API
Cons:
- ❌ Doesn’t work in React Native (uses Web SVG)
- ❌ Requires WebView wrapper (poor performance)
Why not chosen: Not compatible with React Native.
Option 5: D3 + react-native-svg#
Pros:
- ✅ Maximum control
- ✅ D3 math works in React Native
Cons:
- ❌ Must build everything (scales, axes, tooltips)
- ❌ Touch gestures not built-in
- ❌ Weeks of development
Why not chosen: Too much work for standard charts.
Recommended Solution#
Library: Victory Native
Why:
- Built for React Native - Not a web port, native-first
- Touch-optimized - Zoom, pan containers built-in
- Cross-platform - Same code for iOS + Android
- Consistent API - If team knows Victory (web), easy to learn
- Well-maintained - Formidable Labs backing
Trade-offs accepted:
- Larger bundle (210 KB) - Features justify size
- Smaller community than Recharts - But active development
- Learning curve - Worth it for mobile-specific features
Implementation Plan#
Week 1: Setup & Basics
- Install Victory Native + react-native-svg
- Create first line chart (daily steps)
- Test on iOS + Android simulators
- Real device testing (performance)
Week 2: Core Charts
- Bar chart (weekly step comparison)
- Progress arc (daily goal)
- Heart rate chart (time series)
- Touch interactions (zoom, pan)
Week 3: Polish
- Custom theme (match app brand)
- Animations (chart entry, data updates)
- Haptic feedback on long-press
- Offline data handling
Week 4: Testing & Optimization
- Performance profiling (React DevTools)
- Memory usage monitoring
- Battery impact testing
- Edge cases (empty data, single point)
Expected outcome: Production-ready charts in 4 weeks.
Success Metrics#
Cross-platform:
- iOS + Android: Identical behavior ✓
- Single codebase: No platform-specific code ✓
Performance:
- 60 FPS on iPhone 11 (mid-range) ✓
- Memory: < 30 MB per chart
- Battery: < 2% per hour (background)
User experience:
- Touch targets: 44+ pts (Apple HIG)
- Swipe navigation: Smooth
- Haptic feedback: On interactions
Real-World Example#
Feature: 7-Day Step History
import { VictoryLine, VictoryChart, VictoryAxis, VictoryTheme } from 'victory-native'
import { View, Text, Pressable } from 'react-native'
import * as Haptics from 'expo-haptics'
function WeeklyStepsChart({ data }: Props) {
const [selectedPoint, setSelectedPoint] = useState<DataPoint | null>(null)
return (
<View>
<VictoryChart theme={VictoryTheme.material}>
<VictoryAxis
tickFormat={(date) => new Date(date).toLocaleDateString('en-US', { weekday: 'short' })}
/>
<VictoryAxis dependentAxis />
<VictoryLine
data={data}
x="date"
y="steps"
style={{
data: { stroke: "#4ECDC4", strokeWidth: 3 }
}}
events={[{
target: 'data',
eventHandlers: {
onPressIn: (evt, props) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
setSelectedPoint(props.datum)
return []
}
}
}]}
/>
</VictoryChart>
{selectedPoint && (
<View style={styles.tooltip}>
<Text>{selectedPoint.date}: {selectedPoint.steps} steps</Text>
</View>
)}
</View>
)
}Touch interactions:
- Press data point → Show tooltip + haptic feedback
- Swipe chart → Navigate to previous/next week
- Pinch → Zoom into specific days
Mobile-Specific Challenges#
1. Tooltip on Touch Screens#
Problem: No “hover” on touch Solution: Long-press or tap to show tooltip
events={[{
target: 'data',
eventHandlers: {
onPressIn: (evt, props) => {
showTooltip(props.datum)
},
onPressOut: () => {
hideTooltip()
}
}
}]}2. Small Screen Real Estate#
Problem: Desktop charts too dense for mobile Solution: Simplify, larger tap targets
// Desktop: 7 days visible
// Mobile: 3 days visible (swipe for more)
<VictoryChart domain={{ x: [todayMinus3Days, today] }} />3. Performance on Older Devices#
Problem: iPhone 8 struggles with 500 points Solution: Downsample or use simpler chart
const displayData = data.length > 100
? downsample(data, 100)
: data4. Dark Mode Support#
Problem: Charts don’t match system theme Solution: Detect color scheme, apply theme
import { useColorScheme } from 'react-native'
const colorScheme = useColorScheme()
const chartTheme = colorScheme === 'dark' ? darkTheme : lightTheme
<VictoryChart theme={chartTheme} />Lessons Learned#
What worked:
- Victory Native: Smooth on both platforms
- Touch events: Long-press better than tap (more intentional)
- Downsampling: 100 points max keeps performance smooth
What didn’t:
- Tooltips: Hard to dismiss on touch (had to add “tap outside”)
- Animations: Too slow on Android (reduced duration)
Would do differently:
- Test on real devices earlier (simulators too fast)
- Build design system for chart colors upfront
- Add loading skeletons (data fetch can be slow)
Related Personas#
- Wearable App Developer - Similar constraints (small screen, touch)
- Health Coach Platform - Mobile-first, similar metrics
- Fitness Startup - Cross-platform React Native + charts
Use Case: React Dashboard Developer#
Who Needs This#
Persona: Sarah, Frontend Developer at a Series B SaaS Startup
Background:
- 3 years React experience
- Building internal analytics dashboard
- Team of 5 engineers (2 frontend, 3 backend)
- Shipping features weekly
Technical Context:
- React 18, TypeScript, Vite
- Existing component library (shadcn/ui)
- Data from REST API (~100-500 data points per chart)
- Desktop-first, responsive secondary
Why They Need It#
Core Need#
Display sales metrics, user growth, and system health on an admin dashboard.
Chart requirements:
- Line charts (time series trends)
- Bar charts (category comparisons)
- Pie charts (conversion funnel)
- Standard, not custom designs
Pain Points Without a Library#
1. Building from scratch is slow
- Calculating scales manually: 2-3 hours
- Drawing axes with ticks: 2-3 hours
- Adding tooltips: 3-4 hours
- Result: 1 week for a single chart type
2. Maintaining custom code
- Edge cases (empty data, single data point)
- Browser compatibility (Safari, Firefox)
- Responsive behavior
- Result: Ongoing maintenance burden
3. TypeScript integration
- Need types for data shapes
- Autocomplete for chart options
- Type-safe event handlers
4. Team velocity
- Backend expects charts “done in a day”
- Custom code slows feature delivery
- Can’t iterate quickly on designs
What Success Looks Like#
Development speed:
- First chart: < 1 hour
- Additional charts: < 30 minutes
- Iterate on design: < 15 minutes
Code quality:
- Type-safe (no runtime errors)
- Testable (unit + visual regression)
- Maintainable (minimal custom code)
User experience:
- Responsive (mobile + desktop)
- Interactive (tooltips, legends)
- Accessible (WCAG AA)
Requirements Analysis#
Must-Have#
- React components (JSX, not config objects)
- TypeScript support (shipped types)
- Standard chart types (line, bar, pie)
- Built-in tooltips and legends
- Responsive container
- Active maintenance (monthly releases)
Nice-to-Have#
- Animation (smooth transitions)
- Customization (colors, styles)
- Small bundle size (< 150 KB)
- Good documentation
Don’t Need#
- 10,000+ data points (only ~500)
- Custom chart types (standard is fine)
- Canvas rendering (SVG is fine)
- React Native support
Library Evaluation#
Option 1: Recharts ⭐ RECOMMENDED#
Pros:
- ✅ Declarative JSX API (feels like React)
- ✅ TypeScript support (excellent)
- ✅ 9M weekly downloads (mature, stable)
- ✅ Built-in tooltips, legends, responsive container
- ✅ Covers all needed chart types
- ✅ Great documentation and examples
Cons:
- ❌ 130 KB gzipped (medium-large bundle)
- ❌ SVG only (fine for 500 points)
- ❌ Limited customization (ok for internal tools)
Code example:
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
function SalesChart({ data }: { data: SalesData[] }) {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="revenue" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
)
}Time to first chart: 30 minutes (including setup)
Option 2: Nivo#
Pros:
- ✅ Declarative React API
- ✅ TypeScript support
- ✅ Beautiful defaults (great aesthetics)
- ✅ More chart types than Recharts
Cons:
- ❌ 500K weekly downloads (10x less than Recharts)
- ❌ Smaller community (fewer examples)
- ❌ 180 KB gzipped (larger bundle)
Why not chosen: Smaller community, larger bundle, no clear advantage over Recharts.
Option 3: Chart.js#
Pros:
- ✅ 60 KB gzipped (smallest)
- ✅ Excellent documentation
- ✅ Handles 1000-10K points
Cons:
- ❌ Imperative API (config objects, not JSX)
- ❌ Canvas rendering (less accessible)
- ❌ Feels awkward in React
Code example:
import { Line } from 'react-chartjs-2'
function SalesChart({ data }: { data: SalesData[] }) {
return (
<Line
data={{
labels: data.map(d => d.date),
datasets: [{ data: data.map(d => d.revenue) }]
}}
options={{ responsive: true }}
/>
)
}Why not chosen: Imperative API doesn’t fit React idioms, Canvas less accessible.
Option 4: D3 / visx#
Pros:
- ✅ Maximum flexibility
- ✅ Full control over design
Cons:
- ❌ Steep learning curve (D3 knowledge required)
- ❌ More code per chart (20+ lines)
- ❌ No built-in tooltips/legends (build yourself)
- ❌ Slow development (overkill for standard charts)
Why not chosen: Overkill for standard charts, slows team velocity.
Recommended Solution#
Library: Recharts
Why:
- Fastest time to value - JSX API is natural for React developers
- Mature ecosystem - 9M downloads, active maintenance, many examples
- Covers all needs - Line, bar, pie + tooltips, legends, responsive
- TypeScript first-class - Excellent type coverage
- Good enough - Internal tool doesn’t need pixel-perfection
Trade-offs accepted:
- Bundle size (130 KB) - Acceptable for internal tool
- Limited customization - Standard charts don’t need it
- SVG performance ceiling - 500 points is well under limit
Implementation Plan#
Week 1:
- Install Recharts + types
- Create reusable chart wrapper components
- Implement first 3 charts (line, bar, pie)
- Set up visual regression tests
Week 2:
- Add remaining chart types
- Implement loading states
- Error handling (empty data, API failures)
- Mobile responsive tweaks
Week 3:
- Accessibility audit (ARIA labels)
- Performance optimization (memoization)
- Documentation for team
Expected outcome: Complete dashboard in 3 weeks, vs 8+ weeks custom-building.
Success Metrics#
Development:
- Time to first chart: < 1 hour ✓
- Time per additional chart: < 30 min ✓
- Code duplication: Minimal (reusable wrapper components)
Runtime:
- Bundle size impact: +130 KB (acceptable)
- Render time: < 50ms per chart (smooth)
- Accessibility: WCAG AA compliance
Team:
- Velocity maintained (weekly feature releases)
- Backend satisfied with chart speed
- No ongoing maintenance burden
Lessons Learned#
What worked:
- Recharts JSX API felt natural to team
- TypeScript caught data shape errors early
- Built-in responsiveness saved time
What didn’t:
- Customization harder than expected (worked around with CSS)
- Animation performance with 500 points (disabled animations)
Would do differently:
- Test bundle size impact earlier
- Set up visual regression tests from day 1
- Create design system for chart colors upfront
Related Personas#
- Junior React Developer - Same needs, even more benefits from Recharts
- Freelance Developer - Recharts speeds up client work
- Agency Team - Recharts works well for multiple projects
S4: Strategic
S4-strategic: Long-Term Viability and Ecosystem Analysis#
Focus#
This pass evaluates long-term strategic considerations when choosing a data visualization library. While S1 answered “WHICH?”, S2 answered “HOW?”, and S3 answered “WHO?”, S4 answers “WHICH will still be here in 5 years?”
Analysis Framework#
For each library, we examine:
1. Ecosystem Health#
- Maintenance status (active vs abandoned)
- Release frequency and cadence
- Contributor diversity (bus factor)
- Corporate/foundation backing
- Community size and engagement
2. Adoption Trends#
- Download trends (growing vs declining)
- Market share trajectory
- Competitive positioning
- Migration patterns (users switching to/from)
3. Technical Debt and Modernization#
- Keeping up with ecosystem changes
- React 18+ compatibility (concurrent rendering)
- TypeScript adoption
- Build tooling (ESM, tree-shaking)
- Accessibility improvements
4. Lock-In Risk#
- Migration difficulty (if abandoned)
- Proprietary APIs vs standards
- Data portability
- Rewrite cost
5. Future-Proofing#
- Roadmap visibility
- Adaptation to trends (WebGPU, AI-assisted charts)
- Cross-platform evolution
- Breaking changes history
Evaluation Criteria#
Green Flags (Low Risk)#
- ✅ Active development (commits in last 3 months)
- ✅ Growing downloads
- ✅ Corporate/foundation backing
- ✅ Multiple maintainers
- ✅ Clear roadmap
- ✅ Responsive to issues
- ✅ Breaking changes rare, well-communicated
Yellow Flags (Medium Risk)#
- ⚠️ Slow release cadence (6+ months)
- ⚠️ Flat or declining downloads
- ⚠️ Single maintainer (bus factor)
- ⚠️ Lagging ecosystem updates (React 19, Vite 6)
- ⚠️ Community fragmentation
Red Flags (High Risk)#
- 🚩 No commits in 6+ months
- 🚩 Declining downloads
- 🚩 Maintainer burnout signals
- 🚩 Unresolved critical issues
- 🚩 No TypeScript support in 2025
- 🚩 Frequent breaking changes
Strategic Risk Assessment#
Low Risk Libraries (Safe Bets)#
Characteristics:
- Active development
- Corporate backing
- Large community
- Clear succession plan
Examples: D3, Recharts, ECharts, Chart.js
Strategy: Build core features on these, expect 5+ year stability
Medium Risk Libraries (Monitor Closely)#
Characteristics:
- Active but small team
- Niche use cases
- Dependent on single sponsor
Examples: Nivo, visx, Victory
Strategy: Use for non-critical features, have migration plan
High Risk Libraries (Avoid or Isolate)#
Characteristics:
- Inactive development
- Declining community
- Unresolved critical bugs
Examples: react-native-svg-charts (archived)
Strategy: Avoid for new projects, migrate away from existing use
Long-Term Considerations#
1. Framework Lock-In#
React-specific libraries (Recharts, visx, Nivo, Victory):
- Risk: What if you migrate to Vue/Svelte in 3 years?
- Mitigation: Abstract charts behind interface, ease rewrite
Framework-agnostic (D3, Chart.js, ECharts):
- Benefit: Can port across framework migrations
- Trade-off: Less idiomatic in any single framework
2. Bundle Size Trajectory#
Historical trend:
- 2015: Libraries 200-300 KB
- 2020: Libraries 100-150 KB (tree-shaking)
- 2025: Libraries 50-130 KB (better optimization)
Future expectation:
- Libraries not optimizing will lose market share
- ESM and tree-shaking becoming table stakes
Strategy: Pick libraries trending toward smaller bundles (Chart.js, visx)
3. Rendering Technology Evolution#
Current: SVG → Canvas → WebGL Future: WebGPU (compute + graphics on GPU)
Which libraries adapting:
- ECharts: Already has WebGL, likely to add WebGPU
- D3: Rendering-agnostic, can target WebGPU
- Canvas-locked (Chart.js): May add WebGPU renderer
Strategy: Pick libraries with renderer abstraction (ECharts, D3)
4. AI-Assisted Charting Trend#
Emerging pattern (2024-2025):
- Natural language → Chart type selection
- Auto-visualization of data
- Chart optimization suggestions
Which libraries positioned:
- Config-driven (ECharts, Chart.js): Easier for AI to generate
- Code-driven (D3, visx): Harder for AI to generate
Prediction: Declarative config libraries benefit from AI trend
5. Accessibility Regulations#
Increasing: WCAG 2.2 (2023), European Accessibility Act (2025)
Libraries improving accessibility:
- Victory: Best built-in ARIA support
- ECharts: Adding decal patterns for colorblind
- SVG libraries: Inherent advantage (screen readers)
Strategy: Pick SVG libraries or those investing in accessibility
Succession Planning#
What Happens If Library is Abandoned?#
Best case: Established library (Recharts, Chart.js)
- Community fork
- New maintainers step up
- Example: Moment.js → Day.js transition
Worst case: Niche library (small community)
- No fork
- Must rewrite
- Example: react-native-svg-charts (abandoned)
Mitigation strategies:
- Vendor abstraction: Wrap library behind interface
- Standard APIs: Prefer libraries using web standards
- Data portability: Use standard data formats (JSON, CSV)
- Migration budget: Reserve 10-20% of project for rewrites
Competitive Analysis: Market Positioning#
Tier 1: Market Leaders (Dominant)#
- D3: The standard, 13 years old, 108K stars
- Recharts: React leader, 9M downloads/week
- Chart.js: Framework-agnostic leader, 64K stars
Strategic position: Safe for 5+ years, large switching cost for ecosystem
Tier 2: Strong Contenders (Growing)#
- ECharts: Enterprise standard, Apache backing
- visx: Airbnb backing, filling D3+React gap
Strategic position: Growing, viable long-term
Tier 3: Niche Players (Stable)#
- Nivo: SSR niche, steady community
- Victory: React Native niche, Formidable Labs
Strategic position: Serve specific needs, monitor for decline
Tier 4: Declining (Risky)#
- Plotly.js: Losing to ECharts in performance space
- C3.js: D3 wrapper losing to Recharts
Strategic position: Avoid for new projects
Risk Mitigation Strategies#
Strategy 1: Vendor Abstraction#
// Bad: Direct library coupling
import { LineChart } from 'recharts'
<LineChart data={data} />
// Good: Abstraction layer
interface ChartProps {
data: DataPoint[]
type: 'line' | 'bar'
}
function Chart({ data, type }: ChartProps) {
// Can swap Recharts for ECharts without changing consumers
return <RechartsImpl data={data} type={type} />
}Benefit: Swap libraries without rewriting consumers
Strategy 2: Standard Data Formats#
// Bad: Library-specific format
const rechartsData = [{ name: 'A', value: 1 }]
// Good: Standard format
const standardData = [{ x: 'A', y: 1 }]
// Transform at boundary
const rechartsData = standardData.map(d => ({ name: d.x, value: d.y }))Benefit: Port data to new library easily
Strategy 3: Feature Flags#
const CHART_LIBRARY = process.env.CHART_LIBRARY || 'recharts'
function Chart(props: ChartProps) {
if (CHART_LIBRARY === 'recharts') {
return <RechartsChart {...props} />
} else if (CHART_LIBRARY === 'echarts') {
return <EChartsChart {...props} />
}
}Benefit: A/B test migration, gradual rollout
Strategy 4: Regular Health Checks#
Quarterly review:
- Check GitHub commits (last 3 months)
- Check npm download trends
- Check open issue count
- Check breaking changes
Red flag threshold:
- No commits in 6 months → Start migration planning
- Downloads declining 50% → Monitor closely
- 100+ open issues → Evaluate alternatives
Future Trends to Watch#
1. Web Components#
- Standards-based custom elements
- Framework-agnostic by default
- Could disrupt React-specific libraries
Prediction: Minor impact, React still dominant
2. WebGPU Adoption#
- GPU acceleration for charts
- 10-100x performance gain
- ECharts likely early adopter
Prediction: High-performance niche, not mainstream by 2027
3. Declarative Visualization Languages#
- Vega, Vega-Lite gaining traction
- JSON spec → Chart rendering
- AI-friendly generation
Prediction: Niche for data exploration tools, not web apps
4. Real-Time Collaboration#
- Google Docs-style chart editing
- Multiplayer interactions
- CRDTs for chart state
Prediction: Niche feature, not core library concern
5. Accessibility Mandate#
- WCAG 2.2 compliance required
- Screen reader optimization
- Keyboard navigation
Prediction: High impact, libraries must invest or lose market
Conclusion: Strategic Recommendations#
For Long-Term Projects (5+ years)#
Safest choices:
- D3 - Will outlive us all, rendering-agnostic
- Recharts - React ecosystem leader
- ECharts - Apache backing, enterprise adoption
Risky choices:
- Small community libraries (risk of abandonment)
- Single-maintainer projects (bus factor)
For Short-Term Projects (1-2 years)#
Any mature library works:
- Optimize for development speed, not longevity
- Even medium-risk libraries fine for short horizon
Migration Planning#
Budget 10-20% of project for library changes:
- Ecosystem evolves fast
- Plan for rewrites every 3-5 years
- Don’t over-invest in vendor lock-in prevention
Next: See individual library viability analyses in this directory.
D3 and ECharts: Long-Term Viability Analysis#
D3.js: The Immortal Standard#
Executive Summary#
Risk Level: 🟢 LOWEST RISK (Will outlive most frameworks)
Key Strengths:
- 13 years old, still dominant
- 108K GitHub stars
- Creator (Mike Bostock) still active
- Observable (company) backing
Key Concerns:
- None significant (closest to “too big to fail”)
Recommendation: Safest long-term bet if team has D3 expertise.
Why D3 is Different#
D3 is infrastructure, not a library:
- Like jQuery or Lodash (fundamental tool)
- Frameworks come and go, D3 remains
- React, Vue, Angular all use D3 internally
Rendering-agnostic:
- Can target SVG, Canvas, WebGL
- Future-proof (can adopt WebGPU)
- Framework-agnostic
Standard algorithms:
- Scales, shapes, layouts are timeless
- Math doesn’t change
- Other libraries wrap D3 (Recharts, visx, Nivo)
Ecosystem Health: 🟢 EXCELLENT#
Maintenance:
- Active development (monthly releases)
- Creator (Mike Bostock) still maintains
- Observable (his company) backs development
Community:
- 108K GitHub stars (most popular viz library)
- Massive Stack Overflow presence
- Observable notebooks (1000s of examples)
Bus factor:
- Mike Bostock critical, but…
- Observable company provides continuity
- Community could maintain if needed (simple, modular code)
Long-Term Outlook: 🟢 BEST IN CLASS#
Best case (90%): Continues as standard for decades Worst case (10%): Slows down, community forks
Prediction: D3 will be around in 2035.
When to Choose D3#
Use D3 if:
- Need custom, novel visualizations
- Team has D3 expertise
- Framework-agnostic (future-proof)
- Maximum flexibility required
Avoid D3 if:
- Team lacks D3 knowledge (steep learning curve)
- Tight deadline (faster options exist)
- Standard charts only (overkill)
ECharts: The Enterprise Workhorse#
Executive Summary#
Risk Level: 🟢 LOW RISK (Apache Foundation backing)
Key Strengths:
- Apache Foundation project
- Baidu (Chinese tech giant) backing
- Enterprise adoption (finance, telecom)
- WebGL support (future-proof rendering)
Key Concerns:
- Bundle size (320 KB full, 150 KB tree-shaken)
- Western adoption slower than Asian markets
Recommendation: Safest choice for large datasets and enterprise needs.
Ecosystem Health: 🟢 EXCELLENT#
Institutional Backing:
- Apache Software Foundation (non-profit governance)
- Baidu (original creator, still contributes)
- Enterprise sponsors (Alibaba, Tencent use it)
Benefits of Apache:
- Neutral governance (no single company control)
- Succession plan (Apache outlives companies)
- Legal protection (IP, licensing)
Community:
- 61K GitHub stars
- 1M weekly downloads (growing)
- Active Chinese + English communities
Release Cadence: 🟢 ACTIVE#
Recent releases:
- v5.0 (2020): Major rewrite (tree-shaking, TypeScript)
- v5.5 (2024): Performance improvements
- Monthly patch releases
Breaking changes:
- v4 → v5 (2020): Major (well-communicated, migration guide)
- v5.0 → v5.5 (2020-2025): Minor (backward compatible)
Pattern: Conservative, stable (like D3)
Adoption Trends: 🟢 GROWING#
Download trajectory:
- 2020: 400K/week
- 2022: 700K/week
- 2024: 1M/week
- Growth: 2.5x in 4 years
Geographic split:
- China: Dominant (default choice for enterprises)
- West: Growing (displacing Highcharts, Plotly)
Migration patterns:
- Inbound: From Plotly (performance), Highcharts (cost)
- Outbound: Rare (hard to beat performance)
Technical Modernization: 🟢 EXCELLENT#
Rendering evolution:
- Canvas (current default)
- SVG (accessibility mode)
- WebGL (echarts-gl for 1M+ points)
- Future: Positioned for WebGPU
Build tooling:
- ESM support ✅
- Tree-shaking ✅
- TypeScript ✅
- Vite/Webpack/Rollup compatible ✅
Trend: Modernizing faster than most libraries
Cross-Platform: 🟢 STRONG#
Current:
- Web (primary)
- Weex (Alibaba mobile framework)
- Mini-programs (WeChat, Alipay)
Future:
- Investigating React Native (community effort)
- Server-side rendering (via node-canvas)
Future-Proofing: 🟢 EXCELLENT#
WebGL support:
- echarts-gl stable (3D, globe, millions of points)
- Renders on GPU (future-proof)
AI trend positioning:
- Config-driven API (AI-friendly)
- Observable notebooks showcase AI generation
- ECharts likely beneficiary of AI-assisted charts
Accessibility:
- ARIA support added (v5.0+)
- Decal patterns for colorblind users
- SVG renderer option (screen readers)
Competitive Position: 🟢 STRONG#
Strengths:
- Only library handling 1M+ points
- 100+ chart types (comprehensive)
- Apache backing (neutral governance)
Threats:
- Web-native innovations (Web Components, declarative viz)
- Rendering tech shifts (WebGPU adoption slow)
Defense:
- Rendering-agnostic (can adapt)
- Enterprise lock-in (migration cost high)
Long-Term Outlook: 🟢 EXCELLENT#
Best case (80%):
- Becomes Western enterprise standard (like in China)
- Continues innovation (WebGPU, AI)
- Apache ensures long-term stability
Base case (20%):
- Remains niche (large datasets, enterprises)
- Stable, maintained, but not market leader
Worst case (<1%):
- Abandoned by Apache (extremely unlikely)
Prediction: ECharts will be around in 2035, especially for enterprise/large data use cases.
When to Choose ECharts#
Use ECharts if:
- Need 10K+ data points
- Need 100+ chart types
- Enterprise dashboard
- Future-proof rendering (WebGL)
- Long-term project (5+ years)
Avoid ECharts if:
- < 1000 data points (simpler tools suffice)
- React-idiomatic API preferred (use Recharts)
- Bundle size critical (use Chart.js)
Comparative Strategic Analysis#
| Aspect | D3 | ECharts |
|---|---|---|
| Risk Level | 🟢 Lowest | 🟢 Low |
| Backing | Observable (company) | Apache Foundation |
| Longevity | 13 years, will last decades | 12 years, Apache ensures longevity |
| Community | Massive (108K stars) | Large (61K stars) |
| Innovation | Slow, stable | Moderate, modernizing |
| Lock-in risk | Low (math is portable) | Medium (config API proprietary) |
| Future-proofing | Rendering-agnostic | WebGL, positioned for WebGPU |
| Migration cost | High (rewrite from scratch) | Medium (config can be abstracted) |
Strategic Recommendations#
For Maximum Longevity: D3#
Rationale:
- Will outlive frameworks
- Math and algorithms timeless
- Can target any rendering tech
Trade-off: Steeper learning curve, more code
For Enterprise Performance: ECharts#
Rationale:
- Apache ensures longevity
- Only library handling 1M+ points
- Enterprise adoption growing
Trade-off: Larger bundle, config API
Risk Mitigation#
Both are low-risk:
- Institutional backing (Observable, Apache)
- Large communities (can fork if needed)
- Proven track records (10+ years)
No special mitigation needed - these are the two safest long-term bets.
Conclusion#
D3 and ECharts are the two safest long-term choices:
D3: The immortal standard - will outlast most technologies ECharts: The enterprise workhorse - Apache backing, future-proof rendering
For 10+ year projects:
- D3: Maximum longevity guarantee
- ECharts: Best for large datasets with institutional backing
Neither requires migration planning - both will be maintained for decades.
Recharts: Long-Term Viability Analysis#
Executive Summary#
Risk Level: 🟢 LOW RISK (Safe for 5+ year projects)
Key Strengths:
- Market leader (9M weekly downloads)
- Active development
- Large community
- Stable API (few breaking changes)
Key Concerns:
- Performance ceiling (SVG limitation)
- Slower release cadence (6-12 months)
- No corporate backing (community-driven)
Recommendation: Safe choice for standard React dashboards with caveat of SVG performance limits.
Ecosystem Health Assessment#
Maintenance Status: 🟢 ACTIVE#
GitHub Activity (Last 12 Months):
- Commits: 150+ (steady pace)
- Contributors: 20+ active
- Releases: 2-3 per year (v2.10, v2.11, v2.12)
- Response time: < 1 week for issues
Recent milestones:
- React 18 support (2023)
- TypeScript improvements (ongoing)
- Performance optimizations (2024)
Community Size: 🟢 VERY LARGE#
Metrics:
- GitHub stars: 25.6K
- npm downloads: 9M/week (growing)
- Stack Overflow questions: 5,000+
- Discord/Slack: 2,000+ members
Community health:
- Active discussion (daily posts)
- User-contributed examples
- Third-party plugins and extensions
- Strong documentation contributions
Contributor Diversity: 🟡 MEDIUM#
Bus factor: 2-3 core maintainers
Maintainers:
- 3 core team members
- 20+ regular contributors
- 200+ one-time contributors
Risk:
- No corporate sponsor (community-driven)
- Dependent on volunteer time
- Could slow if maintainers leave
Mitigation:
- Large community can fork if needed
- Simple codebase (easy to maintain)
Adoption Trends#
Download Trajectory: 🟢 GROWING#
Historical downloads:
- 2020: 2M/week
- 2022: 5M/week
- 2024: 9M/week
- Growth: 4.5x in 4 years
Market share (React charts):
- Recharts: 9M/week (dominant)
- Nivo: 500K/week
- Victory: 284K/week
- Recharts is 18x larger than nearest competitor
Competitive Position: 🟢 STRONG#
Strengths:
- First-mover advantage (React charts)
- Network effects (most examples, tutorials)
- “Default choice” status
Threats:
- visx (Airbnb backing, more flexible)
- Nivo (better SSR)
- Performance-focused tools (ECharts for large data)
Positioning:
- “Easy React charts” niche (defensible)
- Trade-off: Simplicity over performance
- Hard to dethrone (switching cost)
Migration Patterns: 🟢 STICKY#
Inbound migrations:
- Teams switching from D3 (too complex)
- Teams switching from Chart.js (want React API)
Outbound migrations:
- Teams hitting performance limits → ECharts
- Teams needing customization → visx
Net migration: Positive (more teams adopting than leaving)
Technical Modernization#
React Compatibility: 🟢 EXCELLENT#
Current support:
- React 16.8+ (hooks)
- React 17 (no issues)
- React 18 (concurrent rendering supported)
Future:
- React 19: Likely compatible (minor changes expected)
- Server Components: Planned support (roadmap item)
TypeScript Adoption: 🟢 EXCELLENT#
Type coverage:
- Ships with TypeScript definitions
- 95%+ type coverage
- Actively improving generics
Developer experience:
- Autocomplete works well
- Type inference for data shapes
- Catches errors at compile time
Build Tooling: 🟡 MODERATE#
Module formats:
- CommonJS: ✅ Supported
- ESM: ✅ Supported
- Tree-shaking: ⚠️ Partial (D3 dependencies bundled)
Bundle size trend:
- 2020: 150 KB gzipped
- 2025: 130 KB gzipped
- Improvement: 13% smaller
Room for improvement:
- Better tree-shaking (D3 modules)
- ESM-first build
- Code splitting
Accessibility: 🟡 MODERATE#
Current state:
- SVG output (screen reader accessible)
- Manual ARIA labels required
- No keyboard navigation built-in
Improvements needed:
- Built-in ARIA labels
- Keyboard focus management
- High contrast mode support
Trend: Slowly improving, but behind Victory
Lock-In Risk Assessment#
Migration Difficulty: 🟡 MEDIUM#
Rewrite cost if abandoned:
- JSX API specific to Recharts
- Data format somewhat proprietary
- Estimated effort: 2-4 weeks for typical dashboard
Mitigation:
- Wrap Recharts in abstraction layer
- Use standard data formats
- Budget 10-20% for migration
API Stability: 🟢 EXCELLENT#
Breaking changes history:
- v1 → v2 (2019): Major breaking changes
- v2.0 → v2.12 (2019-2025): No breaking changes
- Stability: 5+ years without major breaks
Upgrade path:
- Patch releases: Drop-in (no changes)
- Minor releases: Deprecations (warnings, not breaks)
- Major releases: Rare (last one 2019)
Data Portability: 🟢 GOOD#
Data format:
// Recharts format (simple objects)
[{ name: 'A', value: 1 }, { name: 'B', value: 2 }]Portability:
- Standard JavaScript objects (not proprietary)
- Easy to transform to other formats
- Can export to CSV, JSON
Future-Proofing#
Roadmap Visibility: 🟡 MODERATE#
Public roadmap:
- GitHub milestones (some visibility)
- Issue labels (feature requests)
- No formal product roadmap
Planned features:
- React Server Components support
- Performance improvements (memoization)
- Accessibility enhancements
Uncertainty:
- Community-driven (features depend on contributors)
- No guaranteed timeline
Adaptation to Trends: 🟡 MODERATE#
Responsive to ecosystem:
- React 18 support: ✅ (adopted quickly)
- TypeScript: ✅ (excellent support)
- ESM: ⚠️ (partial, ongoing)
- WebGPU: ❌ (unlikely, SVG-focused)
Innovation pace:
- Iterative improvements (not revolutionary)
- Conservative changes (stability prioritized)
Cross-Platform Evolution: 🟡 LIMITED#
Platforms:
- Web: ✅ Primary focus
- React Native: ❌ Not supported
- SSR: ⚠️ Partial support (hydration issues)
Future:
- Unlikely to expand beyond web React
- Victory Native fills React Native niche
Breaking Changes History#
v1 → v2 (2019): MAJOR#
Changes:
- Rewritten in TypeScript
- Hooks-based implementation
- API redesign (breaking)
Migration effort: 1-2 weeks for typical project
v2.0 → v2.12 (2019-2025): NONE#
Stability: 6 years without breaking changes
Pattern: Conservative evolution, backward compatibility prioritized
Competitive Threats#
Threat 1: visx (Airbnb)#
Advantage over Recharts:
- More flexible (lower-level primitives)
- Smaller bundle (tree-shakeable)
- Better for custom charts
Recharts defense:
- Simpler API (faster development)
- Larger community (more examples)
- Built-in features (tooltips, legends)
Outcome: Coexistence (different niches)
Threat 2: Nivo#
Advantage over Recharts:
- Better SSR support
- More chart types
- Beautiful defaults
Recharts defense:
- 18x more downloads (network effects)
- Simpler API
- Better TypeScript support
Outcome: Recharts maintains lead
Threat 3: ECharts#
Advantage over Recharts:
- Performance (Canvas, not SVG)
- 100+ chart types
- Large dataset support
Recharts defense:
- React-idiomatic (JSX vs config)
- Simpler for small datasets
- Better React ecosystem fit
Outcome: Different use cases (Recharts for < 1K points, ECharts for 10K+)
Strategic Recommendations#
For New Projects#
Use Recharts if:
- Building React dashboard
- < 1000 data points
- Standard chart types
- Team prefers JSX API
- 5-year timeline acceptable
Avoid Recharts if:
- Need 10K+ data points (use ECharts)
- Need custom visualizations (use visx)
- Need React Native (use Victory Native)
For Existing Projects#
Stick with Recharts:
- Still actively maintained
- No urgent need to migrate
- Stable API (low risk)
Consider migrating if:
- Hitting performance limits (→ ECharts)
- Need custom designs (→ visx)
- SSR critical (→ Nivo)
Risk Mitigation#
Low priority:
- Migration risk is low (could rewrite in 2-4 weeks)
- Community large enough to fork if abandoned
- Stable API reduces upgrade pain
Recommended actions:
- Wrap Recharts in abstraction layer (optional)
- Monitor download trends quarterly
- Keep dependencies updated
- Plan for React 19 upgrade (likely smooth)
Long-Term Outlook (5 Years)#
Best case (70% probability):
- Continues as React chart leader
- Gradual improvements (performance, accessibility)
- Stable API, no major breaks
- Community remains active
Base case (25% probability):
- Slows down (maintainer burnout)
- Community fork emerges
- Feature development stalls
- Still usable, but stagnant
Worst case (5% probability):
- Abandoned by maintainers
- No community fork
- Must migrate to alternative
Conclusion: Low risk, safe for long-term use
Verdict#
Recommended for:
- Standard React dashboards (< 1K points)
- Teams prioritizing stability over cutting-edge
- 5-year projects (low migration risk)
Not recommended for:
- Large datasets (10K+ points)
- Custom visualizations (pixel-perfect)
- React Native projects
Overall: 🟢 LOW RISK - Recharts is a safe, mature choice for React charts.
S4 Recommendation: Strategic Library Selection#
TL;DR: Risk Rankings#
| Library | Risk Level | 5-Year Viability | 10-Year Viability |
|---|---|---|---|
| D3 | 🟢 Lowest | 99% | 95% |
| ECharts | 🟢 Low | 95% | 90% |
| Recharts | 🟢 Low | 90% | 70% |
| Chart.js | 🟢 Low | 90% | 75% |
| visx | 🟡 Medium | 80% | 60% |
| Nivo | 🟡 Medium | 75% | 50% |
| Victory | 🟡 Medium | 70% | 50% |
The Immortals: D3 and ECharts#
D3: Will Outlive Us All#
Why it’s different:
- 13 years old, still growing
- Infrastructure-level (like jQuery, Lodash)
- Rendering-agnostic (SVG, Canvas, WebGL, future WebGPU)
- Framework-agnostic (works with React, Vue, Angular, vanilla)
- Creator (Mike Bostock) + Observable (company) backing
Strategic value:
- Zero framework lock-in
- Can migrate frameworks, keep D3
- Math doesn’t change (scales, shapes timeless)
- Future-proof rendering (can adopt any tech)
Trade-off:
- Steep learning curve
- More code per chart
- Slower development initially
Verdict: Safest 10+ year bet, if team has expertise.
ECharts: The Enterprise Standard#
Why it’s robust:
- Apache Software Foundation (neutral governance)
- Baidu backing (Chinese tech giant)
- Enterprise adoption (finance, telecom)
- WebGL support (1M+ points, future-proof)
Strategic value:
- Apache ensures succession (outlives companies)
- Only library handling massive datasets
- Modernizing faster than competitors (ESM, TypeScript, WebGL)
Trade-off:
- Larger bundle (150-320 KB)
- Config API (not React-idiomatic)
Verdict: Safest choice for large datasets, Apache backing ensures longevity.
The Safe Bets: Recharts and Chart.js#
Recharts: React Ecosystem Leader#
Why it’s stable:
- 9M weekly downloads (18x nearest competitor)
- 5+ years without breaking changes
- Large community (25K stars, 5K SO questions)
- Network effects (most examples, tutorials)
Risk factors:
- No corporate backing (community-driven)
- 2-3 core maintainers (bus factor)
- Slower innovation (conservative pace)
Mitigation:
- Large community can fork if needed
- Simple codebase (easy to maintain)
- Stable API (low upgrade burden)
Verdict: Safe for 5-year projects, monitor for maintainer burnout.
Chart.js: Framework-Agnostic Workhorse#
Why it’s stable:
- 13 years old (like D3)
- 64K GitHub stars
- Active maintenance (monthly releases)
- Framework-agnostic (future-proof)
Risk factors:
- Canvas-only (no SVG fallback)
- Performance ceiling (10K points)
Mitigation:
- Framework independence reduces lock-in
- Simple migration to ECharts if needed
Verdict: Safe for 5-year projects, framework independence is strategic advantage.
The Niche Players: visx, Nivo, Victory#
visx: Airbnb’s D3+React Bridge#
Why it’s viable:
- Airbnb backing (Active OSS program)
- Fills specific niche (D3 power + React patterns)
- Growing adoption (350K weekly downloads)
Risk factors:
- Dependent on Airbnb priorities
- Smaller community than Recharts
- No clear succession plan
Mitigation:
- D3-based (can fall back to vanilla D3)
- Modular (easy to maintain subset)
- Code is simple (forkable)
Verdict: Medium risk, but Airbnb’s OSS track record is good (react-dates, enzyme).
Nivo: SSR Specialist#
Why it’s viable:
- Fills SSR niche (Next.js, Gatsby)
- Beautiful defaults (strong brand)
- Steady community (500K downloads)
Risk factors:
- Small team (1-2 maintainers)
- Niche positioning (SSR not mainstream)
- Slower growth than Recharts
Mitigation:
- Built on D3 (can migrate to D3 or visx)
- Simple codebase (forkable)
Verdict: Medium risk, use for SSR-critical projects, monitor closely.
Victory: React Native Specialist#
Why it’s viable:
- Formidable Labs backing (enterprise OSS)
- Only viable React Native option
- Fills clear niche
Risk factors:
- Smallest community (284K downloads)
- React Native fragmentation (ecosystem churn)
- Formidable Labs could deprioritize
Mitigation:
- No alternative (must use Victory or build from scratch)
- Enterprise backing (Formidable Labs stable)
Verdict: Medium risk, but necessary for React Native. Monitor Formidable Labs commitment.
Strategic Decision Framework#
Decision 1: Project Timeline#
1-2 year projects: Any mature library acceptable
- Optimize for development speed
- Migration risk low (short horizon)
- Recommendation: Recharts (React), Chart.js (agnostic)
3-5 year projects: Stick to low-risk libraries
- Balance speed and longevity
- Monitor ecosystem health quarterly
- Recommendation: Recharts, Chart.js, ECharts
5+ year projects: Only lowest-risk libraries
- Prioritize longevity over features
- Budget for rewrites (10-20%)
- Recommendation: D3, ECharts, Recharts
10+ year projects: Infrastructure-level only
- Framework-agnostic preferred
- Rendering-agnostic ideal
- Recommendation: D3 (only), ECharts (second choice)
Decision 2: Migration Budget#
< 5% budget: Only safest libraries
- Cannot afford rewrites
- Must pick long-term winners
- Recommendation: D3, ECharts
10-20% budget: Low-medium risk acceptable
- Plan for potential migration
- Wrap libraries in abstraction layer
- Recommendation: Recharts, Chart.js, visx
> 20% budget: Any library acceptable
- Can rewrite as needed
- Optimize for current needs
- Recommendation: Best tool for job (Recharts, ECharts, visx)
Decision 3: Framework Lock-In Tolerance#
High tolerance (React-only):
- Committed to React ecosystem
- Unlikely to switch frameworks
- Recommendation: Recharts, visx
Medium tolerance:
- Might switch frameworks in 5+ years
- Want easier migration path
- Recommendation: Chart.js, ECharts, D3
Low tolerance:
- Framework-agnostic required
- Maximum portability
- Recommendation: D3, Chart.js, ECharts
Decision 4: Performance Requirements#
< 1000 points:
- Any library works (SVG fine)
- Recommendation: Recharts (React), Chart.js (agnostic)
1K-10K points:
- Canvas required
- Recommendation: Chart.js, ECharts
10K-1M points:
- Canvas + optimization
- Recommendation: ECharts
1M+ points:
- WebGL required
- Recommendation: ECharts (only option)
Risk Mitigation Best Practices#
1. Vendor Abstraction Layer#
// Isolate library choice behind interface
interface ChartLibrary {
render(data: DataPoint[], container: HTMLElement): void
destroy(): void
}
class RechartsAdapter implements ChartLibrary {
render(data, container) {
// Recharts implementation
}
}
class EChartsAdapter implements ChartLibrary {
render(data, container) {
// ECharts implementation
}
}
// Can swap without touching consumers
const chartLib: ChartLibrary = new RechartsAdapter()Benefit: Swap libraries with minimal refactoring
Cost: Additional abstraction layer (5-10% overhead)
Recommendation: Use for 5+ year projects with medium-risk libraries
2. Standard Data Formats#
// Don't use library-specific formats
// BAD
const rechartsData = [{ name: 'A', value: 1 }]
// GOOD
const standardData = [{ x: 'A', y: 1 }]
// Transform at boundary
const rechartsData = standardData.map(d => ({ name: d.x, value: d.y }))Benefit: Portable data, easy to migrate
Cost: Transformation overhead (negligible)
Recommendation: Always use standard formats
3. Quarterly Health Checks#
Monitor:
- GitHub commits (last 3 months)
- npm download trends
- Open issue count
- Breaking changes
Red flags:
- No commits in 6 months
- Downloads declining 50%+
- 100+ unresolved issues
Action triggers:
- Red flags → Start migration planning
- Yellow flags → Evaluate alternatives
Recommendation: Set calendar reminder (quarterly)
4. Migration Planning#
Budget allocation:
- 1-2 year projects: 0% (no planning needed)
- 3-5 year projects: 10% (some planning)
- 5+ year projects: 20% (active planning)
Migration scenarios:
- Library abandoned → 2-4 weeks rewrite
- Performance limits → 1-2 weeks (ECharts)
- Customization needs → 2-4 weeks (visx/D3)
Recommendation: Have Plan B, don’t over-invest in prevention
Future Trends Impact#
Trend 1: WebGPU Adoption (2026-2028)#
Impact: 10-100x performance for 3D/complex viz
Winners: ECharts (likely early adopter), D3 (rendering-agnostic)
Losers: Canvas-locked libraries (Chart.js, unless they add WebGPU)
Action: If 3D/massive data critical, pick ECharts or D3
Trend 2: AI-Assisted Chart Generation (2025-2027)#
Impact: Natural language → Chart code
Winners: Config-driven (ECharts, Chart.js) - easier for AI to generate
Neutral: Code-driven (D3, visx) - harder for AI, but AI can learn
Action: If AI generation valuable, prefer config-driven libraries
Trend 3: Accessibility Mandates (2025-2026)#
Impact: WCAG 2.2 compliance required (Europe, US)
Winners: SVG libraries (Recharts, visx, D3), Victory (built-in ARIA)
Losers: Canvas-only (Chart.js, ECharts) - need ARIA workarounds
Action: If accessibility critical, pick SVG libraries or ECharts (SVG mode)
Trend 4: React Server Components (2025-2026)#
Impact: SSR becomes mainstream React pattern
Winners: Nivo (built for SSR), visx (pure SVG)
Losers: Recharts (hydration issues), client-heavy libraries
Action: If Next.js/SSR critical, pick Nivo or visx
Final Strategic Recommendations#
For Different Risk Profiles#
Risk-averse (enterprises, 10+ year projects):
- D3 (maximum longevity)
- ECharts (Apache backing, large data)
- Recharts (React ecosystem leader)
Balanced (most teams, 3-5 year projects):
- Recharts (React, < 1K points)
- ECharts (large data)
- visx (custom charts)
- Chart.js (framework-agnostic)
Aggressive (startups, 1-2 year projects):
- Best tool for job (optimize for speed)
- Monitor ecosystem
- Budget for rewrites
The 80/20 Rule#
For 80% of projects:
- React + standard charts: Recharts
- React + custom charts: visx
- Large datasets: ECharts
- Framework-agnostic: Chart.js
- React Native: Victory Native
For 20% of projects (special needs):
- Maximum longevity: D3
- SSR-critical: Nivo
- 3D/WebGL: ECharts + echarts-gl
- Novel visualizations: D3
One-Sentence Recommendations#
| Scenario | Library | Why |
|---|---|---|
| Default React dashboard | Recharts | Safest, fastest |
| Maximum longevity | D3 | Will outlast frameworks |
| Large datasets | ECharts | Only option for 1M+ points |
| Custom visualizations | visx | D3 power + React patterns |
| Framework-agnostic | Chart.js | Smallest, simplest |
| React Native | Victory Native | Only viable option |
Conclusion#
The safest long-term bets:
- D3 - Infrastructure-level, will last decades
- ECharts - Apache backing, future-proof rendering
- Recharts - React ecosystem leader, stable API
All three are low-risk for 5+ year projects.
For 10+ year projects: Only D3 guaranteed. ECharts second choice (Apache ensures longevity).
For most projects: Recharts (React) or Chart.js (agnostic) is the right balance of speed, stability, and longevity.
Budget 10-20% for rewrites - ecosystem evolves, plan for change.