Skip to content

Small Multiples

Small multiples repeat one chart grammar across the values of a field, so every panel shares the same encoding and scale and only the data differs. They answer "show me this same chart, one per region" without hand-building a chart per value.

Add a chart-level multiples: property to any cartesian chart (area, bar, line, scatter, heatmap). It names a rows field, a columns field, or both — at least one is required. The query still owns the data; multiples: only says which field partitions it into panels.

Row stack (rows)

A rows field stacks one panel per value in a single column. The panel's identity reads as a label to the left of each row, not as a per-panel title.

queries:
  monthly:
    columns: [month, region, revenue]
    values:
      - [Jan, West, 120]
      - [Feb, West, 140]
      - [Jan, East, 80]
      - [Feb, East, 90]
charts:
  revenue_by_region:
    type: area
    query: monthly
    x: month
    y: revenue
    multiples:
      rows: region

Column strip (columns)

A columns field lays the panels out side by side, one per value, with the value labelled along the top edge:

queries:
  monthly:
    columns: [month, region, revenue]
    values:
      - [Jan, West, 120]
      - [Feb, West, 140]
      - [Jan, East, 80]
      - [Feb, East, 90]
charts:
  revenue_by_region:
    type: area
    query: monthly
    x: month
    y: revenue
    multiples:
      columns: region

Grid (rows + columns)

Set both to lay the panels out as a matrix — rows down the left edge, columns across the top:

queries:
  monthly:
    columns: [month, region, product, revenue]
    values:
      - [Jan, West, Widgets, 120]
      - [Jan, West, Gadgets, 60]
      - [Jan, East, Widgets, 80]
      - [Jan, East, Gadgets, 40]
charts:
  revenue_grid:
    type: area
    query: monthly
    x: month
    y: revenue
    multiples:
      rows: region
      columns: product

Shared vs. independent scale

By default every panel shares one measure scale, so the panels are directly comparable — a tall panel really is larger than a short one. This is almost always what you want.

To give each panel its own scale instead — useful when you care about the shape of each series rather than its level — add scale: independent:

multiples:
  rows: region
  scale: independent   # shared (default) | independent

Shared axes

Under the default scale: shared, a small-multiples set draws one shared measure axis for the whole set rather than repeating a full axis on every panel. When there is more than one column the shared scale is also drawn on the far edge, so panels on the right stay readable without tracing back to a single axis on the left. Set style.axis_y.mirror explicitly to force that far-edge axis on or off.

Independent scales and a both-edge axis are contradictory — mirroring one shared scale to both edges is meaningless when each panel has its own scale — so combining scale: independent with style.axis_y.mirror: true is rejected at compile time.

scale: independent changes this: each panel computes and draws its own measure axis, since there is no longer one shared scale for a single axis to represent. This applies to the chart's measure axis — normally y, but on a horizontal bar, where the measure is drawn on x instead, scale: independent frees x there.

Rules

  • multiples: requires at least one of rows or columns; set both for a grid.
  • Structurally available on the cartesian families only: area, bar, line, scatter, heatmap.
  • A chart cannot combine multiples: with a data_table: — the attached table has no per-panel meaning.
  • Endpoint labels and multiples: cannot coexist either: the label rail names series for a single panel, and a faceted chart has no single panel for it to sit beside. You do not normally have to do anything about this — on bar, line, and area (the families that carry the endpoint-label rail), setting multiples: turns the endpoint-label default off and moves the colour legend above the panels to name the series instead, so one legend reads across the whole grid. scatter and heatmap have no endpoint-label rail to begin with, so faceting them leaves their legend where it was. Only a chart that opts in to style.endpoint_labels.visible: true alongside multiples: is rejected.
  • An own-query overlay layers: entry (one authoring its own query:, not reusing the base chart's) is rejected when that query returns the rows/columns field — Vega-Lite has no mechanism to hand a different inline dataset to each panel of one layered spec, so per-panel layer data cannot be honoured and the chart refuses to render rather than repeat it wrongly. A layer whose query does not return that field is unaffected: it renders identically in every panel, which is correct for a chart-wide reference (a target line across the whole grid, for example).