Apache ECharts is a powerful, interactive charting and data-visualization library for the browser. It produces over 20 chart types — line, bar, pie, scatter, candlestick, radar, sankey, treemap, sunburst, gauge, geo maps, and a fully programmable custom series — backed by a Canvas/SVG render engine that scales to millions of data points. ECharts powers dashboards, business-intelligence platforms, scientific visualizations, and embedded analytics across the modern web.
This guide takes you from your first chart in five minutes to advanced workflows: declarative charts via the option object, dynamic updates, event handling, theming, internationalization, server-side rendering (SSR), responsive layout, and the on-disk file structure that lets you tree-shake unused chart types out of your production bundle.
1. Quick Start
1.1 In sixty seconds
The fastest way to see ECharts working is to copy this HTML file and open it in a browser. No build tools, no npm install, no server.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>ECharts Hello World</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width: 600px; height: 400px;"></div>
<script>
const myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
title: { text: 'ECharts Getting Started Example' },
tooltip: {},
xAxis: { data: ['shirt', 'cardigan', 'chiffon', 'pants', 'heels', 'socks'] },
yAxis: {},
series: [{
name: 'sales',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
});
</script>
</body>
</html>
A 600×400 chart appears with six bars labelled shirt through socks,
a title at the top, and a tooltip that appears on hover.
Important
The container
<div>must have an explicit width and height before you callecharts.init(). ECharts measures the DOM element to size the canvas. A height of zero produces an invisible chart and aCan't get DOM width or heightwarning in the console.
1.2 The three lines that draw every chart
Every ECharts chart is the same three steps:
- Init — create an instance bound to a DOM element:
echarts.init(dom). - setOption — pass a JavaScript object describing what to draw:
chart.setOption(option). - Update — call
setOption(...)again to mutate the chart, ordispatchAction(...)to trigger interaction.
The option object is the heart of ECharts. Every chart type, axis, legend, tooltip, color, animation, and event handler is configured by adding keys to that object. You will spend most of your time editing option, not calling functions on the chart instance.
Tip
Treat
optionas data, not code. Store it in JSON, fetch it from an API, generate it with a server-side template, or build it from a TypeScript type. ECharts is declarative — you describe the chart, not the steps to draw it.
1.3 What you get out of the box
ECharts 6.0.0 ships with:
- 23 series types — line, bar, pie, scatter, radar, map, tree, treemap, sunburst, sankey, gauge, funnel, graph, chord, parallel, candlestick, boxplot, effectScatter, lines, heatmap, pictorialBar, themeRiver, custom.
- Dual renderers — Canvas (default, fast for many points) and SVG (resolution-independent, better for accessibility and SSR).
- Seven coordinate systems — Cartesian (grid/xy), polar, geo (maps), single-axis, parallel, calendar, matrix.
- Rich interaction — tooltips, legends, data zoom (drag/wheel/pinch), brush selection, axis pointers, animation.
- 53 built-in locales for date formatting and labels.
- Accessibility via the
ariacomponent (live regions, screen-reader descriptions). - Server-side rendering producing a hydratable SVG string for static-first frameworks.
- A typed TypeScript API with
EChartsOptiontypings on every option key.
2. Installation
ECharts can be installed five ways. Pick by deployment target:
| Method | Use when |
|---|---|
npm install echarts |
Modern app with bundler (Vite, webpack, Rollup, Parcel). |
| CDN script tag | Static HTML, prototypes, CMS plugins, JSFiddle/CodePen. |
Local download (dist/echarts.js) |
Air-gapped or offline environments. |
Pre-bundled flavors (echarts/lib/...) |
Smallest possible bundle via tree-shaking. |
| SSR runtime | Node-side rendering for static sites or email. |
2.1 npm / yarn / pnpm
npm
npm install echarts
yarn
yarn add echarts
pnpm
pnpm add echarts
added 2 packages: echarts@6.0.0, zrender@6.0.0
zrender is ECharts' graphics engine and is pulled in automatically as a dependency. The tslib package is also included for TypeScript helper inlining.
Then in your code:
import * as echarts from 'echarts';
const chart = echarts.init(document.getElementById('main'));
chart.setOption({ /* ... */ });
Tip
The default
import * as echarts from 'echarts'imports every chart type and component — about 1 MB minified. For production bundles, switch to the tree-shaken imports shown in §2.4.
2.2 CDN
ECharts is published to two major CDNs.
jsDelivr (worldwide)
<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.min.js"></script>
unpkg
<script src="https://unpkg.com/echarts@6.0.0/dist/echarts.min.js"></script>
The package's unpkg and jsdelivr fields both point to dist/echarts.min.js, so the bare https://cdn.jsdelivr.net/npm/echarts URL also works.
Using a CDN exposes a global echarts namespace:
<script>
const chart = echarts.init(document.getElementById('main'));
</script>
Note
The CDN bundle is the
echarts.allflavor — every chart type, every component, every renderer. It is the slowest to download but requires zero configuration.
2.3 Standalone download
If you cannot use a CDN or registry, download the prebuilt files directly from the release page on GitHub and host them yourself. The dist/ folder of the release contains:
| File | Bundle | Size class | Renderers | Charts |
|---|---|---|---|---|
echarts.js / echarts.min.js |
all |
Largest (~1 MB min) | Canvas + SVG | All 23 |
echarts.common.js / .min.js |
common |
Medium (~470 KB min) | Canvas + SVG | Line, Bar, Pie, Scatter |
echarts.simple.js / .min.js |
simple |
Smallest (~270 KB min) | Canvas only | Line, Bar, Pie |
echarts.esm.mjs / .esm.min.mjs |
all ES module |
Largest | Canvas + SVG | All 23 |
Tip
If you only need line/bar/pie/scatter and the basic interactions,
echarts.common.min.jscuts your payload by half compared to the full bundle.
2.4 Tree-shaken (custom) build
For the smallest bundle, import only what you use through the echarts/core entry. ECharts exposes use() to register chart types, components, and renderers individually.
// Import the core, no charts and renderers included.
import * as echarts from 'echarts/core';
// Import bar charts. All suffixed with Chart or Component.
import { BarChart } from 'echarts/charts';
// Import the title, tooltip, rectangular coordinate, dataset and transform components.
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent
} from 'echarts/components';
// Features like universal transition and label layout.
import { LabelLayout, UniversalTransition } from 'echarts/features';
// Import the Canvas renderer — it is also used by the SVG renderer.
import { CanvasRenderer } from 'echarts/renderers';
// Register the required components.
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// The chart instance is created the same way as before.
const chart = echarts.init(document.getElementById('main'));
chart.setOption({ /* ... */ });
Important
Each chart type, component, and renderer must be passed to
echarts.use()before you callsetOptionwith that type. Forgetting to register a component is the most common source of "the chart renders but feature X is missing" bugs.
The package.json exports map exposes 100+ stable subpath imports for every chart and component, including:
import { LineChart } from 'echarts/charts';
import { GraphChart } from 'echarts/charts';
import { LegendComponent } from 'echarts/components';
import { ToolboxComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { SVGRenderer } from 'echarts/renderers';
The full list is in §6.3 of the Admin Guide.
2.5 TypeScript types
ECharts ships its types alongside the JavaScript. No @types/echarts install is needed.
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
const option: EChartsOption = {
title: { text: 'Typed Example' },
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [120, 200, 150] }]
};
const chart = echarts.init(document.getElementById('main')!);
chart.setOption(option);
For tree-shaken builds, use the per-component option types:
import type {
BarSeriesOption,
LineSeriesOption
} from 'echarts/charts';
import type {
TitleComponentOption,
TooltipComponentOption,
GridComponentOption,
DatasetComponentOption
} from 'echarts/components';
import type { ComposeOption } from 'echarts/core';
type ECOption = ComposeOption<
| BarSeriesOption | LineSeriesOption
| TitleComponentOption | TooltipComponentOption
| GridComponentOption | DatasetComponentOption
>;
const option: ECOption = { /* ... */ };
Tip
ComposeOption<...>produces a precise type that contains only the chart types and components you registered. Wrong option keys become TypeScript errors at compile time.
2.6 Verifying installation
import * as echarts from 'echarts';
console.log(echarts.version);
6.0.0
If this prints the version string, ECharts loaded correctly. If you see echarts is undefined, the script tag is in the wrong order or your bundler is mis-resolving the module.
3. Your First Real Chart
This section walks through building a chart that exercises 80% of what most users need: title, axes, multiple series, tooltip, legend, and responsive resizing.
3.1 The container
ECharts is bound to a DOM element. Its size is read from clientWidth / clientHeight at init time.
<div id="chart" style="width: 100%; height: 400px;"></div>
Warning
Hidden containers (display:none, zero-height parents, collapsed accordions) measure as 0×0 and the chart will not render. Initialize the chart after the container is visible, or call
chart.resize()once it becomes visible.
3.2 init() — creating the chart
const chart = echarts.init(dom, theme, opts);
| Parameter | Type | Default | Purpose |
|---|---|---|---|
dom |
`HTMLElement | null` | required (unless opts.ssr) |
theme |
`string | object | null` |
opts.renderer |
`'canvas' | 'svg'` | 'canvas' |
opts.useDirtyRect |
boolean |
false |
Enable dirty-rectangle optimization for canvas. |
opts.devicePixelRatio |
number |
window.devicePixelRatio |
Pixel ratio for HiDPI screens. |
opts.width |
`number | 'auto'` | DOM clientWidth |
opts.height |
`number | 'auto'` | DOM clientHeight |
opts.locale |
string |
'EN' |
Locale code (see §11). |
opts.useCoarsePointer |
`boolean | 'auto'` | 'auto' |
opts.pointerSize |
number |
unset | Hover hit-area expansion in pixels. |
opts.ssr |
boolean |
false |
Server-side rendering mode (no DOM, returns SVG string). |
const chart = echarts.init(document.getElementById('chart'), 'dark', {
renderer: 'canvas',
useDirtyRect: true
});
Caution
Calling
echarts.inittwice on the same DOM element returns the already-existing instance (with a console warning in dev mode). Always callchart.dispose()before re-initializing.
3.3 setOption — describing the chart
setOption accepts the entire chart configuration as one object. ECharts merges it into any previous option (additive update). To replace the option completely, pass notMerge: true or use the replaceMerge field.
chart.setOption({
title: {
text: 'Monthly Revenue',
subtext: 'FY2025',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
legend: {
bottom: 0,
data: ['Revenue', 'Costs']
},
grid: {
left: '4%',
right: '4%',
bottom: '12%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
},
yAxis: {
type: 'value',
axisLabel: { formatter: '${value}K' }
},
series: [
{
name: 'Revenue',
type: 'bar',
data: [120, 200, 150, 80, 70, 110]
},
{
name: 'Costs',
type: 'line',
data: [80, 110, 90, 60, 55, 75]
}
]
});
Renders a combined bar/line chart with title, legend at bottom,
axis-trigger tooltip, dollar-formatted Y axis, and 6 months of data.
3.4 setOption signatures
ECharts accepts two call shapes:
// Legacy positional API
chart.setOption(option, notMerge, lazyUpdate);
// Modern options-object API (recommended)
chart.setOption(option, {
notMerge: false, // Replace option entirely instead of merging
replaceMerge: ['series'], // Replace only listed mainTypes
lazyUpdate: false, // Defer redraw until next animation frame
silent: false, // Skip events fired by this update
transition: undefined, // Custom inter-state transitions
darkMode: undefined // Force light/dark mode resolution
});
| Field | Default | Effect |
|---|---|---|
notMerge |
false |
When true, discards previous option and uses only the new one. |
replaceMerge |
unset | Array of mainTypes (e.g. ['series', 'yAxis']) to replace instead of merge. Other types still merge. |
lazyUpdate |
false |
Useful when calling setOption very frequently — coalesces redraws to the next frame. |
silent |
false |
Suppress events fired as a result of this update. |
Important
Repeatedly calling
setOptionwithnotMerge: false(the default) adds rather than replaces. If the new option has fewer series than the old one, the leftover series stay on the chart. UsereplaceMerge: ['series']ornotMerge: truefor full replacement.
3.5 Resizing
ECharts does not auto-resize. You must call chart.resize() whenever the container changes size (window resize, sidebar collapse, tab activation).
window.addEventListener('resize', () => chart.resize());
For modern apps, ResizeObserver is more precise:
const ro = new ResizeObserver(() => chart.resize());
ro.observe(document.getElementById('chart'));
You can also pass an explicit size to override the container measurement:
chart.resize({ width: 800, height: 600 });
chart.resize({ width: 'auto', height: 'auto' }); // Re-measure DOM
chart.resize({ silent: true }); // Don't fire events
chart.resize({ animation: { duration: 300, easing: 'cubicOut' } });
3.6 Cleanup
When you remove a chart from the DOM (component unmount, route change), call dispose() to release resources:
chart.dispose();
Or dispose by DOM reference:
echarts.dispose(document.getElementById('chart'));
echarts.dispose(chart); // Pass the instance
echarts.dispose(chart.id); // Or its string ID
Warning
Forgetting
dispose()on chart removal leaks DOM listeners and zrender resources, which over time degrades performance and can cause memory pressure in single-page apps that mount/unmount many charts.
4. Chart Types
ECharts has 23 built-in series types. This section shows the option shape for each. For an interactive gallery, see echarts.apache.org/examples.
4.1 Cartesian basics
Line
{
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },
yAxis: { type: 'value' },
series: [{
type: 'line',
data: [120, 132, 101, 134, 90],
smooth: true, // Curved line
areaStyle: {}, // Fill below
showSymbol: true, // Dots at data points
symbol: 'circle',
symbolSize: 8
}]
}
Bar
{
xAxis: { type: 'category', data: ['A', 'B', 'C', 'D'] },
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: [10, 22, 28, 43],
barWidth: '60%',
itemStyle: {
borderRadius: [4, 4, 0, 0]
}
}]
}
Stacked bar
Set stack to a shared string on multiple bar series:
series: [
{ type: 'bar', stack: 'total', data: [10, 22, 28, 43], name: 'A' },
{ type: 'bar', stack: 'total', data: [5, 8, 12, 15], name: 'B' }
]
Scatter / EffectScatter
{
xAxis: {}, yAxis: {},
series: [{
type: 'scatter',
data: [[10.0, 8.04], [8.0, 6.95], [13.0, 7.58], [9.0, 8.81]],
symbolSize: 20
}]
}
effectScatter adds a rippling animation around each point and is useful for highlighting key data points (e.g. capital cities on a map).
4.2 Statistical
Boxplot
{
xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
yAxis: { type: 'value' },
series: [{
type: 'boxplot',
data: [
[low, q1, median, q3, high], // Q1
[low, q1, median, q3, high] // Q2
// ...
]
}]
}
The prepareBoxplotData helper in the optional dataTool extension computes the five-number summary for you. See §13.
Candlestick
For OHLC financial data:
{
xAxis: { type: 'category', data: dates },
yAxis: {},
series: [{
type: 'candlestick',
data: [
[open, close, low, high], // Day 1
// ...
]
}]
}
Heatmap
series: [{
type: 'heatmap',
data: [[xIndex, yIndex, value], ...]
}]
Often paired with a visualMap component to map value → color.
4.3 Hierarchical
Pie
{
series: [{
type: 'pie',
radius: ['40%', '70%'], // Inner/outer = donut
data: [
{ value: 1048, name: 'Search' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' }
]
}]
}
Sunburst
A multi-level pie:
series: [{
type: 'sunburst',
data: [{
name: 'Root',
children: [
{ name: 'A', value: 10, children: [/* ... */] },
{ name: 'B', value: 30 }
]
}],
radius: [0, '90%']
}]
Treemap
series: [{
type: 'treemap',
data: [{
name: 'NodeA',
value: 100,
children: [{ name: 'Sub1', value: 60 }, { name: 'Sub2', value: 40 }]
}],
leafDepth: 1 // Show only this depth, drill on click
}]
Tree
series: [{
type: 'tree',
data: [{
name: 'Root',
children: [
{ name: 'A', children: [/* ... */] }
]
}],
layout: 'orthogonal', // 'orthogonal' | 'radial'
orient: 'LR' // Left→Right
}]
4.4 Relational
Graph (network / force-directed)
series: [{
type: 'graph',
layout: 'force', // 'none' | 'circular' | 'force'
data: [
{ name: 'n1', x: 100, y: 100, value: 5 },
{ name: 'n2', x: 200, y: 200, value: 8 }
],
links: [
{ source: 'n1', target: 'n2' }
],
force: { repulsion: 100 },
roam: true
}]
Sankey
series: [{
type: 'sankey',
data: [
{ name: 'A' }, { name: 'B' }, { name: 'C' }
],
links: [
{ source: 'A', target: 'B', value: 5 },
{ source: 'B', target: 'C', value: 3 }
]
}]
Chord
A circular relationship diagram. New in v6 as a standalone series (formerly only via graph with circular layout).
series: [{
type: 'chord',
data: [{ name: 'A' }, { name: 'B' }, { name: 'C' }],
edges: [
{ source: 'A', target: 'B', value: 10 },
{ source: 'B', target: 'C', value: 15 }
]
}]
4.5 Geographic
Map
echarts.registerMap('USA', usaGeoJson);
{
series: [{
type: 'map',
map: 'USA',
data: [
{ name: 'California', value: 100 },
{ name: 'Texas', value: 70 }
]
}]
}
ECharts does not bundle map definitions. Register a GeoJSON or SVG asset with echarts.registerMap(name, geoJson) first. See §10.
Lines
Animated routes between two points (great for migration / flight data):
series: [{
type: 'lines',
coordinateSystem: 'geo',
data: [
{ coords: [[fromLng, fromLat], [toLng, toLat]] }
],
lineStyle: { width: 1, opacity: 0.6, curveness: 0.2 },
effect: { show: true, period: 6, trailLength: 0.5 }
}]
4.6 Specialized
| Series | Use case |
|---|---|
radar |
Multi-axis comparison (skills profile, product spec sheet). |
gauge |
Single-value progress dial. |
funnel |
Conversion stages (form fills → sign-ups → activations). |
parallel |
High-dimension data, parallel coordinate plot. |
themeRiver |
Stacked time series rendered as a flowing river. |
pictorialBar |
Bars rendered as repeated icon shapes. |
custom |
Programmable rendering for any chart not above. |
Each series has the same call shape: pass an item to series: [...] with type and series-specific keys.
4.7 The custom series escape hatch
When no built-in series fits, custom lets you draw any chart by returning render-item descriptors:
series: [{
type: 'custom',
renderItem(params, api) {
const xValue = api.value(0);
const yValue = api.value(1);
const point = api.coord([xValue, yValue]);
return {
type: 'rect',
shape: { x: point[0] - 10, y: point[1] - 10, width: 20, height: 20 },
style: { fill: api.visual('color') }
};
},
data: [[1, 5], [2, 8], [3, 3]]
}]
This single mechanism powers most third-party chart extensions for ECharts — radial bar charts, gantt charts, hexbin maps, etc.
5. Working with Data
5.1 Direct series.data
The simplest data model: each series owns its array.
series: [{
type: 'bar',
data: [120, 200, 150, 80]
}]
Items can be primitives, arrays, or objects:
data: [
120, // value
[10, 5], // [x, y]
{ value: 100, name: 'Highlight', itemStyle: { color: '#f88' } }
]
5.2 The dataset component
For multi-series charts that share data, register the data once with the dataset component:
{
dataset: {
source: [
['product', '2022', '2023', '2024'],
['Matcha', 43.3, 85.8, 93.7],
['Milk Tea', 83.1, 73.4, 55.1],
['Cheese Tea', 86.4, 65.2, 82.5]
]
},
xAxis: { type: 'category' },
yAxis: {},
series: [
{ type: 'bar' }, // Auto-uses dataset
{ type: 'bar' },
{ type: 'bar' }
]
}
The first row is treated as headers; subsequent rows become a series each. Use seriesLayoutBy: 'row' (default) or 'column' to flip orientation.
Tip
Datasets are the right choice when the same data drives multiple visuals (e.g. switching between bar and line representations of the same numbers). They also enable the powerful
transformfeature below.
5.3 Transforms (filter, sort, aggregate)
Datasets can be derived from other datasets via the transform component:
{
dataset: [
{
source: [/* original data */]
},
{
transform: { type: 'filter', config: { dimension: 'Year', '>=': 2023 } }
},
{
fromDatasetIndex: 1,
transform: { type: 'sort', config: { dimension: 'Value', order: 'desc' } }
}
],
series: [{ type: 'bar', datasetIndex: 2 }]
}
Built-in transforms include filter and sort. Register custom transforms via echarts.registerTransform(transform).
5.4 Streaming with appendData
For real-time data (live ticks, IoT streams) that grows continuously, use appendData instead of setOption to avoid full-chart redraws:
chart.appendData({
seriesIndex: 0,
data: [[Date.now(), 42.7]]
});
This appends rows in-place. The chart redraws only the new region.
5.5 universalTransition
Cross-chart morphing animations: when you switch from a bar chart to a pie chart with the same data IDs, ECharts can morph each shape across the boundary.
series: [{
type: 'bar',
data: [...],
universalTransition: { enabled: true }
}]
The UniversalTransition feature must be registered (use([UniversalTransition])) — it is included in the all build by default.
6. Interaction
6.1 Tooltip
tooltip: {
trigger: 'axis', // 'axis' | 'item' | 'none'
axisPointer: { type: 'shadow' }, // 'line' | 'shadow' | 'cross' | 'none'
formatter: (params) => {
return params.map(p => `${p.seriesName}: ${p.value}`).join('<br/>');
},
backgroundColor: 'rgba(50,50,50,0.9)',
textStyle: { color: '#fff' },
confine: true // Keep inside chart bounds
}
| trigger | When tooltip fires |
|---|---|
'axis' |
Hovering over an axis category — shows all series at that index. |
'item' |
Hovering over a single data point — shows that point only. |
'none' |
Tooltip is registered but not auto-shown (use dispatchAction({type:'showTip'})). |
6.2 Legend
legend: {
data: ['Series 1', 'Series 2'], // Optional, defaults to series names
type: 'scroll', // Enable scrolling for many series
orient: 'horizontal', // 'horizontal' | 'vertical'
bottom: 0,
selected: { // Initial visibility
'Series 1': true,
'Series 2': false
}
}
Click a legend entry to toggle the corresponding series. Listen for 'legendselectchanged' events to react.
6.3 Data zoom
Adds drag/wheel/pinch zoom to one or more axes.
dataZoom: [
{ type: 'inside', xAxisIndex: 0 }, // Wheel/pinch on chart
{ type: 'slider', xAxisIndex: 0, height: 24, bottom: 0 } // Bottom slider bar
]
inside— zoom by mouse wheel and touch pinch on the chart body.slider— render a draggable scrollbar.
To programmatically zoom:
chart.dispatchAction({
type: 'dataZoom',
start: 20, // Percentage
end: 80
});
6.4 Brush (range selection)
toolbox: {
feature: { brush: { type: ['rect', 'polygon', 'clear'] } }
},
brush: {
toolbox: ['rect', 'polygon', 'keep', 'clear'],
xAxisIndex: 0
}
Listen for 'brushSelected' to read selected indices.
6.5 Axis pointer
A crosshair-style indicator that follows the mouse:
axisPointer: {
type: 'cross', // 'line' | 'cross' | 'shadow' | 'none'
link: [{ xAxisIndex: 'all' }], // Sync across multiple charts
snap: true,
label: { backgroundColor: '#6a7985' }
}
6.6 Events
chart.on('click', (params) => {
console.log('Clicked:', params.seriesName, params.data);
});
chart.on('mouseover', { seriesName: 'Revenue' }, (params) => {
// Filtered: fires only for the Revenue series.
});
chart.off('click');
Common events:
| Event | Fired when |
|---|---|
'click', 'dblclick' |
User clicks/double-clicks a series item or background. |
'mouseover', 'mouseout' |
Pointer enters/leaves a data item. |
'globalout' |
Pointer leaves the entire chart. |
'rendered' |
A render frame finishes. params.elapsedTime is render duration in ms. |
'finished' |
Animation has finished and chart is at rest. |
'legendselectchanged' |
A legend toggle occurred. |
'datazoom' |
A data-zoom range changed. |
'brushselected' |
A brush selection committed. |
'restore' |
Toolbox restore button pressed. |
'dataviewchanged' |
DataView toolbox edited and applied. |
'magictypechanged' |
Chart type switched via toolbox magicType. |
'georoam' |
A geo map was panned/zoomed. |
'timelinechanged', 'timelineplaychanged' |
Timeline component updates. |
You can also listen to raw zrender events (low-level pointer events) via chart.getZr().on(...).
6.7 dispatchAction — programmatic interaction
Anything a user can do, you can do programmatically:
// Highlight a data point
chart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: 3
});
// Show a tooltip
chart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: 5
});
// Zoom
chart.dispatchAction({ type: 'dataZoom', start: 0, end: 50 });
// Toggle a legend entry
chart.dispatchAction({ type: 'legendToggleSelect', name: 'Revenue' });
Tip
dispatchActionis the ideal way to drive a chart from external UI (a button bar that filters, a slider that changes the time range, a map that spotlights a country).
7. Coordinate Systems
A coordinate system places series in 2-D space. Most users only ever touch grid, but ECharts ships seven.
| System | Series typically using it | Configured via |
|---|---|---|
cartesian2d (a.k.a. grid) |
line, bar, scatter, candlestick, boxplot, heatmap | xAxis, yAxis, grid |
polar |
radar, custom radial layouts | polar, angleAxis, radiusAxis |
geo |
map, lines, scatter on a map | geo |
singleAxis |
themeRiver, scatter on a single axis | singleAxis |
parallel |
parallel | parallel, parallelAxis |
calendar |
scatter / heatmap on a calendar grid | calendar |
matrix (new in v6) |
grids of related charts | matrix |
{
grid: { left: '10%', right: '5%', top: 60, bottom: 50 },
xAxis: { type: 'value' },
yAxis: { type: 'category' },
series: [{ type: 'bar', data: [...] }]
}
7.1 Axis types
xAxis: {
type: 'category' | 'value' | 'time' | 'log',
data: [...], // Required for 'category'
min: 0,
max: 100,
boundaryGap: true,
axisLabel: {
formatter: '{value}%',
rotate: 45
},
axisLine: { show: true },
splitLine: { show: false }
}
7.2 Axis breaks
New in v6: insert visual gaps in an axis to skip empty ranges or weekends.
xAxis: {
type: 'time',
breaks: [{ start: '2024-12-25', end: '2024-12-26' }]
}
You must register the AxisBreak feature (included in all and common builds).
7.3 The matrix coordinate system (v6 new)
The matrix coord system arranges sub-charts in a grid layout — useful for small-multiples and faceting:
{
matrix: {
columns: { data: ['2023', '2024', '2025'] },
rows: { data: ['North', 'South'] }
},
series: [{
type: 'pie',
coordinateSystem: 'matrix',
coordinateSystemUsage: 'box',
data: [/* ... */]
}]
}
8. Theming and Styling
8.1 Built-in themes
ECharts ships 37 themes under theme/*.js. Load and register one:
import 'echarts/theme/dark.js'; // For some prebuilt scripts; or:
// Manual registration
import darkTheme from './theme/dark.json' with { type: 'json' };
echarts.registerTheme('dark', darkTheme);
const chart = echarts.init(dom, 'dark');
The 37 names include: dark, infographic, macarons, roma, shine, vintage, walden, westeros, plus enterprise styles like chalk and wonderland.
8.2 Custom themes
A theme is a plain object that supplies defaults for any option key:
echarts.registerTheme('cyber-panda', {
color: ['#22d3ee', '#a78bfa', '#f472b6', '#facc15'],
backgroundColor: '#0f172a',
textStyle: { color: '#e2e8f0' },
title: { textStyle: { color: '#f8fafc' } },
legend: { textStyle: { color: '#cbd5e1' } },
tooltip: { backgroundColor: 'rgba(30, 41, 59, 0.92)' }
});
Once registered, pass the name to echarts.init(dom, 'cyber-panda').
Tip
The bundled
theme/tool/build.jsscript in the repo can convert exported themes from the official Theme Builder into the registration object format.
8.3 Per-series styling
Theme defaults are starting points. Anything in option overrides the theme. Set colors with:
series: [{
type: 'bar',
itemStyle: { color: '#3b82f6' },
emphasis: { itemStyle: { color: '#1d4ed8' } },
data: [...]
}]
Or use a function to color by data:
itemStyle: {
color: (params) => params.value > 100 ? '#ef4444' : '#10b981'
}
8.4 Dark mode
chart.setOption({ darkMode: true, /* ... */ });
Setting darkMode: true re-resolves color choices that depend on background luminance. Combined with backgroundColor: 'transparent' it lets your theme system control the surface color.
9. Animation
ECharts animates everything by default: initial render, data updates, axis changes, hover states.
{
animation: true,
animationDuration: 1000,
animationEasing: 'cubicOut',
animationDelay: (idx) => idx * 50,
animationDurationUpdate: 500,
animationEasingUpdate: 'cubicInOut',
animationDelayUpdate: 0,
animationThreshold: 2000 // Disable animation above N data points
}
To disable globally:
{ animation: false }
9.1 Universal transition
Already covered in §5.5. Set universalTransition: { enabled: true } per series for cross-type morphing.
9.2 Label layout
The labelLayout feature prevents label overlap automatically:
series: [{
type: 'pie',
labelLayout: { hideOverlap: true }
}]
Or globally:
{ labelLayout: { hideOverlap: true } }
10. Maps (Geo)
ECharts does not bundle map data. You must register a GeoJSON file with registerMap.
10.1 Registering
import worldGeo from './world.geo.json';
echarts.registerMap('world', worldGeo);
The mapName is the key by which the geo component and map series refer to it.
10.2 Using a map
{
geo: { map: 'world', roam: true },
series: [{
type: 'scatter',
coordinateSystem: 'geo',
data: [{ name: 'Paris', value: [2.35, 48.85, 100] }]
}]
}
Or as a series itself:
series: [{
type: 'map',
map: 'world',
data: [
{ name: 'United States', value: 100 },
{ name: 'Brazil', value: 70 }
]
}]
10.3 SVG basemaps
registerMap also accepts SVG strings, allowing you to use floor plans, organ diagrams, or any custom shape as a coordinate system:
echarts.registerMap('floor', { svg: svgString });
10.4 Baidu Maps extension (bmap)
For tile-based geographic maps within China, the optional extension-src/bmap extension wraps Baidu Maps as an ECharts coordinate system. See §13.
Warning
Loading
extension/bmaprequires the Baidu Maps API to be already loaded on the page (<script src="https://api.map.baidu.com/...">). The extension throws "BMap api is not loaded" otherwise. Only one bmap component per chart is allowed.
11. Internationalization
ECharts ships 53 locale packs under i18n/. Each defines toolbox tooltips, datazoom labels, time formatter strings, etc.
import 'echarts/i18n/langDE.js';
const chart = echarts.init(dom, null, { locale: 'DE' });
Or register from JSON:
import jaLocale from 'echarts/i18n/langJA-obj.json';
echarts.registerLocale('JA', jaLocale);
Locale codes shipped (subset shown — full list in i18n folder):
| Code | Language |
|---|---|
EN |
English (default) |
ZH |
Chinese (Simplified) |
ZH-TW |
Chinese (Traditional) |
JA |
Japanese |
KO |
Korean |
DE |
German |
FR |
French |
ES |
Spanish |
PT-BR |
Portuguese (Brazil) |
IT |
Italian |
RU |
Russian |
AR |
Arabic |
HE |
Hebrew |
Locale strings affect: toolbox button tooltips, dataZoom slider labels, chart-level "loading" text, and built-in time axis date formatting.
12. Server-Side Rendering (SSR)
ECharts can render to an SVG string in Node.js, returning a static markup payload that a browser can later hydrate to add interactivity.
12.1 Server side — produce SVG
import * as echarts from 'echarts';
const chart = echarts.init(null, null, {
renderer: 'svg',
ssr: true,
width: 600,
height: 400
});
chart.setOption({
title: { text: 'Sales' },
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: {},
series: [{ type: 'bar', data: [10, 20, 30] }]
});
const svg = chart.renderToSVGString();
// Send `svg` in your HTML response.
chart.dispose();
12.2 Client side — hydrate
The static SVG is markup. Add interactivity (hover, click) without re-rendering by importing the lightweight SSR client:
import { hydrate } from 'echarts/ssr/client';
hydrate(document.getElementById('chart'), {
on: {
mouseover: ({ seriesIndex, dataIndex }) => { /* ... */ },
mouseout: ({ seriesIndex, dataIndex }) => { /* ... */ },
click: ({ seriesIndex, dataIndex }) => { /* ... */ }
}
});
The hydrate runtime is ~3 KB minified — much smaller than the full ECharts library — making it ideal for static-first frameworks (Astro, Next.js RSC, 11ty) where you want a fast first paint and only ship interaction code on demand.
Important
SSR hydration is event-only: you can't
setOptionor update data after hydration without loading full ECharts. For dynamic charts, render a placeholder server-side and bootstrap full ECharts on the client.
13. Optional Extensions
13.1 dataTool (echarts/extension/dataTool)
Helpers for transforming raw data:
import echarts from 'echarts';
import 'echarts/extension/dataTool';
// Boxplot summary
const stats = echarts.dataTool.prepareBoxplotData(rawArrays);
// stats.boxData = [[low, q1, median, q3, high], ...]
// stats.outliers = [[index, value], ...]
// GEXF (Gephi format) parser for graphs
const graphData = echarts.dataTool.gexf.parse(gexfXmlString);
13.2 bmap (echarts/extension/bmap)
Baidu Maps coordinate system for tile-based Chinese geographic visualization:
<script src="https://api.map.baidu.com/api?v=3.0&ak=YOUR_KEY"></script>
<script src="echarts.min.js"></script>
<script src="extension/bmap.min.js"></script>
{
bmap: {
center: [116.397, 39.90],
zoom: 10,
roam: true,
mapStyle: { /* Baidu mapStyle config */ }
},
series: [{
type: 'scatter',
coordinateSystem: 'bmap',
data: [/* [lng, lat, value] */]
}]
}
14. Accessibility
The aria component generates ARIA labels and screen-reader descriptions:
{
aria: {
enabled: true,
description: 'Quarterly revenue chart for FY2025',
decal: { show: true } // Add patterns for color-blind users
}
}
decal.show: true overlays distinct visual patterns (lines, dots, crosshatches) on each series so users who cannot distinguish colors can still tell series apart.
15. Responsive & Media Queries
ECharts supports option-level media queries — different options for different chart sizes:
chart.setOption({
baseOption: {
title: { text: 'Sales' },
series: [{ type: 'bar', data: [10, 20, 30] }]
},
media: [
{
query: { maxWidth: 600 },
option: {
legend: { orient: 'vertical', right: 0 }
}
},
{
query: { minWidth: 1200 },
option: {
title: { textStyle: { fontSize: 24 } }
}
}
]
});
When the chart is resized (via resize()) ECharts re-evaluates queries and merges the matching option block.
16. Common Tasks (Recipes)
16.1 Save the chart as an image
const url = chart.getDataURL({
type: 'png', // 'png' | 'jpeg' | 'svg'
pixelRatio: 2, // Higher → sharper
backgroundColor: '#fff',
excludeComponents: ['toolbox']
});
const link = document.createElement('a');
link.download = 'chart.png';
link.href = url;
link.click();
16.2 Combine multiple charts into one image
const url = echarts.connect([chart1, chart2]) && chart1.getConnectedDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
});
16.3 Sync zoom across multiple charts
echarts.connect([chart1, chart2, chart3]);
// or
chart1.group = 'shared';
chart2.group = 'shared';
chart3.group = 'shared';
echarts.connect('shared');
After connecting, dataZoom, legend selection, tooltip axisPointer, and brushing propagate across the group.
16.4 Export rendered SVG as a string
const svg = chart.renderToSVGString(); // Requires renderer: 'svg'
16.5 Render to a fresh canvas (snapshot)
const canvas = chart.renderToCanvas({ pixelRatio: 2, backgroundColor: '#fff' });
document.body.appendChild(canvas);
Caution
renderToCanvasonly works when the chart was initialized withrenderer: 'canvas'. Otherwise it throws "renderToCanvas can only be used in the canvas renderer." Same forrenderToSVGStringand SVG.
16.6 Convert pixel ↔ data coordinates
const pixelXY = chart.convertToPixel({ seriesIndex: 0 }, [dataX, dataY]);
const dataXY = chart.convertFromPixel({ seriesIndex: 0 }, [pixelX, pixelY]);
const inside = chart.containPixel({ seriesIndex: 0 }, [pixelX, pixelY]);
Useful for synchronising overlay HTML or canvas content with the chart.
16.7 Loading state
chart.showLoading(); // 'default' loading effect
chart.showLoading('default', { text: 'Fetching data...' });
chart.hideLoading();
Register a custom loading effect:
echarts.registerLoading('spinner', (api, cfg) => { /* return a zrender Group */ });
chart.showLoading('spinner');
16.8 Get / inspect the current option
const opt = chart.getOption();
console.log(opt.series.length, 'series active');
16.9 Replace a single mainType cleanly
chart.setOption({ series: [{ type: 'bar', data: newData }] }, {
replaceMerge: ['series']
});
This is the safest way to swap a chart's series list without manually clearing it.
16.10 Clear the chart
chart.clear();
Removes all series and components but keeps the instance alive for reuse.
17. Troubleshooting
Error: Initialize failed: invalid dom.
Cause: echarts.init() was called with null, undefined, or a non-existent element ID, and opts.ssr was not set.
Solution: Make sure document.getElementById('main') returns a non-null element before init. If you load ECharts in <head>, defer init to DOMContentLoaded or move the script to the bottom of <body>.
Prevention: Always assert the dom is present first:
const dom = document.getElementById('main');
if (!dom) throw new Error('Chart container missing');
const chart = echarts.init(dom);
Error: renderToCanvas can only be used in the canvas renderer.
Cause: The chart was initialized with renderer: 'svg' and chart.renderToCanvas() was called.
Solution: Either init with renderer: 'canvas', or call chart.renderToSVGString() instead.
Prevention: Check chart.getZr().painter type or store the renderer choice alongside the chart.
Error: renderToSVGString can only be used in the svg renderer.
Cause: Mirror of the previous error: chart is canvas, you asked for SVG.
Solution: Init with renderer: 'svg' (this is also required for SSR).
Error: Option should contains series.
Cause: setOption was called with no series key (or an empty array) and the chart cannot determine what to draw.
Solution: Provide at least one entry in series: [...]. To clear the chart, use chart.clear() instead.
Error: Illegal priority
Cause: Used echarts.registerProcessor / registerVisual etc. with a numeric priority outside the allowed buckets.
Solution: Pass one of the predefined constants from echarts.PRIORITY (e.g. PRIORITY.PROCESSOR.SERIES_FILTER) or omit priority to use the default.
Error: Unknown dimension
Cause: A series referenced a dimension index or name that does not exist in the data store.
Solution: Verify series.encode keys match dimension names defined in your dataset.dimensions or the column count of dataset.source.
Error: BMap api is not loaded
Cause: The extension/bmap script ran before the Baidu Maps SDK was on the page.
Solution: Ensure the order is: Baidu Maps <script> first, then echarts.min.js, then extension/bmap.min.js. All three must be loaded before setOption.
Error: Only one bmap component can exist
Cause: The bmap extension supports only a single bmap component per chart instance.
Solution: If you need multiple maps, create multiple chart instances.
Error: render method must been implemented
Cause: A custom ChartView subclass was registered without overriding render().
Solution: Implement render(seriesModel, ecModel, api, payload) in your subclass. See the Admin Guide §11 for chart extension internals.
Warning: There is a chart instance already initialized on the dom.
Cause: echarts.init was called twice on the same DOM element. The second call returned the existing instance.
Solution: Call chart.dispose() before re-initializing, or check echarts.getInstanceByDom(dom) first.
Warning: Can't get DOM width or height. ... should not be 0.
Cause: Container has zero size at init time (often inside a hidden tab, accordion, or modal).
Solution: Init only after the container is visible, or pass opts.width and opts.height explicitly. For tabs, listen for the show event and call chart.resize().
Symptom: Chart is blank / nothing rendered
Likely causes:
- Container has zero height (CSS fix:
height: 400px). - A required component or chart type was not registered (
echarts.use(...)) in a tree-shaken build. seriesis empty ordataisnull.- The chart was disposed (
chart.isDisposed() === true).
Diagnosis:
console.log('disposed?', chart.isDisposed());
console.log('size:', chart.getWidth(), chart.getHeight());
console.log('opt:', JSON.stringify(chart.getOption().series));
Symptom: Tooltip shows but is in the wrong position / clipped
Cause: Tooltip is positioned in the document body (default appendToBody: true) but the chart is inside a transformed parent (CSS transform, filter, or position: fixed).
Solution: Set tooltip.appendToBody: false to position relative to the chart container, or tooltip.confine: true to clip inside the chart bounds.
Symptom: setOption frequently and animations stutter
Cause: Each setOption triggers a full update cycle. Frequent calls (>10/sec) overwhelm the render loop.
Solution: Use lazyUpdate: true in setOption opts to coalesce updates, or appendData() for streaming inserts. Also consider animation: false for high-frequency feeds.
Symptom: Memory grows after repeatedly mounting/unmounting charts
Cause: Missing chart.dispose() on unmount.
Solution: Call chart.dispose() in the unmount/cleanup hook of your component:
// React
useEffect(() => {
const chart = echarts.init(ref.current);
return () => chart.dispose();
}, []);
// Vue 3
onBeforeUnmount(() => chart.dispose());
Symptom: Time axis displays wrong dates with bar series
Cause: Known issue with xAxis.type: 'time' combined with series.type: 'bar' (community issue #19737).
Workaround: Use xAxis.type: 'category' with pre-formatted date strings, or pass exact timestamp numbers as bar data x-values.
Symptom: Tooltip not displayed when symbol is 'none' and labels hidden
Cause: Reported in community issue #17202. Hit-test region is inferred from symbols/labels.
Workaround: Set tooltip.trigger: 'axis' (which uses an axis-wide hit area) instead of trigger: 'item'.
Symptom: Memory leak with LTTB sampling on overlapping points
Cause: Reported in community issue #18562 for series.line with sampling: 'lttb' and >10K overlapping points.
Workaround: Disable sampling (sampling: 'none') or pre-deduplicate overlapping x-values before passing to ECharts.
18. Frequently Asked Questions
Q: Is ECharts free to use commercially?
Yes. Apache ECharts is licensed under the Apache License 2.0, which permits commercial use, modification, distribution, patent grant, and private use. No royalty or attribution beyond the license header is required.
Q: Does ECharts work in Node.js?
Yes, in SSR mode (opts.ssr: true, renderer: 'svg'). The output is an SVG string. See §12. You will need a JSDOM-free build path; ECharts itself does not require browser globals in SSR mode but check that any third-party feature you use does not.
Q: Does ECharts work in React Native / mobile WebView?
In a WebView yes (it's just a web page). For native rendering, projects such as react-native-echarts-pro wrap ECharts in a WebView under the hood. There is no native React Native renderer maintained by the ECharts team.
Q: Can I use ECharts with React / Vue / Svelte / Angular?
Yes, all four. ECharts is framework-agnostic — you bind it to a ref/element and call setOption. Maintained wrappers exist (e.g. echarts-for-react, vue-echarts) but are optional.
Q: How do I add a chart type that ECharts doesn't have?
Use the custom series (§4.7). It can render any shape your data describes. Several published extensions (gantt charts, hexbin maps, sunburst variants) are built this way.
Q: How do I limit the bundle size?
Switch from import * as echarts from 'echarts' to the tree-shaken pattern in §2.4 — import only the chart types and components you actually use. A typical dashboard with 4 chart types ships ~250 KB minified instead of ~1 MB.
Q: My chart is slow with 100K+ points. What can I do?
Several options:
- Switch the renderer to canvas (default) if you tried SVG.
- Set
useDirtyRect: trueoninitfor canvas. - Use
series.sampling: 'lttb'on line charts to downsample. - Use
progressiverendering on scatter/line:series.progressive: 5000. - Disable animation:
animation: false. - Replace
setOptionwithappendDatafor streaming inserts.
Q: Can I export a chart to PDF?
Indirectly. Export to PNG via getDataURL and embed it in a PDF using a library like jsPDF, or export to SVG via renderToSVGString and feed the SVG to a PDF generator that supports vector input.
Q: How do I detect when the chart has finished rendering / animating?
chart.on('finished', () => {
// Animations complete, chart is at rest.
});
chart.on('rendered', (params) => {
console.log('Frame elapsed:', params.elapsedTime, 'ms');
});
Q: Can I use SVG and Canvas for different elements in the same chart?
No. A chart instance has exactly one renderer. If you need both, create two charts.
Q: How do I get notified when a user zooms via the dataZoom slider?
chart.on('datazoom', (params) => {
console.log('Range:', params.start, '-', params.end, '%');
});
Q: Why does my legend / dataset label show item indices instead of names?
Your series doesn't have a name, or your dataset doesn't have headers in the first row. Add name: 'Revenue' to each series, or dimensions: [...] / a header row to the dataset.
Q: How do I make a chart 100% responsive to its container?
Set the container CSS to flex or grid sizing, then attach a ResizeObserver:
const ro = new ResizeObserver(() => chart.resize());
ro.observe(container);
// On unmount:
ro.disconnect();
chart.dispose();
Q: Do I need to bundle map JSON for every country?
No. Register only the maps you actually display. Map definitions can be lazy-loaded:
async function loadCountryMap(name) {
const geo = await fetch(`/maps/${name}.geo.json`).then(r => r.json());
echarts.registerMap(name, geo);
}
Q: Where do I report a bug or request a feature?
Issues are tracked at github.com/apache/echarts/issues. Use the [Bug] or [Feature] template prefix. The community mailing list is [email removed].
Q: How is ECharts maintained?
ECharts is an Apache Software Foundation top-level project. It is governed by the ASF charter with a Project Management Committee, public mailing list, and Apache release process.
19. Where to Go Next
- Architecture, internals, and extension authoring — see the Admin Guide. It covers the component model, render pipeline, building custom themes, plugin development, build flavors, and security considerations.
- Live examples — echarts.apache.org/examples has 600+ runnable demos searchable by chart type.
- Option reference — every option key with type signatures: echarts.apache.org/option.
- Source — github.com/apache/echarts, Apache 2.0 license.