Give a mark a series field and it splits into one line or bar group per distinct
value, each taking the next color from the palette. withLegend names them.
This is US unemployment by industry — 1,707 monthly observations across fourteen industries, rolled up per year:
Bars stack by default. layout: 'grouped' puts them side by side, which compares
series against each other rather than comparing totals, and layout: 'nested'
draws each series inside the one before it, for series that contain one another
rather than adding up to something.
A stack runs from the value axis' zero outward in the order the series are
declared, so the first one sits against the axis. stackOrder: 'reversed' puts
it at the far end instead:
Reach for it where the series are ranked and the reader scans from the biggest, so the long tail stays gathered against the axis. Colors are unaffected — the palette follows the declared order whichever way the stack runs.
stackOffset: 'share' measures each segment as its share of its category's total
rather than as its own value, which is the hundred-percent stacked bar:
Every stack then fills the axis, and the chart compares composition rather than
volume. The value axis reads in percent unless a format of your own says
otherwise, and each row keeps its own number for the tooltip, which reads out
both — 58.1% (42). A category whose rows sum to zero draws nothing.
A whole axis reads in shares or none of it does, and only the chart's first
value axis can. Where another mark on the same axis is drawing its own values,
or where the stack sits on a second value axis, stackOffset has no effect and
the stack is drawn in its units like everything else.
Both options apply to area as well, and neither affects layout: 'grouped' or
layout: 'nested'.
layout: 'nested' is for subset metrics — sent, delivered and opened, or
impressions, clicks and conversions — where each series is part of the one before
it rather than part of a total. Declare them outermost first:
Every series stands on the same category, each drawn narrower than the one around it and painted over it, so the funnel is what the reader sees. Each bar still measures its own value, so one legend and one shared tooltip carry every stage at its true figure.
Reach for it wherever stacking would be a lie and grouping would lose the point. Stacked segments would have to be differences rather than the metrics themselves, and side by side, three bars are three quantities that happen to be near each other with nothing saying one contains the next.
thickness scales the whole ladder where a chart wants narrower bars. Nothing
that describes a stack applies — stackOrder, stackOffset and stackGap are
all about an edge two series share, and nested bars share none.
Not every chart splits its rows. A chart can be one series per mark instead —
bars for the volume, a line for the trend over them — where each mark is already
a single thing and the name belongs to the mark rather than to any row.
seriesName gives it that name:
A named mark is a series like any other: it takes the next color from the palette, gets a legend entry, names itself in the tooltip, and can be hidden from the legend along with the rest.
Use series wherever the rows carry the split themselves, and seriesName where
the mark is the series. A mark given both is split by series.
The legend is interactive: press an entry to take its series off the plot, press it again to bring it back. The axes recompute from what is left, so series that a larger one was flattening become readable.
Every series can be hidden, which leaves an empty plot.
hiddenSeries says which series start hidden. Pass onHiddenSeriesChange alongside
it to own the set — to persist what the reader hid, or to keep two charts in step:
A series is named by the string its series accessor produced, which is what its
legend entry reads.
legendPlacement moves the legend. 'top' and 'bottom' are a band across the
chart, with entries packed along it and wrapped onto as many rows as they need.
'left' and 'right' are a column beside the plot, one entry per row.
Reach for a column when the chart has a handful of series with names too long to sit side by side, or when the plot is round. It costs the plot the width the column takes, so on a narrow chart a band is still the better trade.
legendValueFormat writes a number beside each name. It is called once per series
with that series' values added together, the total across every series, and the
share of it — so a column of names becomes a small table ranking them. The totals
cover every series the chart was given, so the figures stay put when the reader
takes one off the plot.
A drawing that is already a proportion of what it plots is the exception — a donut,
and a stack with stackOffset: 'share'. There the figures cover the series still on
the plot and a hidden one reads as zero, so hiding a series re-proportions them the
way it re-proportions the slices or the columns, and each figure keeps matching the
shape beside it. A gauge measures against the total it declared, which is what its
ring is allocated over.
A column reserves its width from the chart, outside whatever the axis on that side is already using — the plot narrows, the guides stay where they were.
Series take the data-viz-categorical tokens in order, so a chart with no color
of its own is already on-palette, and those slots carry hover twins already.
To give a mark a meaning-carrying color, set color from a data-viz helper —
the helpers pick the right step for a palette of that size, which reaching for a
numbered token by hand skips. color accepts either form the helpers give you:
semantic('success', 1)[0]) — preferred, because
it carries both the base color and its hover twin, which is what
withHoverColor repaints with.semantic('success', 1)[0].token, or any CSS color)
— a single color with no twin, so withHoverColor has nothing to swap to and
leaves the mark alone.A colored mark is out of the palette's hand-out, not out of the legend. The series it names still gets an entry, and that entry's swatch is painted the color the mark was given — a key drawing a color the plot never used would contradict the thing it labels. Where two marks draw one series in different colors there is nothing a single swatch can honestly say, so the palette's color stands.
When the whole palette is the thing being replaced — a chart of statuses, say —
pass palette rather than coloring each mark:
The guides — text, grid and background — are CSS variables, so they are set the way any Pluma component variable is: through vanilla-extract, off the component's own exports, never by writing the variable name as a string.
PlumaChart.vars also carries textColor, mutedTextColor, backgroundColor,
crosshairColor, and series1Color through series10Color with a
series1HoverColor through series10HoverColor beside them, so an individual
palette slot — or its hover state — can be overridden from a container when
setting it on the mark isn't possible.