Coordinate systems, axes and scales
Configure grid and Cartesian axes properly, choose between category, value, time and log axes, and lay out multiple grids without guessing at numbers.
The grid and Cartesian axes
const option = {
// The grid is the plotting rectangle. All four insets default to sensible
// values that do NOT account for your labels, so set them deliberately.
grid: {
left: 64, // space for the y axis labels
right: 24,
top: 48, // space for the title and legend
bottom: 56, // space for rotated x labels
containLabel: true // grow the insets to fit the labels automatically
},
xAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4'],
name: 'Quarter',
nameLocation: 'middle',
nameGap: 32,
boundaryGap: true, // bars: true; lines: false
axisLine: { lineStyle: { color: '#cbd5e1' } },
axisTick: { alignWithLabel: true },
axisLabel: { interval: 0, rotate: 0, hideOverlap: true },
splitLine: { show: false } // vertical grid lines are usually noise
},
yAxis: {
type: 'value',
name: 'Revenue',
nameGap: 16,
min: 0,
max: null, // let ECharts choose
splitNumber: 5,
axisLabel: { formatter: (value) => '$' + value.toLocaleString() },
splitLine: { lineStyle: { type: 'dashed', color: '#e2e8f0' } }
},
series: [{ type: 'bar', data: [820, 932, 901, 1290], barMaxWidth: 40 }]
};| Axis type | Data expected | Note |
|---|---|---|
category | An ordered list | The default for a bar chart; boundaryGap: true |
value | Continuous numbers | scale: true lets the axis start above zero when the range is narrow |
time | Timestamps or date strings | Needs a parsed date; gap-aware spacing |
log | Positive numbers spanning orders of magnitude | Log of zero is invalid — filter or offset first |
category with ordinal: true | Uneven categories | Rarely worth the confusion |
⚠️
containLabel: true overrides the four inset numbers you set. Use it when you do not know the label width, and use explicit insets when you need pixel-exact alignment with a neighbouring chart. Setting both and expecting both is the usual source of misaligned dashboards.Multiple axes and multiple grids
// Two y axes on one grid
const dualAxis = {
grid: { left: 56, right: 56, top: 40, bottom: 40, containLabel: true },
xAxis: { type: 'time' },
yAxis: [
{ type: 'value', name: 'Requests', position: 'left', axisLabel: { formatter: '{value}' } },
{ type: 'value', name: 'Latency (ms)', position: 'right',
splitLine: { show: false } }
],
series: [
{ name: 'Requests', type: 'line', yAxisIndex: 0, data: requestData },
{ name: 'Latency', type: 'line', yAxisIndex: 1, data: latencyData }
]
};
// Two grids stacked: the x axis of one is shared by id
const stacked = {
grid: [
{ id: 'top', left: 56, right: 24, top: 32, height: '32%' },
{ id: 'bottom', left: 56, right: 24, top: '58%', height: '26%' }
],
xAxis: [
{ id: 'xTop', gridIndex: 0, type: 'category', data: months, axisLabel: { show: false } },
{ id: 'xBottom', gridIndex: 1, type: 'category', data: months, axisLabel: { hideOverlap: true } }
],
yAxis: [
{ gridIndex: 0, type: 'value', name: 'Volume' },
{ gridIndex: 1, type: 'value', name: 'Errors' }
],
// Linking two x axes by id keeps them in sync without extra configuration
axisPointer: { link: [{ xAxisId: ['xTop', 'xBottom'] }] },
dataZoom: [
{ type: 'inside', xAxisIndex: [0, 1] }, // one zoom controls both grids
{ type: 'slider', xAxisIndex: [0, 1], bottom: 8 }
],
series: [
{ name: 'Volume', type: 'bar', xAxisIndex: 0, yAxisIndex: 0, data: volume },
{ name: 'Errors', type: 'line', xAxisIndex: 1, yAxisIndex: 1, data: errors }
]
};- Every axis and series is bound to a grid by index. Forgetting
gridIndexputs everything on grid 0 and the second grid stays empty. axisPointer.linkis what makes a hover on one grid show the same x position on the other — essential for stacked time-series views.- Percentages for grid insets resolve against the chart height, which is why a resize can shift a dashboard that used percentages next to one that used pixels.
- Give axes ids when several grids share them. Referring to
xAxisIndexby number works but breaks the moment you reorder the array.
Axis labels, names and breaks
const option = {
yAxis: {
type: 'value',
axisLabel: {
// A formatter receives the value and the index. Return a string.
formatter: (value) => {
if (value >= 1_000_000) return (value / 1_000_000).toFixed(1) + 'M';
if (value >= 1_000) return (value / 1_000).toFixed(0) + 'k';
return String(value);
},
color: '#475569',
fontSize: 11
},
axisLine: { show: false },
axisTick: { show: false }
},
xAxis: {
type: 'category',
data: labels,
axisLabel: {
rotate: 45,
interval: 'auto',
hideOverlap: true,
// formatter can also be a template string
// formatter: '{value} units'
}
},
// A break in the axis: useful when one outlier would flatten everything else
series: [{
type: 'bar',
data: values,
markLine: {
silent: true,
symbol: 'none',
lineStyle: { type: 'dashed' },
data: [{ yAxis: 5000, label: { formatter: 'Target' } }]
}
}]
};
// Axis breaks (a visual gap in a value axis) are a per-axis option in ECharts 6:
const withBreak = {
yAxis: {
type: 'value',
breaks: [{ startValue: 1200, endValue: 8000 }],
breakLine: { show: true, lineStyle: { type: 'wavy' } }
}
};| Symptom | Cause | Fix |
|---|---|---|
| Labels overlap | Too many categories for the width | hideOverlap, rotate, or fewer ticks |
| First label is cut off | Label wider than the left inset | containLabel: true or a larger left |
| Axis name overlaps the labels | Default nameGap | Set nameGap and nameLocation: 'middle' |
Numbers show as 1.2e+3 | Default numeric formatting | A custom axisLabel.formatter |
| The chart starts at zero and looks flat | Value axis defaults to include zero | scale: true on the axis |
| A single outlier flattens the rest | Linear scale over a wide range | A log axis or an axis break |
FAQ
Why do my two charts not line up?
One used
containLabel: true and the other used fixed insets, so their plotting rectangles differ. Fix the grid with explicit pixel insets and matching margins on the containing elements, or give both charts the same containLabel setting and the same container width.When should I use a log axis?
When the values span several orders of magnitude and the small ones matter. Remember that zero and negatives are invalid on a log scale, so filter or offset the data, and label the axis clearly — a reader who assumes a linear scale will misread it.
Related
Datasets, dimensions and transforms Tooltips, legends, labels and visual maps
Last refreshed 2026-09-18.