trellis

Configuration Structure

trellis: {
by: string | ((series: any, index: number) => string | ...,
row: string | ((series: any, index: number) => string | ...,
column: string | ((series: any, index: number) => string | ...,
emptyPanels: 'placeholder' | 'skip' | ...,
data: Record<string, any>[],
x: string,
y: string,
seriesBy: string,
columns: number | 'auto',
minPanelWidth: number,
gap: number,
aspectRatio: number,
panelHeight: number,
minPanelHeight: number,
order: 'first-seen' | 'asc' | ...,
limit: number,
virtualize: 'auto' | boolean,
scales: { ... },
x: 'shared' | 'independent',
y: 'shared' | 'independent' | ...,
color: 'shared',
size: 'shared',
header: { ... },
show: boolean,
formatter: Function,
style: { ... },
fontSize: string,
fontWeight: string | number,
color: string,
axes: { ... },
labels: 'edges' | 'all' | 'none',
legend: 'shared' | 'none',
toolbar: 'shared' | 'none',
tooltip: 'panel' | 'sync' | 'grid',
zoom: 'sync' | 'none',
promote: boolean,
radiusByTotal: boolean,
targetTicks: number,
panel: Function,
}

Split one series array into a grid of panels, each a real chart of the host's chart.type. Setting by (or row / column) makes the chart a trellis host, and the host owns everything shared between panels: the scale domains, the pixel-aligned plot rectangles, the colour-by-series-name map, the headers, one legend, one toolbar and the responsive column count.

Requires the Trellis feature, which is not in the default bundle: import 'apexcharts/features/trellis', or add dist/features/trellis.js after apexcharts.js on a script-tag page. See the Trellis guide for a walkthrough.

by

Default: undefined

Facet accessor: the name of a key on each series object, or a function receiving (series, index) and returning the panel key.

series: [
  { name: 'Revenue', region: 'North', data: [/* … */] },
  { name: 'Revenue', region: 'South', data: [/* … */] },
],
trellis: {
  by: 'region',
}

Series without the key repeat in every panel, which is how a target line or a benchmark reaches all of them at once. facet is the typed field on a series object, but any key name works from plain JavaScript.

Mutually exclusive with row / column.

row

Default: undefined

Row facet accessor for a 2-D grid. With row and column set, the grid is every (row, column) combination in row-major order with a fixed column count: column labels draw once across the top, row labels once down the left, and panels shrink rather than re-column on resize.

A series carrying only the row key repeats across that row, which makes it a row-scoped reference series.

column

Default: undefined

Column facet accessor for a 2-D grid. See row. A series carrying only the column key repeats down that column.

emptyPanels

Default: 'placeholder'

What to do with (row, column) combinations that carry no data.

Available Options:

  • 'placeholder' (default) mounts a real empty panel at the shared geometry with a quiet noData.text label
  • 'skip' keeps the slot with a tinted blank
  • 'hide' keeps the slot with nothing in it

All three keep the slot, so the grid stays rectangular and the row and column labels keep lining up.

data

Default: undefined

Tidy-row input, an alternative to series: a row table pivoted by the by, x, y and seriesBy column names, all of which must be strings in this form.

trellis: {
  data: [
    { month: '2025-01', region: 'North', revenue: 58 },
    { month: '2025-01', region: 'South', revenue: 42 },
  ],
  by: 'region',
  x: 'month',
  y: 'revenue',
}

Rows win over series when both are given. Aggregation is out of scope: duplicate (panel, series, x) rows keep the last one and warn, so sum or average the rows yourself first.

x

Default: undefined

Name of the x-value column. Tidy-row form only.

y

Default: undefined

Name of the y-value column. Tidy-row form only.

seriesBy

Default: undefined

Name of the column whose values become series names. Tidy-row form only; absent means one series per panel, named after the y column.

columns

Default: 'auto'

Column count. 'auto' fits as many minPanelWidth columns as the container holds and re-columns on resize; a number fixes the count.

minPanelWidth

Default: 220

Narrowest a panel may get, in pixels. Drives columns: 'auto' and the responsive collapse.

gap

Default: 12

Gap between cells, in pixels.

aspectRatio

Default: 1.6

Panel width divided by panel height, used when no explicit height governs.

panelHeight

Default: undefined

Explicit panel height in pixels. Takes precedence over aspectRatio and chart.height.

minPanelHeight

Default: 80

Floor for a height-derived panel, in pixels.

Below this a panel stops being readable and the layout starts producing degenerate plot boxes, so a grid that cannot fit its container overflows, and warns, rather than shrink past it. Lower it when fitting a short container matters more than panel legibility.

order

Default: 'first-seen'

Panel order.

Available Options:

  • 'first-seen' (default) follows the order the keys appear in the data
  • 'asc' / 'desc' sort the keys
  • an array of keys gives an explicit order
  • a comparator receiving (a, b) sorts them

limit

Default: undefined

Render only the first N panels in order, and warn about the rest. Useful for a top-N grid without pre-slicing the data.

virtualize

Default: 'auto'

Mount only the panels near the viewport.

Available Options:

  • 'auto' (default) virtualizes once the grid exceeds 64 panels
  • true always virtualizes
  • false always renders eagerly

An unmounted cell keeps its header and a fixed-height skeleton, so page height and scroll position never shift. A panel scrolled out of view is destroyed with its view state stashed, and a remount restores its zoom window. getPanel(key) returns null for an unmounted panel.

scales

How each visual channel resolves across panels. A shared domain is what makes panels comparable; an independent one is what makes a small panel readable.

x

trellis.scales.x
Default: 'shared'

Available Options:

  • 'shared' (default)
  • 'independent'

y

trellis.scales.y
Default: 'shared'

Available Options:

  • 'shared' (default) gives every panel the union domain, so a taller shape really is a bigger number
  • 'independent' gives every panel its own domain
  • 'independent-row' shares one domain per row (2-D grids)
  • 'independent-column' shares one domain per column (2-D grids)

Panels stay pixel-aligned in every mode: a gutter pass measures each panel's axis-label width and pushes the widest as a shared floor, so the plot rectangles agree to the pixel. 'independent' and 'independent-column' show y labels on every panel, since the ticks differ; 'independent-row' keeps them on the first column, since the ticks along a row are identical.

color

trellis.scales.color
Default: 'shared'

Colour is keyed by series name across the whole grid, so one series is one colour in every panel.

Available Options:

  • 'shared' (default)

size

trellis.scales.size
Default: 'shared'

Bubble radius resolution. Shared means one z extent for the grid, so bubble areas compare across panels.

Available Options:

  • 'shared' (default)

show

trellis.header.show
Default: true

Available Options:

  • true (default)
  • false

formatter

trellis.header.formatter
Default: undefined

Rewrite a header label. Receives (key, { dimension, index, count }) and returns the string to display. dimension names which accessor produced the key in a 2-D grid.

header: {
  formatter: (key, { index, count }) => `${key} (${index + 1}/${count})`,
}

fontSize

trellis.header.style.fontSize
Default: undefined

Font size of the header label, as a CSS length.

fontWeight

trellis.header.style.fontWeight
Default: undefined

Font weight of the header label.

color

trellis.header.style.color
Default: undefined

Colour of the header label.

axes

Axis-label policy across the grid.

labels

trellis.axes.labels
Default: 'edges'

Available Options:

  • 'edges' (default) shows y labels on the first column and x labels on each column's bottom panel
  • 'all' labels every panel
  • 'none' labels none

Label space is reserved on every panel whatever the setting, so panels stay aligned and switching modes never re-flows the grid.

legend

Default: 'shared'

One legend for the grid, whose entries toggle a series name in every panel at once. Per-panel legends are hidden.

Available Options:

  • 'shared' (default)
  • 'none'

toolbar

Default: 'shared'

One zoom, pan and reset toolbar for the grid.

Available Options:

  • 'shared' (default)
  • 'none'

tooltip

Default: 'panel'

Where the tooltip card appears on hover. The crosshair sweeps every panel in all three modes.

Available Options:

  • 'panel' (default) shows a card only in the hovered panel
  • 'sync' shows each panel its own card at the hovered x
  • 'grid' shows one card near the cursor with a row per panel at the hovered x

'grid' composes its rows from the panels' own tooltips, so every formatter is honoured. Virtualized panels that are not mounted contribute no row. Pair it with tooltip.compact when a normal card would cover a small panel.

zoom

Default: 'sync'

Whether a zoom or pan in one panel moves the others.

Available Options:

  • 'sync' (default)
  • 'none'

promote

Default: false

Let a click on a cell header expand that panel to the grid's full width, with an "All panels" breadcrumb back.

Off by default: an embedded trellis is usually read rather than driven, and nothing on a header says it is a button until the cursor changes over it. promotePanel(key) and restorePanels() work whatever this is set to, so you can drive promotion from your own UI without the header affordance.

Available Options:

  • false (default)
  • true

radiusByTotal

Default: false

Pie, donut and polarArea only: scale each panel's radius so its area is proportional to that panel's total.

Equal-size pies cannot encode magnitude, so a grid of them says nothing about which slice of the business is bigger. This is what makes a pie trellis honest.

Available Options:

  • false (default)
  • true

targetTicks

Default: 3

Tick-interval target for the shared nice y scale. Panels are small, so the default of 3 intervals (about four labels) keeps the axis from outweighing the data.

panel

Default: undefined

Per-panel option override, merged last. Receives (key, { index, seriesNames }) and returns a partial options object.

trellis: {
  by: 'store',
  panel: (key, { index }) => ({
    colors: [scoreOf(key) >= 85 ? '#15803D' : '#DC2626'],
  }),
}

Use it for what genuinely differs per panel. Anything shared belongs in the host options, so the trellis can keep the panels aligned.