Plot

Labels

Attach content to individual datapoints: the value on a bar, the share in a slice, the series name at the end of a line.

AI assisted, human approved — novem uses AI to review and keep our documentation up to date.

Note: the labels file is part of the early-access flat config tree. You can explore it interactively in the plot playground.

Where a legend describes a whole series, a label describes one point. Labels live in one file directly under your plot's config:

plotconfig/labels

One instruction per line:

[slicer [slicer]] [prop ...]

Every prop is a k:v pair, and the label text itself is the value of the l: prop. Labels never appear uninvited: an empty file draws nothing, and a file containing just l:%v gives correct value labels on any chart — above columns (sign-aware), centered in stacked segments and slices.

Props split into two namespaces. Bare keys are line-scoped instructions: the selector, the text (l:), the lane policy (af:), total and hide. Everything that styles the label box carries the l prefix — le: li: la: lm: lo: lr: lc: lb: lbs: lbd: lbp: ltx: and the size keys — one l* vocabulary shared with facet strips, axis titles and complications, on the model of the axis t/tl prefix. The old bare spellings (e:, m:, c:, ...) are retired and error with a pointer to their l* replacement.

The datapoint is the unit: a cell (row, column) on bars, lines and scatter, a slice (row) on pie and donut. Selection is the shared slicer vocabulary, aimed at points:

l:%v le:c                 -- no slicer: every datapoint
espresso l:%v             -- one slicer: a column (a series)
-1 : l:%s le:r            -- two slicers: rows x cols of the raw csv
1,-1 : l:%v               -- first and last data rows

One slicer addresses columns; two are rows × columns of the RAW csv layout (the slicing contract): row 0 is the header line, col 0 the x column — both paint nothing — so the first data row is 1 and the first series is column 1. Ranges are stop-inclusive, negatives count from the end, header names substitute in the column token. Pie and donut slices are ROWS: 2 : l:%v labels the second slice. Under facets, labels resolve per panel against the panel's own data.

The row slot also takes a value token: @x the maximum, @i the minimum, @f the first, @l the last cell with a value. Resolution happens server-side, per selected series, so each series gets its own answer:

@x :  l:%v lc:mark        -- every series' peak, series-colored
@x -1 l:"peak %v"         -- the last series' peak only
@i espresso l:%v          -- the espresso series' minimum

The @ letters live in the row slot only, so they need their column selection spelled out: @x : addresses every series, and : @x is invalid. @e and @a (median, mean) are reserved — they name a value between cells, not a cell — and an unknown @ token drops the line, since a failed selector never broadens.

The bare keyword total makes the line address stack sums instead of cells: one label per x position, over the stacked region the selection touches. %v is the sum; every other prop rides unchanged. A column slicer narrows which stacked series contribute, and a row window narrows the x positions:

total l:%v                -- the sum over every stack
-5: : total l:%v,.0f      -- the last five stacks only

total is line-scoped, like hide, and meaningful on stacked types (stacked bars and areas).

The label text is the value of the l: directive — one letter that means label text across the whole platform, from axis titles and facet strips to legend entries, and an explicit directive keeps live editing stable: a half-typed word is never reclassified as label text. The value is unquoted when it has no spaces (l:%v), quoted when it does (l:"NVDA ($%v)"); quotes are ' or " and must match — an unterminated quote recovers loudly, the text running to the end of the line while you type, and the label stays put.

Inside the value, the legend token set, re-scoped to the point:

tokenmeaning
%vthis point's value
%vl %vf %vi %vx %ve %vavalue stats over the point's series: last, first, min, max, median, mean
%lthe point's row label (category, slice name, date)
%sthe series name
%pthe point's share of its series total, default .0%

Format specs append directly to a token, the table cell-format vocabulary: %v,.1f, %p.1%, %l%b-%y (strftime for dates).

A line with no l: uses the point's natural label: %l, the category on arcs, the row/x label on cartesian points. So -1 : alone labels the last row of every series with its natural label. Bare text is never a template: an unknown bare token drops the whole line with a diagnostic, and an unknown k:v drops just that prop.

Two keywords, the facet-strip vocabulary: an unquoted l:none (or l:0) means no label — it replaces any earlier label on those cells — and l:auto (or l:1) spells out the natural default. A quoted value is always literal, so l:"none" labels with the text "none".

| is a deck break inside the value: the label stacks vertically, and a leading < > - aligns each deck within the block:

Every template glyph escapes with a backslash: \| is a literal pipe, and \- \< \> start a deck with that character rather than aligning it — write l:"\-5% of total" to keep the minus sign. Those four are the only escapes; a backslash anywhere else is ordinary text.

l:-%l|-%v,.0f             -- centered name over centered value

Three orthogonal keys place the label; the same keys drive facet and axis labels, so you learn them once.

keyvaluesmeaning
le:t b l r, corners tl tr bl br, cthe anchor edge or corner on the mark. Default: the value end (top of a positive column, bottom of a negative one).
li:in, outwhich side of that edge the text sits. Stacked segments and arcs default in, everything else out.
la:data, axisx attachment target. data uses the selected mark; axis follows the y rail that claims its series (y2 s:… before the primary rail). Y remains at that rail's selected value.
lm:px or % of the mark, pair lm:x,ythe gap from the anchor edge. Default 2; unsigned is exact, while +/- adjusts the default (lm:+5 is 7px).
lml: lmr: lmt: lmb:pxper-side margins on the label box. Unsigned is exact; +/- adjusts the zero default. The anchored side nudges the label away from its mark, while the free side joins the plot's automatic margin.
lmx: lmy: lma:pxside bundles under the same absolute/relative rule; a later single side overrides its half.

Text grows into the mark inside and away from it outside, so there is no separate alignment key. On point marks (line vertices, scatter) le: is the compass direction from the point. On arcs (pie and donut) the edge set reads radially: le:t anchors at the outer rim, le:b at the inner edge, le:c at the middle of the annulus (the default), and a corner degrades to its radial component (tl reads as t). The angular position is always the centroid ray. li: and lm: keep their meanings against the chosen edge — li:in sits inside it, li:out beyond it (past the rim for le:t), and lm: is the radial gap.

l:%v le:tl li:in lm:2        -- inside each rect, top-left
l:%v le:c                    -- dead center
l:%v le:t li:out lm:2        -- above the bar (the default)
l:%l|%p li:in                -- donut: name over share, in the arc
l:%l li:out lm:8 lr:ray      -- outside the arc, on the radial line
-1 : l:%s le:r lm:6 lc:mark  -- series names at the line ends

The last line is the pseudo-legend: the series name rides the last datapoint, in the series color, and the plot reserves the margin it needs.

valuebehavior
lo:e lo:t lo:w lo:oellipsis, truncate, wrap, overflow
lo:hhide the label when it does not fit
lo:fflip to the other side of the edge when it does not fit

The former alternate letter (o:a) is now the lane policy af:a; see the Autofit section below.

The size trio bounds the label box, the CSS clamp reading: a fixed size, a minimum and a maximum. Values are px only, greater than zero, and when a minimum and a maximum conflict, min beats max. The letters rhyme with the platform: the table width file and the %vi/%vx value stats already spell min/max as i/x.

keymeaning
lw: lh:fixed width / height in px
lwi: lhi:minimum — floors the box: equal-width plates, so line-end pseudo-legends align
lwx:maximum width — caps the text (wrap or ellipsis per lo:) and the box and margin it reserves
lhx:maximum height — caps the deck count; excess decks drop

Inside a mark the fit box is the mark minus margins; outside it is lwx: or unbounded. Rect labels default to lo:f, the best-fit behavior; everything else defaults to lo:o. The old w: (which meant max width) is retired; its meaning lives at lwx:.

Point labels avoid colliding with each other automatically, the way axis tick labels do: one decision per label lane, measured before anything draws. When labels would overlap, every other one mirrors across its mark (the alternate stagger); when even that cannot fit, the lane thins to the smallest uniform step that does — and the last point always keeps its label.

af: is the lane policy, and it is a bare key: it belongs to the line's selection, not to the label box.

keyvaluesmeaning
af:absent, a, 0 (none, off)absent runs the automatic ladder; af:a forces the alternate stagger regardless of fit; af:0 disables collision handling for the lane

Only af:0 opts a lane out: box props such as lo:, lr: or lwx: style the labels without touching collision handling.

keyvaluesmeaning
lr:degrees −90..90, rayrotation; ray aligns arc labels to the centroid ray, flipping to stay readable
lc:color[,dark], ink, marktext color. ink (the default) contrast-flips inside dark marks; mark inherits the element's color
lb:auto, mark, 0, color[,dark]a legibility plate behind the label; mark uses the resolved mark color
lbs:flat, point, notch, round, cut; one value or near,farvector shapes for the logical ends of the responsive plate. One value applies to both ends.
lbd:px or near,farexact horizontal depth of shaped ends. lbd:5 makes a 5px chevron; omitted stays responsive.
lbp:px or x,yhorizontal and vertical plate padding. Unsigned is exact; +/- adjusts the themed default (3,2).
ltx:style/size/family, any orderthe table text vocabulary: ltx:b, ltx:600,sm,mono, ltx:12
hidebare keyworddrop the sliced points' labels

The plate is a generated SVG path, not a glyph or emoji: its body and ends grow with the measured text, font, wrapping and padding. near faces the mark and far faces away, so the silhouette remains correct when a label flips from one side to the other. For centered labels, near falls back to the inline-start side. Text defaults to contrast ink against the plate; an explicit lc: still wins.

An explicit depth participates in layout: lbd:5 lbp:4,2 lays out the tip, then 5px of shaped end, then 4px of horizontal padding before the text. The point cannot overlap or clip the first glyph.

la:data keeps a label attached to its mark and lets lm: offset that attachment. la:axis keeps the value-derived y but places text at the same x as the corresponding y-axis labels. A claimed series follows its claiming rail, so a Volume badge selected by y2 s:-1 aligns with y2, not the nearer primary y axis. Ordinary labels default to data; an outward near-point endpoint defaults to axis for line, area, scatter and bar marks. Side margins still apply after axis placement: omit lml:/lmr: for exact tick-label alignment.

For that Bloomberg endpoint, the point tip lands on the domain line while the text lands in the axis-label lane. The tip stays fixed while padding and content reshape the box around the text. The complete rendered SVG text block is measured and centered before padding is added, so multi-deck labels such as l:"%v|%s|%l" receive the same space above, below and on both sides.

This example turns the final value into a mark-colored arrow label. The pointed near end grows with ltx: and lbp: and the plot reserves its full vector extent.

waiting for the playground…
date,price
2026-01-01,101.2
2026-02-01,104.8
2026-03-01,102.9
2026-04-01,109.4
2026-05-01,114.7
2026-06-01,112.6

A label occupies a slot, its resolved (e, i) position. One label per point per slot; different slots coexist:

l:%l le:t li:out          -- category above every bar
l:%v le:c                 -- value centered inside the same bars

Plain lines define labels, and later lines replace their slot (later wins). To patch a previous line instead, lead with the + attach marker: the line restates the previous one over a new selector, merging its overrides onto what those points already have. A leading - erases.

l:%v
+ -1 : lc:mark ltx:b      -- ...and the last points get bold, series-colored

An unknown bare token drops its whole line; an unknown k:v prop drops just that prop. A token still being typed drops alone too: a key without its colon (lm before lm:6), an l: with no value yet, and an unterminated quote, which runs to the end of the line so the label holds its place while you type inside it. Drops are loud but never fatal: the rest of the file still applies, and every drop lands in the plot's diagnostics with its line number. The

playground shows them inline.