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#

ElementsBest Renderer
< 1000SVG
1000 - 50000Canvas
> 50000WebGL

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#

ChartBest For
LineTrends over time
BarComparing categories
PiePart of whole (< 6 parts)
ScatterRelationship between variables
AreaCumulative trends
HeatmapDensity, patterns
TreemapHierarchical data
SankeyFlow, connections
NetworkRelationships

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.

  • 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#

SituationRecommendation
React dashboard (standard charts)Recharts
Custom/complex visualizationsvisx
Large datasets (1000+ points)ECharts or Chart.js
React Native cross-platformVictory
Full creative controlD3.js
Enterprise with support needsHighcharts (commercial)

2025 Landscape#

Weekly Downloads (npm)#

Recharts:    ███████████████████████████████████  9M
D3.js:       █████████████████████████            4.5M
Chart.js:    ████████████████████                 3M
ECharts:     ███████████                          1M
Nivo:        ███                                  500K
visx:        ██                                   350K
Victory:     ██                                   284K

GitHub Stars#

D3.js:       ████████████████████████████████████████████████  108K
Chart.js:    ██████████████████████████████████████            64K
ECharts:     ████████████████████████████████████              61K
Recharts:    █████████████████████████                         25.6K
visx:        ███████████████████                               19K
Nivo:        ██████████████                                    13.6K
Victory:     ███████████                                       11.2K

The 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#

ScenarioBetter Choice
1000+ data pointsECharts (WebGL)
Network graphs, force layoutsD3.js or visx
Highly custom animationsD3.js
Server-side renderingNivo
React + React NativeVictory
Need commercial supportHighcharts

Library Tiers#

Tier 1: High-Level React (Easiest)#

LibraryBest ForTrade-off
RechartsDashboardsLimited customization
NivoSSR, varietySmaller community
VictoryCross-platformLess popular

Tier 2: Low-Level React (More Control)#

LibraryBest ForTrade-off
visxCustom D3+ReactSteeper learning curve

Tier 3: Framework Agnostic (Maximum Power)#

LibraryBest ForTrade-off
D3.jsAnything customVery steep learning curve
EChartsLarge datasetsComplex API
Chart.jsQuick setupLess flexible

Rendering: SVG vs Canvas vs WebGL#

RendererBest ForLimit
SVG<1000 points, interactivitySlows down with many elements
Canvas1K-50K pointsFast but no DOM manipulation
WebGL50K+ pointsFastest, 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#


Chart.js#

“Simple yet flexible JavaScript charting library.”

Quick Facts#

MetricValue
GitHub Stars64,000
npm Weekly Downloads~3M
RenderingCanvas
LicenseMIT
React Wrapperreact-chartjs-2

Why Chart.js?#

Chart.js is the simplest way to add charts to any project:

  1. Tiny: ~60KB gzipped (with tree-shaking)
  2. Fast: Canvas rendering
  3. Simple API: Works in minutes
  4. Framework agnostic: Works anywhere
  5. 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#

  1. Limited chart types: 8 built-in (vs 20+ in ECharts)
  2. Less customizable: No arbitrary shapes
  3. Canvas limitations: Can’t CSS style elements
  4. No geographic maps: Need plugins

Chart.js vs Alternatives#

AspectChart.jsRechartsECharts
Setup timeMinutesMinutesHours
Bundle~20-60KB~100KB~100KB+
Chart types81220+
RenderingCanvasSVGCanvas/WebGL
CustomizationModerateLimitedHigh
React-firstNoYesNo

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#

LibraryStarsWeekly DLRenderingReact-First
D3.js108K4.5MSVG/CanvasNo
Chart.js64K3MCanvasNo
ECharts61K1MCanvas/WebGLNo
Recharts25.6K9MSVGYes
visx19K350KSVGYes
Nivo13.6K500KSVG/CanvasYes
Victory11.2K284KSVGYes

Feature Matrix#

FeatureRechartsChart.jsEChartsvisxNivoVictoryD3
Standard charts★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
Custom viz★★★★★★★★★★★★★★★★★★★★★★★
Large datasets★★★★★★★★★★★★★★★★★★★★★★
Learning curve★★★★★★★★★★★★★★★★★★★★★★★★
TypeScript★★★★★★★★★★★★★★★★★★★★★★★★★★★★
React Native
SSR
3D charts
Geographic

Rendering Technology#

RendererLibrariesData Point LimitInteractivity
SVGRecharts, visx, Victory, Nivo~1000Full DOM access
CanvasChart.js, ECharts~50,000Event-based
WebGLECharts-GL, D3100K+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#

PriorityBest Choice
React ecosystemRecharts
SimplicityChart.js
SSR neededNivo

Large Datasets (1000+ points)#

PriorityBest Choice
PerformanceECharts
SimplicityChart.js
ControlD3 + Canvas

Custom Visualizations#

PriorityBest Choice
React projectvisx
Any projectD3
Network graphsD3/visx

Cross-Platform (React + React Native)#

PriorityBest Choice
Same APIVictory
Only optionVictory

Enterprise Requirements#

PriorityBest Choice
Commercial supportHighcharts
Feature-richECharts
CommunityRecharts

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):   ████████                      ~25KB

Migration Paths#

From Chart.js to React#

  • Use react-chartjs-2 wrapper
  • 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#

MetricValue
GitHub Stars108,000
npm Weekly Downloads~4.5M
RenderingSVG/Canvas/HTML
LicenseISC
CreatorMike 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#

ModulePurpose
d3-scaleMap data to visual values
d3-axisCreate axes
d3-shapeLines, areas, pies, arcs
d3-hierarchyTrees, treemaps, pack
d3-forceForce-directed layouts
d3-geoGeographic projections
d3-transitionAnimations
d3-selectionDOM 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 components

When 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:

LevelTimeWhat You Can Build
Beginner1-2 weeksBasic bar chart
Intermediate1-2 monthsCustom interactive charts
Advanced6+ monthsComplex animations, custom layouts

Resources#


Apache ECharts#

“An open-source JavaScript visualization library.”

Quick Facts#

MetricValue
GitHub Stars61,000
npm Weekly Downloads~1M
RenderingCanvas/SVG/WebGL
LicenseApache 2.0
MaintainerApache Foundation

Why ECharts?#

ECharts is the performance champion for large datasets:

  1. Canvas/WebGL: Handles 10K-100K+ data points
  2. Feature-rich: 20+ chart types, maps, 3D
  3. Enterprise-ready: Used by major companies
  4. Mobile-optimized: Small, modular bundle

When to Choose ECharts#

ScenarioWhy ECharts
1000+ data pointsCanvas beats SVG
50000+ data pointsWebGL support
Real-time dashboardsEfficient updates
Geographic mapsBuilt-in support
3D visualizationsECharts-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#

  1. Complex API: Many options to learn
  2. React wrapper outdated: May need custom integration
  3. Bundle size: Full library is large (use modular imports)
  4. Styling: Less flexible than D3/visx

ECharts vs Alternatives#

AspectEChartsRechartsD3
PerformanceExcellentGoodDepends
Chart variety20+10Unlimited
Learning curveModerateEasySteep
CustomizationGoodLimitedMaximum
React integrationOKExcellentManual

Resources#


Nivo#

“Supercharged React dataviz components, built on top of D3.”

Quick Facts#

MetricValue
GitHub Stars13,600
npm Weekly Downloads~500K
RenderingSVG/Canvas/HTML
LicenseMIT

What Sets Nivo Apart?#

Nivo offers unique features not found in other React chart libraries:

  1. Server-Side Rendering: API for rendering charts on server
  2. Multiple renderers: SVG, Canvas, and HTML
  3. Theming: Deep theme customization
  4. Motion: Built-in smooth animations
  5. 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:

RendererBest For
SVGInteractivity, small datasets
CanvasLarge datasets, performance
HTMLAccessibility, SEO
import { ResponsiveBar } from '@nivo/bar'       // SVG
import { ResponsiveBarCanvas } from '@nivo/bar' // Canvas

Limitations#

  1. Smaller community: Less tutorials, fewer Stack Overflow answers
  2. Bundle size: Larger than Recharts
  3. Learning curve: Many options to configure
  4. React only: No vanilla JS option

Nivo vs Recharts#

AspectNivoRecharts
Downloads500K/week9M/week
SSRBuilt-in APIManual
Chart variety20+12
ThemingDeepBasic
Learning curveModerateEasy
CommunitySmallerLarger

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#

MetricValue
GitHub Stars25,600
npm Weekly Downloads~9M
RenderingSVG
LicenseMIT
React RequirementYes

Why Recharts Dominates#

Recharts is the default choice for React charts:

  1. Most popular: 9M weekly downloads (32x more than Nivo)
  2. Declarative: JSX-based API feels like React
  3. D3 foundation: Solid math under the hood
  4. TypeScript: Excellent type support
  5. 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#

  1. SVG only: Slows with 1000+ data points
  2. Limited chart types: No network graphs, force layouts
  3. Animation limits: Less control than D3
  4. 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 → Highcharts

The Default: Recharts#

For most React dashboards, use Recharts:

npm install recharts

Why:

  • 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 recharts

2. 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-react

Or 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/axis

Pick 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-svg

Same code, both platforms.


5. Server-Side Rendered Charts#

Recommended: Nivo

Unique SSR API for generating charts on server:

npm install @nivo/line @nivo/bar

Use case: Email reports, PDFs, static sites.


6. Simple Charts, Any Framework#

Recommended: Chart.js

Fastest setup, works everywhere:

npm install chart.js

For 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#

LibraryUse For
RechartsReact dashboards
Chart.jsQuick charts, any framework

Tier 2: Specific Needs#

LibraryUse For
EChartsLarge datasets, 3D, maps
visxCustom visualizations
VictoryReact + React Native
NivoSSR, variety

Tier 3: Special Cases#

LibraryUse For
D3.jsMaximum control, custom everything
HighchartsEnterprise, commercial support
PlotlyScientific, 3D

Performance Summary#

Data SizeRecommendedWhy
< 500RechartsSimplicity
500-5000Chart.jsCanvas faster
5000-50000EChartsOptimized Canvas
50000+ECharts-GLWebGL 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#

MetricValue
GitHub Stars11,200
npm Weekly Downloads~284K
RenderingSVG
LicenseMIT
MaintainerNearform (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#

ScenarioWhy Victory
React + React NativeSame charting code
Composable chartsModular component system
ThemeableBuilt-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#

  1. SVG only: Can slow with large datasets
  2. Smaller ecosystem: Fewer plugins than Chart.js
  3. Less popular: 284K vs 9M (Recharts)
  4. Learning curve: API different from other libraries

Victory vs Alternatives#

AspectVictoryRechartsChart.js
React Native
ComposabilityExcellentGoodLimited
CommunitySmallerLargeHuge
PerformanceSVGSVGCanvas

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#

MetricValue
GitHub Stars19,000
npm Weekly Downloads~350K
RenderingSVG
LicenseMIT
MaintainerAirbnb
Production Use3+ 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?#

Problemvisx Solution
D3 + React DOM conflictD3 calculates, React renders
D3 learning curveReact component API
High-level libraries too limitingLow-level primitives
Bundle size concernsPick only packages you need

The visx Philosophy#

  1. Un-opinionated: Bring your own styles, animations, state
  2. Modular: 30+ packages, use what you need
  3. Low-level: Primitives, not pre-made charts
  4. 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 more

Basic 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#

AspectD3visxRecharts
FlexibilityMaximumHighLimited
Learning curveSteepModerateEasy
React integrationManualNativeNative
Pre-made chartsNoneNoneMany
Bundle controlGoodExcellentAll-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#

  1. Recharts - Composable React components
  2. D3.js - Low-level DOM manipulation
  3. Chart.js - Imperative Canvas API
  4. ECharts - Configuration-driven rendering
  5. Nivo - React wrapper with SSR
  6. visx - React primitives for D3
  7. 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#

  1. SVG Layout Thrashing - Reading/writing layout repeatedly
  2. Animation Frame Budget - Exceeding 16ms per frame
  3. Memory Leaks - Not cleaning up event listeners
  4. 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 → Pixels

Detailed steps:

  1. Parse config - Validate chart type, data, options
  2. Calculate layout - Determine axis positions, label spacing
  3. Generate scales - Map data values to pixel coordinates
  4. Render elements - Draw axes, grid, data points, labels
  5. Register interactions - Set up hover/click handlers
  6. Animation loop - Interpolate values over time

Chart Types#

Built-in chart types (extensible):

Basic#

  • line - Time series, trends
  • bar - Comparisons (vertical/horizontal)
  • pie / doughnut - Part-to-whole
  • radar - Multivariate data
  • polarArea - Circular bar chart
  • scatter - X-Y relationships
  • bubble - 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 PointsRender TimeFrame Rate
100~3ms333 FPS
1,000~12ms83 FPS
10,000~80ms12 FPS
50,000~450ms2 FPS

Performance ceiling: ~10,000 points for smooth interaction

Why Canvas is Faster than SVG#

  1. No DOM overhead - Single Canvas element, not 1000s of SVG nodes
  2. Direct pixel manipulation - Bypasses layout/paint pipeline
  3. 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, afterInit
  • beforeUpdate, afterUpdate
  • beforeDraw, afterDraw
  • beforeDatasetDraw, 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 point
  • nearest - Closest point to cursor
  • index - All points at same x-index
  • dataset - All points in dataset
  • x / 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:

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#

  1. Canvas performance - Handles 10K points smoothly
  2. Simple API - Config object easier than D3
  3. Small bundle - 60 KB gzipped
  4. Great docs - Comprehensive, well-organized
  5. Active maintenance - Regular releases

Key Limitations#

  1. Less flexible than D3 - Opinionated chart types
  2. Accessibility - Canvas harder for screen readers
  3. Customization limits - Harder to style than SVG
  4. 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, nests
  • d3-random - Random number generators

Scales and Axes#

  • d3-scale - Map data → visual values
  • d3-axis - Generate axis elements
  • d3-time - Time intervals, formatting

Shapes and Layouts#

  • d3-shape - Line, area, arc, pie generators
  • d3-chord - Chord diagrams
  • d3-hierarchy - Tree layouts, treemaps, partitions
  • d3-force - Force-directed graph layouts
  • d3-sankey - Flow diagrams

DOM Manipulation#

  • d3-selection - Select and manipulate elements
  • d3-transition - Animated transitions
  • d3-drag - Drag-and-drop
  • d3-zoom - Pan and zoom

Geographic#

  • d3-geo - Map projections
  • d3-geo-projection - Extended projections

Utilities#

  • d3-fetch - Load CSV, JSON, etc.
  • d3-format - Number formatting
  • d3-interpolate - Interpolation functions
  • d3-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:

  1. Enter: Create missing elements (4 data points, 0 circles → create 4)
  2. Update: Update existing elements (4 data points, 4 circles → update 4)
  3. 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 nodes
  • forceCenter - Pull toward center point
  • forceCollide - Collision detection
  • forceX / forceY - Pull toward axis
  • forceRadial - 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 gzipped

Best 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#

  1. Maximum flexibility - Can create ANY visualization
  2. Modular - Import only what you need
  3. Battle-tested - 13 years, 108K GitHub stars
  4. Excellent docs - Observable notebooks, examples

Key Limitations#

  1. Steep learning curve - Takes weeks to master
  2. Verbose - More code than high-level libraries
  3. No built-in charts - You build everything from scratch
  4. 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/DOM

ZRender 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#

RendererData PointsRender TimeInteractive
Canvas10,000~50msYes (smooth)
Canvas100,000~200msYes (60 FPS)
Canvas1,000,000~1.5sLaggy
WebGL1,000,000~300msYes (smooth)
WebGL10,000,000~2sYes (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#

  1. Massive datasets - 10M points with WebGL
  2. 100+ chart types - Most comprehensive library
  3. Enterprise features - Data zoom, export, theming
  4. Great docs - Chinese + English, many examples
  5. Active development - Apache Foundation backing

Key Limitations#

  1. Large bundle - 320 KB full, 150 KB tree-shaken
  2. Complex API - Steep learning curve
  3. Imperative - Not React-idiomatic (config objects)
  4. 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#

LibraryRendererMax ElementsFrame Rate @ 10K
RechartsSVG~10008 FPS (choppy)
D3SVG/Canvas~1000 SVG, ~50K Canvas12 FPS (Canvas)
Chart.jsCanvas~10,00012 FPS
EChartsCanvas/SVG/WebGL10M+ (WebGL)60 FPS (WebGL)
NivoSVG~10008 FPS
visxSVG~10008 FPS
VictorySVG~10008 FPS

Key insight: ECharts is the only library that handles massive datasets smoothly.

Bundle Size (gzipped)#

LibraryFull BundleTree-ShakenNotes
Chart.js60 KBN/ANo tree-shaking
visx60 KB30-60 KBExcellent tree-shaking
D370 KB15-50 KBExcellent tree-shaking
Recharts130 KB100-130 KBLimited tree-shaking
ECharts320 KB150-200 KBModerate tree-shaking
Nivo180 KB150 KBLimited tree-shaking
Victory210 KB180 KBLimited tree-shaking

Winner: Chart.js (smallest) and visx (best tree-shaking)

API Paradigm#

LibraryParadigmExample
RechartsDeclarative (JSX)<LineChart><Line /></LineChart>
Chart.jsImperative (config)new Chart(ctx, { type: 'line' })
EChartsImperative (config)chart.setOption({ series: [...] })
D3Imperative (method chain)d3.select().data().join()
visxDeclarative (JSX)<LinePath data={data} />
NivoDeclarative (JSX)<ResponsiveLine data={data} />
VictoryDeclarative (JSX)<VictoryLine data={data} />

React projects: Prefer declarative (Recharts, visx, Nivo, Victory) Framework-agnostic: Use imperative (Chart.js, ECharts, D3)

TypeScript Support#

LibraryType CoverageNotes
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 TypeRechartsChart.jsEChartsD3visxNivoVictory
Line
Bar
Pie/Donut
Scatter
Area
Radar
Heatmap
Treemap
Sankey
Network
3D✅ (GL)
Geographic

Winner: ECharts (most comprehensive), D3 (fully customizable)

Interactivity Features#

FeatureRechartsChart.jsEChartsD3visxNivoVictory
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#

LibraryEngineEasing FunctionsStagger Support
RechartsCSS/JS5
Chart.jsCustom15+
EChartsCustom10+
D3Custom (transitions)15+
visx❌ (use external)N/AN/A
Nivoreact-springMany
VictoryCustom5

Winner: D3 (most powerful), Nivo (smoothest with react-spring)

Responsive Design#

LibraryAuto-ResizeContainer QueryAdaptive Layout
Recharts<ResponsiveContainer>
Chart.jsresponsive: true
EChartschart.resize()✅ Media queries
D3❌ Manual
visx<ParentSize>
Nivo<Responsive*>
Victory<VictoryContainer>

Winner: ECharts (media query support)

Accessibility#

LibrarySVG OutputARIA SupportScreen ReaderKeyboard Nav
Recharts❌ ManualPartial❌ Manual
Chart.js❌ Canvas
ECharts✅ (SVG mode)✅ Built-in
D3❌ Manual❌ Manual
visx❌ Manual❌ Manual
Nivo❌ ManualPartial❌ Manual
Victory✅ Built-in

Winner: Victory, ECharts (best ARIA support)

Cross-Platform Support#

LibraryReactReact NativeVueAngularVanilla 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)#

LibrarySSR SupportHydrationNotes
Recharts⚠️ PartialIssuesNeeds isomorphic-style-loader
Chart.jsN/ACanvas requires browser
EChartsGoodServer-side Canvas via node-canvas
D3GoodCan generate SVG server-side
visxGoodPure SVG, works well
NivoExcellentBuilt for SSR
Victory⚠️ PartialIssuesComplex setup

Winner: Nivo (built for SSR), visx, D3

Testing Support#

LibraryUnit TestingVisual RegressionSnapshot 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#

AspectRechartsChart.jsEChartsD3visxNivoVictory
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#

ScenarioBest ChoiceRunner-Up
React dashboard (< 1K points)RechartsNivo
Large datasets (10K+ points)EChartsChart.js
Custom visualizationD3visx
Minimal bundle sizeChart.jsvisx
Server-side renderingNivovisx
React NativeVictory-
Framework-agnosticChart.jsECharts
TypeScript projectRechartsvisx
Accessibility priorityVictoryECharts
Maximum chart varietyEChartsD3

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 interpolation
  • d3-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 PointsRender TimeFrame Rate
100~5ms200 FPS
500~20ms50 FPS
1000~50ms20 FPS
2000~120ms8 FPS (choppy)
5000~400msUnusable

Performance wall: ~1000 SVG elements

Why SVG Slows Down#

  1. Layout thrashing: Browser recalculates layout for each element
  2. Paint complexity: Each element painted separately
  3. Memory overhead: Each DOM node has event listeners, properties
  4. GC pressure: Creating/destroying many objects

Optimization Strategies#

1. Data Sampling

const sampledData = data.length > 1000
  ? sampleData(data, 1000)
  : data

2. 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 KB

Tree-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-out
  • linear
  • 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#

  1. 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 />} />
  1. Custom Labels
const CustomLabel = ({ x, y, value }) => (
  <text x={x} y={y} textAnchor="middle">{value}</text>
)

<Line label={<CustomLabel />} />
  1. Custom Tooltips (as seen earlier)

  2. 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#

  1. Event Listeners
// Recharts handles this internally
useEffect(() => {
  const handler = () => {}
  element.addEventListener('mousemove', handler)
  return () => element.removeEventListener('mousemove', handler)
}, [])
  1. Animation Frames
  • Recharts cleans up on unmount
  • Manual animations need cleanup
  1. 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.0
  • react-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#

  1. React-native API - JSX feels natural
  2. D3 math - Solid calculations
  3. TypeScript - Full type safety
  4. Composition - Mix chart types easily

Key Limitations#

  1. SVG only - Performance ceiling at 1000 points
  2. Limited chart types - No network graphs, Sankey diagrams
  3. Animation control - Less flexible than D3
  4. 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)

  • useTooltip hook, Tooltip component, TooltipWithBounds

Event (@visx/event)

  • localPoint - Get mouse coordinates relative to SVG
  • Touch event helpers

Responsive (@visx/responsive)

  • ParentSize - Measure parent container
  • ScaleSVG - 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 PointsRender 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 gzipped

Comparison:

  • 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#

  1. React-native - Components, not config objects
  2. Flexible - Build any visualization
  3. Modular - Tree-shakeable, small bundle
  4. Type-safe - Excellent TypeScript support
  5. D3 power - All D3 capabilities available

Key Limitations#

  1. More code - Requires building charts from scratch
  2. No built-in interactivity - Must implement tooltips, legends, etc.
  3. Steeper learning curve - Need D3 knowledge
  4. 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#

AspectvisxRecharts
AbstractionLow-level primitivesHigh-level components
CodeMore verboseMore concise
FlexibilityMaximumLimited
Learning curveSteeperGentler
Bundle size60 KB130 KB
Built-in featuresMinimalTooltips, legends, etc.
Best forCustom chartsStandard 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)
  • Which library best fits their needs
  • Why it’s the right choice
  • What trade-offs they accept

User Personas Identified#

  1. React Dashboard Developers - Building internal analytics tools
  2. Data Scientists - Visualizing large datasets
  3. Custom Visualization Designers - Creating novel, branded charts
  4. Mobile App Developers - Cross-platform React Native charts
  5. 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#

PersonaPrimary NeedRecommended LibraryWhy
React Dashboard DeveloperFast development, standard chartsRechartsJSX API, built-in features, 9M downloads
Data ScientistLarge datasets, interactivityECharts100K+ points, Canvas/WebGL, data zoom
Custom DesignerPixel-perfect, unique chartsvisxD3 + React, full control, small bundle
Mobile DeveloperReact Native, touch gesturesVictory NativeNative support, touch interactions
Full-Stack (SSR)Server-side renderingNivoBuilt for SSR, beautiful defaults
Agency (Multi-framework)Works everywhereChart.js60 KB, framework-agnostic, simple
Data JournalistCustom storytellingD3Novel 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:

  1. Migrate one chart at a time
  2. Run both libraries temporarily
  3. 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:

  1. Keep Recharts for standard charts
  2. Use visx only for custom visualizations
  3. 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:

  1. Keep D3 scales and math
  2. Replace .select().join() with JSX
  3. Migrate chart by chart

Final Recommendations by Priority#

Priority 1: Match Your Data Size#

Data PointsLibrary
< 1000Any SVG library (Recharts, visx, Nivo)
1K-10KCanvas library (Chart.js, ECharts)
10K+ECharts (Canvas or WebGL)

Most common mistake: Using SVG for large datasets.

Priority 2: Match Your Framework#

FrameworkLibrary
React (standard charts)Recharts
React (custom charts)visx
React NativeVictory Native
Vue/Angular/NoneChart.js or ECharts

Most common mistake: Fighting framework integration.

Priority 3: Match Your Team Skills#

ExperienceLibrary
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#

TimelineLibrary
DaysRecharts, Chart.js
WeeksECharts, Nivo, Victory
Monthsvisx, D3

Most common mistake: Picking D3 with tight deadline.

Conclusion#

The “best” library is the one that:

  1. Handles your data size
  2. Works in your framework
  3. Matches your team’s skills
  4. Fits your timeline
  5. 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#

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.

Library: visx + react-spring (for animations)

Why:

  1. Full control - Every SVG element customizable
  2. React-native - JSX, not D3 DOM mutations
  3. D3 power - Scales and shapes without reimplementing
  4. Small bundle - Tree-shakeable (30-60 KB)
  5. 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
  • 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#

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.

Library: ECharts (with echarts-gl for 3D)

Why:

  1. Performance at scale - 100K points smoothly, 1M+ with WebGL
  2. Built-in controls - Data zoom, pan, export (saves weeks)
  3. Rich chart library - Heatmaps, 3D, geo maps all built-in
  4. Framework-agnostic - Minimal JavaScript knowledge needed
  5. 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)
  • 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#

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.

Library: Victory Native

Why:

  1. Built for React Native - Not a web port, native-first
  2. Touch-optimized - Zoom, pan containers built-in
  3. Cross-platform - Same code for iOS + Android
  4. Consistent API - If team knows Victory (web), easy to learn
  5. 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)
  : data

4. 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)
  • 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#

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.

Library: Recharts

Why:

  1. Fastest time to value - JSX API is natural for React developers
  2. Mature ecosystem - 9M downloads, active maintenance, many examples
  3. Covers all needs - Line, bar, pie + tooltips, legends, responsive
  4. TypeScript first-class - Excellent type coverage
  5. 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
  • 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
  • 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:

  1. Vendor abstraction: Wrap library behind interface
  2. Standard APIs: Prefer libraries using web standards
  3. Data portability: Use standard data formats (JSON, CSV)
  4. 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

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:

  1. D3 - Will outlive us all, rendering-agnostic
  2. Recharts - React ecosystem leader
  3. 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)

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#

AspectD3ECharts
Risk Level🟢 Lowest🟢 Low
BackingObservable (company)Apache Foundation
Longevity13 years, will last decades12 years, Apache ensures longevity
CommunityMassive (108K stars)Large (61K stars)
InnovationSlow, stableModerate, modernizing
Lock-in riskLow (math is portable)Medium (config API proprietary)
Future-proofingRendering-agnosticWebGL, positioned for WebGPU
Migration costHigh (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)

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

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:

  1. Wrap Recharts in abstraction layer (optional)
  2. Monitor download trends quarterly
  3. Keep dependencies updated
  4. 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#

LibraryRisk Level5-Year Viability10-Year Viability
D3🟢 Lowest99%95%
ECharts🟢 Low95%90%
Recharts🟢 Low90%70%
Chart.js🟢 Low90%75%
visx🟡 Medium80%60%
Nivo🟡 Medium75%50%
Victory🟡 Medium70%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

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):

  1. D3 (maximum longevity)
  2. ECharts (Apache backing, large data)
  3. Recharts (React ecosystem leader)

Balanced (most teams, 3-5 year projects):

  1. Recharts (React, < 1K points)
  2. ECharts (large data)
  3. visx (custom charts)
  4. Chart.js (framework-agnostic)

Aggressive (startups, 1-2 year projects):

  1. Best tool for job (optimize for speed)
  2. Monitor ecosystem
  3. 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#

ScenarioLibraryWhy
Default React dashboardRechartsSafest, fastest
Maximum longevityD3Will outlast frameworks
Large datasetsEChartsOnly option for 1M+ points
Custom visualizationsvisxD3 power + React patterns
Framework-agnosticChart.jsSmallest, simplest
React NativeVictory NativeOnly viable option

Conclusion#

The safest long-term bets:

  1. D3 - Infrastructure-level, will last decades
  2. ECharts - Apache backing, future-proof rendering
  3. 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.

Published: 2026-03-06 Updated: 2026-03-06