mrkeyoor.com_
Sat 08 Aug 20:59 UTC
npmWeb Frontendupdated 08 Aug 2026

echarts

Apache ECharts is a browser visualization engine that turns a declarative option object into interactive Canvas or SVG charts. It covers familiar bars, lines, pies, and scatter plots as well as heatmaps, graphs, trees, treemaps, sankeys, gauges, maps, brushing, data zoom, timelines, datasets, and custom series. The same instance API handles rendering, events, animated updates, export, linked charts, and server-rendered SVG. Version 6 is framework-neutral JavaScript with TypeScript declarations, built on the separate zrender graphics engine.

Verdict

ECharts is the best open-source default when a dashboard will outgrow basic charts and you are willing to manage its instance lifecycle and option model. Use tree-shaken imports from day one; for a handful of simple charts, Chart.js or uPlot costs less in bundle size and mental overhead.

API stability4/5The option-object model, init, setOption, resize, event, action, and dispose APIs have remained recognizable across major versions, and version 6 provides a detailed migration guide plus a v5 compatibility theme. It is not a perfect five because version 6 intentionally changed default colors, component positions, axis label behavior, and other visuals that can alter production charts without type errors.
Docs5/5The official site separates a conceptual handbook, exhaustive option manual, API reference, and a large interactive example gallery. It documents tree-shaken imports, the required renderer registration, container sizing, ResizeObserver, disposal, ARIA, Canvas and SVG tradeoffs, server rendering, and version 6 migration details with runnable code rather than only listing types.
Maintenance4/5Version 6.1.0 shipped on May 19, 2026 and the Apache repository was pushed on August 4, 2026, with continuous-integration status visible in the README. The project is clearly active, but GitHub search showed 1,385 open issues and the repository API counted 1,557 open issues and pull requests together, so a niche bug can wait behind a very large queue.
Ecosystem5/5ECharts recorded 4,509,588 downloads for the measured week and has 67,008 GitHub stars. It has maintained wrappers for major UI frameworks, extensions for WebGL, word clouds, statistics, and map providers, hundreds of examples, CDN builds, themes, locale files, and enough chart types that teams rarely need a second visualization engine.

Use it if

  • You need many chart types, interactions, annotations, coordinate systems, and visual encodings behind one consistent option model
  • Your dashboard needs data zoom, brushing, linked charts, animated transitions, large-series optimizations, or custom rendering
  • You want to choose Canvas or SVG per chart and may also render SVG strings on the server
  • You need a permissive Apache-2.0 library with a large community, extensive examples, and no commercial feature tier
Skip it if

Setup reality

npm install echarts gives you ESM, CommonJS, TypeScript declarations, zrender, and tslib with no peer dependencies. The tempting import * as echarts from 'echarts' includes every chart and component and measures 368.0 KB gzipped. Production apps should import echarts/core, register only the chart types and components they use with echarts.use(), and explicitly register either CanvasRenderer or SVGRenderer because the tree-shakeable core includes neither. A chart container must already have nonzero width and height before echarts.init(); flex and hidden-tab layouts are frequent sources of blank charts. ECharts does not automatically follow every container resize, so attach ResizeObserver and call chart.resize(), then disconnect the observer and chart.dispose() when the view unmounts. setOption merges by default. That is convenient for incremental data, but removed series can survive unless you use replaceMerge: ['series'] or notMerge for a full replacement. Stable series IDs make animated updates more predictable. Framework wrappers need to avoid initializing twice on the same DOM node and must unregister event handlers or dispose the instance. Tree-shaken builds also need every optional feature registered: setting aria.show does nothing without AriaComponent, dataZoom needs its component, and an SVG chart needs SVGRenderer. Maps are not bundled as geographic data; load GeoJSON and call registerMap yourself. Server-side SVG requires renderer: 'svg', ssr: true, explicit dimensions, renderToSVGString(), and disposal. Canvas server rendering additionally needs a Canvas implementation such as node-canvas. Version 6 changed visual defaults, including theme colors and legend placement, so screenshot-test a v5 migration or initialize with the supplied v5 theme when preserving appearance is more important than adopting the new defaults.

Patterns

Register only the features you useconfigure-tree-shaking

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

echarts.use([
  BarChart, LineChart, AriaComponent, DatasetComponent, DataZoomComponent,
  GridComponent, LegendComponent, TitleComponent, TooltipComponent, LabelLayout,
  UniversalTransition, CanvasRenderer,
]);

The core entry point registers nothing automatically, including the renderer. Missing registrations usually produce an empty feature rather than a useful compile error.

Render a basic bar chartrender-bar-chart

const element = document.getElementById('sales-chart');
if (!element) throw new Error('chart container is missing');

const chart = echarts.init(element);
chart.setOption({
  title: { text: 'Weekly sales' },
  tooltip: {},
  xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },
  yAxis: { type: 'value' },
  series: [{ id: 'sales', name: 'Sales', type: 'bar', data: [12, 20, 15, 8, 17] }],
});

The container needs a real width and height before init. Register BarChart, GridComponent, TitleComponent, TooltipComponent, and a renderer when using the core build.

Plot timestamped line datarender-time-series

chart.setOption({
  tooltip: { trigger: 'axis' },
  xAxis: { type: 'time' },
  yAxis: { type: 'value', scale: true },
  series: [{
    id: 'temperature',
    type: 'line',
    showSymbol: false,
    data: readings.map((row) => [row.timestamp, row.value]),
  }],
});

Use timestamps or parseable dates on a time axis. scale: true prevents a value axis from always forcing zero into the visible range.

Share one dataset across several seriesbind-tabular-dataset

chart.setOption({
  legend: {},
  tooltip: { trigger: 'axis' },
  dataset: {
    source: [
      ['month', 'Revenue', 'Cost'],
      ['Jan', 120, 80],
      ['Feb', 150, 92],
      ['Mar', 170, 110],
    ],
  },
  xAxis: { type: 'category' },
  yAxis: {},
  series: [
    { id: 'revenue', type: 'bar', encode: { x: 'month', y: 'Revenue' } },
    { id: 'cost', type: 'line', encode: { x: 'month', y: 'Cost' } },
  ],
});

Register DatasetComponent in a core build. Named dimensions make encode mappings easier to review than positional column numbers.

Replace live series without leaving stale onesreplace-live-series

chart.setOption(
  {
    series: streams.map((stream) => ({
      id: stream.id,
      name: stream.label,
      type: 'line',
      showSymbol: false,
      data: stream.points,
    })),
  },
  { replaceMerge: ['series'], lazyUpdate: true },
);

setOption merges by default. replaceMerge removes series no longer present, while stable IDs preserve sensible matching and transitions for retained series.

Handle clicks and remove the handler laterhandle-chart-click

const onBarClick = (params) => {
  if (params.componentType === 'series' && params.seriesType === 'bar') {
    openDetails(params.name, params.value);
  }
};

chart.on('click', onBarClick);

// During teardown when the chart instance is retained:
chart.off('click', onBarClick);

Register the same function reference with off. Calling on again after every render without cleanup stacks duplicate handlers.

Resize when the chart container changesresize-with-container

const observer = new ResizeObserver((entries) => {
  const { width, height } = entries[0].contentRect;
  if (width > 0 && height > 0) chart.resize();
});
observer.observe(chartElement);

function cleanup() {
  observer.disconnect();
  chart.dispose();
}

A window resize listener misses sidebar, grid, and tab layout changes. Do not call resize with positional width and height arguments; use resize() or an options object.

Add slider and wheel zoomingadd-data-zoom

chart.setOption({
  xAxis: { type: 'time' },
  yAxis: { type: 'value' },
  dataZoom: [
    { type: 'inside', xAxisIndex: 0, filterMode: 'filter' },
    { type: 'slider', xAxisIndex: 0, bottom: 8 },
  ],
  grid: { bottom: 70 },
  series: [{ id: 'requests', type: 'line', data: points }],
});

Register DataZoomComponent in the core build. Leave enough grid space for the slider or it will overlap axis labels.

Downsample a dense line seriesdownsample-large-line

chart.setOption({
  animation: false,
  xAxis: { type: 'time' },
  yAxis: { type: 'value' },
  series: [{
    id: 'telemetry',
    type: 'line',
    data: points,
    showSymbol: false,
    sampling: 'lttb',
    large: true,
    largeThreshold: 2000,
  }],
});

Sampling changes the rendered representation, not the source array. Test tooltips and visual fidelity because large mode disables or simplifies some item-level effects.

Enable an ARIA description and decalsenable-accessibility

chart.setOption({
  title: { text: 'Revenue by quarter' },
  aria: {
    enabled: true,
    decal: { show: true },
    description: 'Quarterly revenue in thousands of dollars for 2025.',
  },
  xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
  yAxis: { type: 'value' },
  series: [{ type: 'bar', data: [120, 135, 128, 160] }],
});

Register AriaComponent first. Add an adjacent HTML table or equivalent for exact values and test keyboard and screen-reader flows rather than relying on one generated label.

Render a chart to an SVG string on the serverrender-svg-on-server

import * as echarts from 'echarts/core';
import { BarChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers';

echarts.use([BarChart, GridComponent, SVGRenderer]);

const chart = echarts.init(null, null, {
  renderer: 'svg',
  ssr: true,
  width: 800,
  height: 400,
});
try {
  chart.setOption(option);
  return chart.renderToSVGString();
} finally {
  chart.dispose();
}

SSR requires explicit dimensions and SVGRenderer. The returned SVG can animate initially, but ordinary browser interactions and tooltips need client code.

Dispose an instance during view teardowndispose-chart-instance

let chart = echarts.getInstanceByDom(chartElement);
if (!chart) chart = echarts.init(chartElement);

// when the route, component, or DOM node is removed
chart.dispose();

Initializing twice on the same element causes warnings and leaks. Dispose releases event handlers, rendering resources, and the DOM-to-instance association.

Alternatives

PackageRegistryPick it when
chart.jsnpmYou need common responsive chart types with a smaller API and a widely understood plugin model
vega-litenpmYou want a grammar of graphics and reproducible declarative specifications rather than an imperative chart instance
uplotnpmYou need very fast, compact time-series plotting and can accept far fewer chart types and built-in interactions