Monitors full of colorful charts with a large red Maine Coon cat resting nearby

ECharts: One Option Object to Chart Them All

The Orange Cat
The Orange Cat

Some charting libraries are happy to draw you a quick bar chart and call it a day. Apache ECharts is the one you reach for when "a quick bar chart" turns into a full analytics product with candlesticks, heatmaps, network graphs, choropleth maps, and live-updating streams that all need to stay buttery smooth. It is a free, powerful, interactive data-visualization library written in TypeScript, rendered through its own zrender graphics engine, and battle-tested at the scale of millions of weekly downloads.

The core idea is delightfully simple: you describe your entire chart — axes, series, legends, tooltips, colors, animations — as one nested JavaScript option object and hand it to a chart instance. ECharts renders it, wires up all the interactions for you, and intelligently diffs the next option you pass it. No manual event plumbing, no hand-rolled SVG. It started life at Baidu, was donated to the Apache Software Foundation, and graduated to a top-level Apache project in 2020. Today it sits comfortably in the top tier of data-viz libraries with roughly 66k GitHub stars and around 3.1 million npm downloads a week.

Why ECharts Earns Its Bundle Size

ECharts aims squarely at the middle-to-heavy end of charting needs, and it brings a lot to the table:

  • Rich interactions out of the box. Tooltips, legends, brushing, zooming, drag-to-select, cross-hairs, and animations are all configured declaratively. You describe what you want and ECharts handles the event wiring.
  • Big-data performance. Canvas rendering, dedicated large modes, and progressive rendering let it draw tens of thousands to millions of points without freezing the UI. This is its single biggest edge over SVG-based React libraries.
  • Breadth in one package. Line, bar, pie, scatter, candlestick, boxplot, heatmap, graph, tree, treemap, sunburst, sankey, funnel, gauge, radar, geo maps, and more — all under one consistent config model. No juggling a separate library per chart type.
  • Dashboard-grade tooling. Components like dataZoom, visualMap, toolbox, and brush exist specifically for the case where you have many charts, frequent updates, and a need for polished interactivity.

The trade-off versus D3 is less infinite flexibility in exchange for dramatically less code. The trade-off versus lighter libraries like Chart.js or Recharts is more power and performance in exchange for a larger bundle and a bigger config surface.

Getting It Into Your Project

ECharts ships with only two dependencies (zrender and tslib), so installation is painless.

npm install echarts

Or with yarn:

yarn add echarts

If you plan to use it inside React and want a ready-made wrapper, you can optionally add the community package too:

npm install echarts echarts-for-react

Your First Chart in Three Lines of Config

The fundamental loop is always the same: grab a DOM node, call echarts.init, and feed it an option.

import * as echarts from 'echarts';

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

chart.setOption({
  title: { text: 'Weekly Sales' },
  tooltip: {},
  xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
  yAxis: { type: 'value' },
  series: [{ type: 'bar', data: [120, 200, 150] }],
});

That single object covers the title, an empty tooltip (which is enough to enable hover tooltips), a categorical x-axis, a value y-axis, and one bar series. Updating is just as declarative — pass a new option and ECharts merges and diffs it:

chart.setOption({ series: [{ data: [99, 88, 77] }] });

One thing to internalize early: setOption merges by default. It does not replace. If you ever find stale series, leftover markLines, or ghost data points clinging on, the fix is almost always passing { notMerge: true } to wipe the slate clean before applying the new option.

Separating Data From Visuals With Datasets

A core design principle of ECharts is "provide the data once, then map it to visuals." Instead of reshaping your numbers for every chart type, you put them in a dataset and use encode to tell each series which dimensions map to which visual channels.

const option = {
  dataset: {
    source: [
      ['score', 'amount', 'product'],
      [89.3, 58212, 'Matcha Latte'],
      [57.1, 78254, 'Milk Tea'],
      [74.4, 41032, 'Cheese Cocoa'],
    ],
  },
  xAxis: { type: 'value' },
  yAxis: { type: 'category' },
  series: [
    {
      type: 'bar',
      encode: { x: 'amount', y: 'product', tooltip: ['score'] },
    },
  ],
};

Here the dataset.source holds the raw rows with a header. The series never copies that data — it just references dimensions by name through encode. Want to swap this bar chart for a scatter or a line? Change type and the dataset stays exactly the same. Multiple series can read from the same dataset, and seriesLayoutBy lets you flip between reading columns (the default) or rows.

Living Happily Inside React

ECharts has no first-party React component, which surprises people, but the manual glue is short and gives you total control. The recipe is: initialize once, update on prop changes, and — critically — dispose on unmount so you do not leak canvas instances.

import { useEffect, useRef } from 'react';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';

function Chart({ option }: { option: EChartsOption }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const chart = echarts.init(ref.current!);
    const onResize = () => chart.resize();
    window.addEventListener('resize', onResize);

    return () => {
      window.removeEventListener('resize', onResize);
      chart.dispose(); // critical: avoids memory leaks in SPAs
    };
  }, []);

  useEffect(() => {
    const chart = echarts.getInstanceByDom(ref.current!);
    chart?.setOption(option);
  }, [option]);

  return <div ref={ref} style={{ width: '100%', height: 400 }} />;
}

The first effect handles the lifecycle and never re-runs. The second one re-applies the option whenever it changes by grabbing the existing instance with getInstanceByDom. Two things bite newcomers here: forgetting chart.dispose() leaks zrender instances across every mount/unmount, and forgetting chart.resize() leaves charts that refuse to fill their flex or grid container.

If you would rather not write that yourself, the popular echarts-for-react wrapper handles init, dispose, and resize for you:

import ReactECharts from 'echarts-for-react';

<ReactECharts
  option={option}
  style={{ height: 400 }}
  notMerge
  lazyUpdate
  theme="dark"
  onEvents={{ click: (params) => console.log(params) }}
/>;

Just keep in mind it is a thin community-maintained wrapper, separate from the ECharts core, so it tracks core releases on its own schedule.

Taming the Bundle With Tree-Shaking

The convenient import * as echarts from 'echarts' pulls in everything — every chart type, every component — which lands around 500+ kB gzipped. For production you import from echarts/core and register only the pieces you actually use.

import * as echarts from 'echarts/core';
import { BarChart } from 'echarts/charts';
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';

echarts.use([
  BarChart,
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LabelLayout,
  UniversalTransition,
  CanvasRenderer,
]);

This brings a typical bar or line chart down to roughly 100 kB gzipped. The one rule you must never forget: explicitly register a renderer (CanvasRenderer or SVGRenderer). Leaving it out is the classic "nothing renders and there are no errors" bug. TypeScript users can take this further by composing a strict ComposeOption type from only the modules they imported, so autocomplete reflects exactly the chart you built. The official sample editor on the ECharts site can even generate the precise tree-shakeable import list for any chart you assemble.

Surviving Large and Streaming Data

This is where ECharts pulls ahead of SVG-based libraries. When you are plotting tens of thousands of points, two options keep things responsive:

const option = {
  xAxis: { type: 'value' },
  yAxis: { type: 'value' },
  series: [
    {
      type: 'scatter',
      large: true,
      largeThreshold: 2000,
      progressive: 5000,
      data: hugePointArray, // can be a typed array
    },
  ],
};

The large: true flag switches the series into an optimized batch-drawing path once it crosses largeThreshold points. progressive tells ECharts to render the dataset in chunks rather than all at once, keeping the main thread free so the UI never locks up. For live data, you can stream new points in with appendData and feed typed arrays directly, which avoids the overhead of rebuilding the whole option on every tick. Pair that Canvas rendering with dataZoom and you get a chart that stays smooth even while it ingests data in real time — exactly the scenario where SVG-based options start stuttering past a thousand points or a handful of updates per second.

A Few Things Worth Knowing

ECharts rewards a little upfront awareness. Resize handling is manual — there is no auto-resize, so a chart that does not fill its box almost always needs a chart.resize() call (or a ResizeObserver). The option object is enormous and deeply nested, and discoverability comes from the docs rather than autocomplete, so expect to keep the option reference open. Geo and map charts require you to register GeoJSON with echarts.registerMap(name, geoJson) before use, since map data is not bundled. And in single-page apps, always dispose charts on unmount. None of these are dealbreakers — they are just the lifecycle realities of a library this capable.

Should You Reach For It?

Choose Apache ECharts when you are building data-dense, interactive dashboards or analytics products and want one library that covers the full spectrum from a basic bar chart to a candlestick, a heatmap, a network graph, or a choropleth map — complete with built-in zoom, tooltips, brushing, smooth animations, and Canvas performance that survives large datasets and live updates. Reach for something lighter when you only need a couple of simple charts, want idiomatic React composition for standard dashboards, or need a fully bespoke custom visualization.

The price of admission is a large, deeply-nested config object, a sizable bundle you tame with echarts/core tree-shaking, and a bit of manual resize and dispose lifecycle management in React. For teams shipping serious dashboards, that trade is almost always worth it — you describe one option object, and ECharts does the rest.