Appearance
Are you an LLM? You can read better optimized documentation at /embed/docs/axes/secondary.md for this page in Markdown format
Secondary / dual axis
Use a secondary value axis when two series need independent scales (e.g. revenue in dollars vs growth in %). Same units on two axes is usually harder to read; prefer one axis when you can.
Secondary sits on:
| Orientation | Secondary side |
|---|---|
vertical | right |
horizontal | top |
Config shape
js
axes: {
right: {
id: 'secondaryRange',
enabled: true,
visible: true,
boundSeries: [1], // original series indices (0-based)
labels: {
preferredTickInterval: 10,
numberFormat: {
mode: 'custom',
customFormat: {
prefix: '',
postfix: '%',
decimalPlaces: 0,
forceDecimals: false,
thousandSeparator: ',',
decimalSeparator: '.',
signDisplay: 'auto',
scaleDown: 0,
},
},
},
axisTitle: { visible: true, text: 'Growth' },
},
}| Field | Role |
|---|---|
enabled / visible | Turn the secondary scale and chrome on |
boundSeries | Which original series indices use this scale (not filtered/display order) |
labels.* | Independent ticks, bounds, and formats from the primary range |
breaks | Supported on secondary the same way as primary – see Breaks |
Series not listed in boundSeries stay on the primary range. Primary bounds exclude secondary-bound series so each scale fits its own data.
Line charts
Dual-axis is fully supported on line charts:
js
{
chartType: 'line',
isDataTransposed: true,
seriesData: [
['', 'Q1', 'Q2', 'Q3', 'Q4'],
['Revenue', 120, 132, 141, 155],
['Margin %', 18, 19, 17, 21],
],
axes: {
left: {
axisTitle: { visible: true, text: 'Revenue' },
labels: {
numberFormat: {
mode: 'custom',
customFormat: {
prefix: '$',
postfix: '',
decimalPlaces: 0,
forceDecimals: false,
thousandSeparator: ',',
decimalSeparator: '.',
signDisplay: 'auto',
scaleDown: 0,
},
},
},
},
right: {
id: 'secondaryRange',
enabled: true,
visible: true,
boundSeries: [1],
axisTitle: { visible: true, text: 'Margin %' },
labels: {
preferredTickInterval: 5,
numberFormat: {
mode: 'custom',
customFormat: {
prefix: '',
postfix: '%',
decimalPlaces: 0,
forceDecimals: false,
thousandSeparator: ',',
decimalSeparator: '.',
signDisplay: 'auto',
scaleDown: 0,
},
},
},
},
},
}Live: Dual-axis line sample.
Indices in boundSeries match row order in seriesData after the header row – series 0 is the first data row, series 1 the second, and so on.
Horizontal dual axis (secondary on top)
With orientation: 'horizontal', put secondary on top:
js
{
orientation: 'horizontal',
chartType: 'line',
axes: {
bottom: { /* primaryRange – values */ },
top: {
id: 'secondaryRange',
enabled: true,
visible: true,
boundSeries: [1],
},
left: { /* domain – categories */ },
},
}Give the chart enough height: domain labels on the left plus a top value axis need vertical room.
Combo charts
Combo can bind series to the secondary axis the same way (boundSeries + enabled / visible). Pair with combo.seriesTypes so the secondary series renders as a line (or whatever type you need):
js
{
chartType: 'combo',
isDataTransposed: true,
seriesData: [
['', 'Q1', 'Q2', 'Q3', 'Q4'],
['Units', 40, 48, 52, 61],
['ASP', 12, 11, 13, 14],
],
combo: {
seriesTypes: { 1: 'line' },
},
axes: {
right: {
id: 'secondaryRange',
enabled: true,
visible: true,
boundSeries: [1],
labels: {
numberFormat: {
mode: 'custom',
customFormat: {
prefix: '$',
postfix: '',
decimalPlaces: 0,
forceDecimals: false,
thousandSeparator: ',',
decimalSeparator: '.',
signDisplay: 'auto',
scaleDown: 0,
},
},
},
},
},
}Bars that stay on the primary axis still share one primary scale. Prefer exporting from the editor if you are unsure of series indices after reordering.
Anti-patterns
- Two axes for the same unit (readers compare the wrong scale)
- Forgetting
enabled: trueandvisible: true - Wrong
boundSeriesindex after inserting/reordering series rows - Putting secondary on
rightafter flipping to horizontal (it moves totop)
Next: Value axis · Breaks · By chart type