Back to blog
Guides

Comparing the most popular open-source charting libraries

Josselin Liebe
Josselin Liebe

If you need charts in a web app, you have plenty of open-source options. Too many, honestly. Each library makes different trade-offs around bundle size, customization, framework coupling, and learning curve.

Here is a practical breakdown of the five libraries that come up most often, with code examples for each. At the end, we will look at when client-side rendering is not the right approach at all.

Chart.js

Chart.js has been around since 2013 and still tops npm downloads in the charting category. It renders to <canvas>, ships 8 chart types out of the box, and has a plugin ecosystem large enough that you rarely need to build things from scratch.

The API is a single configuration object. You pass in type, data, and options, and Chart.js handles the rest:

import { Chart } from 'chart.js/auto';

new Chart(document.getElementById('myChart'), {
  type: 'bar',
  data: {
    labels: ['Jan', 'Feb', 'Mar', 'Apr'],
    datasets: [{
      label: 'Revenue ($k)',
      data: [42, 58, 37, 65],
      backgroundColor: '#6366f1'
    }]
  },
  options: {
    responsive: true,
    scales: { y: { beginAtZero: true } }
  }
});

The config format will look familiar if you have used ChartQuery's chart API, which accepts the same structure server-side.

Where it works well: dashboards, internal tools, any project where you need simple charts fast without a steep learning curve. The React wrapper (react-chartjs-2) is well maintained.

Where it falls short: highly custom or unconventional visualizations. Chart.js gives you 8 types and a plugin system, but if you want a treemap or a Sankey diagram, you are relying on community plugins of varying quality.

ECharts

ECharts is an Apache Foundation project used by Amazon, GitLab, and others. It ships 20+ chart types (box plots, heat maps, funnels, tree maps, Sankey, gauge, etc.) and renders to both Canvas and SVG.

What sets ECharts apart is the dataset approach. You feed in raw tabular data and the library figures out the mapping:

import * as echarts from 'echarts';

const chart = echarts.init(document.getElementById('myChart'));

chart.setOption({
  dataset: {
    source: [
      ['product', '2025', '2026'],
      ['Widgets', 42, 51],
      ['Gadgets', 58, 67],
      ['Gizmos',  37, 44]
    ]
  },
  xAxis: { type: 'category' },
  yAxis: {},
  series: [
    { type: 'bar' },
    { type: 'bar' }
  ]
});

ECharts automatically creates two bar series from the columns. No manual dataset wiring. For larger datasets, this saves real time.

Where it works well: complex dashboards with many chart types, map visualizations (built-in GeoJSON support), and projects that need accessibility features. ECharts generates chart descriptions automatically for screen readers.

Where it falls short: bundle size. The full package is ~1 MB minified. You can tree-shake, but it takes effort. The documentation is extensive but can be hard to navigate.

Plotly

Plotly is the go-to if your team works in Python, R, or Julia. The JavaScript version (plotly.js) builds on D3 and supports 40+ chart types, including 3D surfaces, statistical charts, and geographic maps.

A basic chart in Plotly looks like this:

import Plotly from 'plotly.js-dist-min';

Plotly.newPlot('myChart', [{
  x: ['Jan', 'Feb', 'Mar', 'Apr'],
  y: [42, 58, 37, 65],
  type: 'bar',
  marker: { color: '#6366f1' }
}], {
  title: 'Quarterly revenue',
  yaxis: { title: 'Revenue ($k)' }
});

The Python version integrates with Jupyter notebooks and works as standalone HTML files, which is hard to beat for data science workflows.

Where it works well: scientific and statistical visualization, teams that work across Python/R/JS, any project that needs 3D or geographic charts.

Where it falls short: bundle size is the biggest trade-off. plotly.js is over 3 MB minified. The plotly.js-dist-min package helps, but it is still heavy. Performance with thousands of data points can lag compared to Canvas-based libraries.

Nivo

Nivo is built on top of D3 but fully written in React. Not a wrapper around a non-React library. Actual React components with props for every configuration option.

import { ResponsiveBar } from '@nivo/bar';

function RevenueChart({ data }) {
  return (
    <ResponsiveBar
      data={data}
      keys={['revenue']}
      indexBy="month"
      margin={{ top: 20, right: 20, bottom: 40, left: 60 }}
      padding={0.3}
      colors={['#6366f1']}
      axisBottom={{ legend: 'Month' }}
      axisLeft={{ legend: 'Revenue ($k)' }}
    />
  );
}

Each chart type is a separate @nivo/* package, so you only ship what you use. The Storybook lets you prototype charts interactively before writing code.

Where it works well: React apps where you want declarative, responsive charts that follow React patterns (props, composition, theming). Server-side rendering works out of the box.

Where it falls short: React only. If your project uses Vue, Svelte, or vanilla JS, Nivo is not an option. Customization beyond what props expose requires understanding D3 internals.

visx

visx is Airbnb's collection of low-level visualization primitives for React. It is not a charting library in the traditional sense. There are no pre-built <BarChart> or <LineChart> components. Instead, you compose charts from primitives like <Bar>, <LinePath>, <Axis>, and <Group>.

import { Bar } from '@visx/shape';
import { scaleBand, scaleLinear } from '@visx/scale';

const data = [
  { month: 'Jan', value: 42 },
  { month: 'Feb', value: 58 },
  { month: 'Mar', value: 37 },
];

const xScale = scaleBand({ domain: data.map(d => d.month), padding: 0.3 });
const yScale = scaleLinear({ domain: [0, 65] });

function Chart({ width, height }) {
  xScale.rangeRound([0, width]);
  yScale.range([height, 0]);

  return (
    <svg width={width} height={height}>
      {data.map(d => (
        <Bar
          key={d.month}
          x={xScale(d.month)}
          y={yScale(d.value)}
          width={xScale.bandwidth()}
          height={height - yScale(d.value)}
          fill="#6366f1"
        />
      ))}
    </svg>
  );
}

That is a lot of code for three bars. On purpose. visx gives you full control over every pixel, which is the point.

Where it works well: custom visualizations where pre-built chart components are too rigid. Design-heavy products where charts need to match a specific aesthetic precisely.

Where it falls short: development speed. Building a standard bar chart with axes, tooltips, legends, and responsive behavior takes 10x more code than Chart.js. Not the right tool if you just need a dashboard.

Quick comparison

Chart.js ECharts Plotly Nivo visx
Bundle size ~200 KB ~1 MB ~3 MB Modular Modular
Chart types 8 20+ 40+ 15+ Build your own
Rendering Canvas Canvas + SVG SVG + WebGL SVG + Canvas SVG
Framework Any Any Any React only React only
Learning curve Low Medium Low-medium Low High
Best for Simple dashboards Complex dashboards Data science React apps Custom visualizations

When client-side rendering is not the right approach

All five libraries above share the same assumption: charts render in the browser. That works well for interactive dashboards, but there are common scenarios where it breaks down:

  • Emails and notifications. Email clients do not execute JavaScript. You need an image.
  • PDF reports. Generating charts inside a PDF pipeline means running a headless browser, which adds complexity and latency.
  • Slack/Teams bots. Chat platforms render images, not JS.
  • Backend systems. If a Python or Go service needs to attach a chart to an API response, pulling in a Node.js runtime just for rendering is clunky.

For these cases, a server-side chart API makes more sense. You send JSON, you get an image back. No browser, no DOM, no bundle size concerns.

That is exactly what ChartQuery does. The chart API accepts the same configuration format as Chart.js, but renders server-side and returns PNG, SVG, JPEG, or PDF:

curl -X POST https://api.chartquery.com/v1/chart \
  -H "Content-Type: application/json" \
  -d '{
    "type": "bar",
    "data": {
      "labels": ["Jan", "Feb", "Mar", "Apr"],
      "datasets": [{
        "data": [42, 58, 37, 65],
        "backgroundColor": "#6366f1"
      }]
    }
  }' \
  --output chart.png

No headless browser. No frontend dependencies. The same JSON you would pass to Chart.js works directly.

And if you need more than charts, ChartQuery also handles diagrams (28 languages including Mermaid and PlantUML), barcodes and QR codes, and labels from the same API. You can even describe what you want in plain English and let the AI generate the configuration. Try it in the Playground.

Picking the right tool

If you are building an interactive dashboard with hover states, zoom, and drill-down, use a client-side library. Chart.js for simplicity, ECharts for variety, Plotly for data science.

If you are embedding charts in emails, PDFs, Slack messages, or backend pipelines, skip the browser entirely and use a server-side chart API.

If you are migrating from a legacy service like Google Image Charts or QuickChart, check out the migration guide.