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 typeData expectedNote
categoryAn ordered listThe default for a bar chart; boundaryGap: true
valueContinuous numbersscale: true lets the axis start above zero when the range is narrow
timeTimestamps or date stringsNeeds a parsed date; gap-aware spacing
logPositive numbers spanning orders of magnitudeLog of zero is invalid — filter or offset first
category with ordinal: trueUneven categoriesRarely 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 gridIndex puts everything on grid 0 and the second grid stays empty.
  • axisPointer.link is 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 xAxisIndex by 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' } }
  }
};
SymptomCauseFix
Labels overlapToo many categories for the widthhideOverlap, rotate, or fewer ticks
First label is cut offLabel wider than the left insetcontainLabel: true or a larger left
Axis name overlaps the labelsDefault nameGapSet nameGap and nameLocation: 'middle'
Numbers show as 1.2e+3Default numeric formattingA custom axisLabel.formatter
The chart starts at zero and looks flatValue axis defaults to include zeroscale: true on the axis
A single outlier flattens the restLinear scale over a wide rangeA 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.

Datasets, dimensions and transforms Tooltips, legends, labels and visual maps

Last refreshed 2026-09-18.