Datasets, dimensions and transforms
Drive series from dataset.source, name your dimensions, use encode to map them, and filter or aggregate data with transforms instead of in JavaScript.
dataset.source shapes
// 1. Array of arrays, with dimensions declared separately
const optionA = {
dataset: {
dimensions: ['month', 'revenue', 'cost'],
source: [
['Jan', 820, 410],
['Feb', 932, 480],
['Mar', 901, 455]
]
},
xAxis: { type: 'category' },
yAxis: { type: 'value' },
series: [
{ type: 'bar', encode: { x: 'month', y: 'revenue' } },
{ type: 'bar', encode: { x: 'month', y: 'cost' } }
]
};
// 2. Array of objects: the keys become the dimension names, order does not matter
const optionB = {
dataset: {
source: [
{ month: 'Jan', revenue: 820, cost: 410 },
{ month: 'Feb', revenue: 932, cost: 480 }
]
},
// ...series identical: encode still refers to dimension names
};
// 3. An external dataset with a source header row
const optionC = {
dataset: { source: rawCsvRows, sourceHeader: true }
};
// 4. Several datasets, referred to by index or id
const optionD = {
dataset: [
{ id: 'raw', source: rows },
{ id: 'monthly', fromDatasetId: 'raw', transform: { type: 'filter', config: { dimension: 'month', value: 'Jan' } } }
],
series: [{ type: 'line', datasetId: 'monthly' }]
};| Source shape | Dimensions come from | When to use |
|---|---|---|
[['Jan', 820], ...] | The dimensions array | Compact data, hand-written examples |
[{ month: 'Jan', ... }] | The object keys | Data straight from a JSON API |
| CSV-style with a header row | sourceHeader: true | Parsed files and pasted spreadsheet data |
| Typed arrays | The dimensions array | Very large numeric series |
| A typed-array dataset | dimensions plus sourceHeader | Performance-critical rendering |
The dimensions array does more than label columns: it sets the order, so a dimension can be referenced by name rather than by index in encode. That matters because reordering the source columns then requires no change at all in the series.
encode and multiple series over one dataset
const option = {
dataset: {
dimensions: ['month', 'revenue', 'cost', 'margin'],
source: [
['Jan', 820, 410, 0.5],
['Feb', 932, 480, 0.485],
['Mar', 901, 455, 0.495],
['Apr', 1290, 610, 0.527]
]
},
tooltip: { trigger: 'axis' },
legend: { data: ['Revenue', 'Cost', 'Margin'] },
grid: { left: 56, right: 64, top: 40, bottom: 40 },
xAxis: { type: 'category', axisLabel: { rotate: 0 } },
yAxis: [
{ type: 'value', name: 'Currency' },
{ type: 'value', name: 'Ratio', min: 0, max: 1, position: 'right',
axisLabel: { formatter: (value) => (value * 100).toFixed(0) + '%' } }
],
series: [
{ name: 'Revenue', type: 'bar', encode: { x: 'month', y: 'revenue', tooltip: ['revenue'] } },
{ name: 'Cost', type: 'bar', encode: { x: 'month', y: 'cost' } },
{ name: 'Margin', type: 'line', yAxisIndex: 1, smooth: true,
encode: { x: 'month', y: 'margin' } }
]
};
// A different view of the same data: no new arrays were built for any of this.
const switched = {
...option,
series: [
{ name: 'Revenue', type: 'line', encode: { x: 'month', y: 'revenue' } },
{ name: 'Cost', type: 'line', encode: { x: 'month', y: 'cost' } }
]
};encodemaps dimensions to the axes and to other slots:x,y,itemName,value,tooltip,label,seriesName.encode.tooltipis how you include a dimension in the tooltip that is not on any axis.- For a pie chart the mapping is
itemNameandvalue, notxandy. Using the axis names on a pie silently renders nothing. - Switching chart type is a change to
typeonly, because the data lives in the dataset. That is the practical benefit of this model.
💡
Datasets are per-instance, not shared. Two charts showing the same data each hold their own copy of it. If a very large dataset feeds several charts, keep one source array in your application state and pass the same reference to each chart's
dataset.source — ECharts reads it, it does not clone it.Filter, sort and aggregation transforms
const option = {
dataset: [
// the raw source
{
id: 'sales',
dimensions: ['region', 'month', 'amount'],
source: [
['North', 'Jan', 120], ['North', 'Feb', 140], ['North', 'Mar', 130],
['South', 'Jan', 90], ['South', 'Feb', 110], ['South', 'Mar', 150],
['East', 'Jan', 75], ['East', 'Feb', 80], ['East', 'Mar', 95]
]
},
// filter: only March
{
id: 'march',
fromDatasetId: 'sales',
transform: { type: 'filter', config: { dimension: 'month', '=': 'Mar' } }
},
// sort: by amount, descending
{
id: 'topMarch',
fromDatasetId: 'march',
transform: { type: 'sort', config: { dimension: 'amount', order: 'desc' } }
},
// aggregate: sum of amount grouped by region
{
id: 'byRegion',
fromDatasetId: 'sales',
transform: {
type: 'ecSimpleTransform:aggregate',
config: { resultDimensions: [{ name: 'total', from: 'amount', method: 'sum' }], groupBy: 'region' }
}
}
],
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', name: 'Region' },
yAxis: { type: 'value' },
series: [{ type: 'bar', datasetId: 'byRegion', encode: { x: 'region', y: 'total' } }]
};| Transform | Config keys | Typical use |
|---|---|---|
filter | dimension, =, >, <, and, or, parser | Slice by a category or range |
sort | dimension, order | Rank before rendering a bar chart |
ecSimpleTransform:aggregate | groupBy, resultDimensions with method | Sum, average, min, max, count |
boxplot | config with layout | Derive quartiles |
ecSimpleTransform:regression | method, dimensions | Trend line from raw points |
Custom (registerTransform) | Your own function | Domain-specific calculation |
// A custom transform: registered once, then usable by name anywhere.
import { registerTransform } from 'echarts/core';
registerTransform({
type: 'transform',
transform(params) {
const { upstream, upstreamData } = params;
const values = upstreamData.map((row) => [row[0], row[1] * 1.2]);
const dims = upstream[0].dimensions.slice();
return { dimensions: dims, data: values };
}
});
// Parsing strings to numbers and dates inside a transform
const withOption = {
dataset: [{
id: 'raw',
source: [{ when: '2026-01-15', amount: '820' }]
}, {
fromDatasetId: 'raw',
transform: {
type: 'filter',
config: {
dimension: 'when',
'>': '2026-02-01',
parser: 'time' // parse the dimension as a date before comparing
}
}
}]
};Transforms run inside ECharts, in the same pass that prepares the series data. That means the derived dataset is available to several series without extra JavaScript, and the logic is declarative — a real advantage when the chart option is itself generated from configuration.
FAQ
Should I use dataset or series.data?
Use
dataset when more than one series reads the same rows, when the data comes from a table-shaped API, or when you want transforms. Use series.data for a single series with hand-built values where a dataset would be ceremony.Why does my pie chart render nothing from a dataset?
The mapping names differ. A pie needs
encode: { itemName: 'name', value: 'amount' }; the axis-oriented x and y keys mean nothing without a Cartesian coordinate system.Related
Option configuration Coordinate systems, axes and scales
Last refreshed 2026-09-18.