Plot

Legend

Legends are key components in making a visualisation readable.

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

Example chart with complex legend example here.

Suggestion: a map with both a gradient and ordinal colors

The novem legend is closely tied to the novem color formatting as ideally each legend item responds meaningfully to the corresponding novem color formatting.

In the abstract you can think about novem legends as being a logical grouping of text and icons to support a novem plot. This grouping can then be placed somewhere within your plot drawing context as an element, or created as a standalone plot setting the type to legend.

In essence a novem legend consists of three components, position places the legend in your plot. format styles each legend category and layout controls how each formatted item is positioned.

Legends follows the regular novem API config structure as outlined below.

plot_name                       => Name
├── config                      => Configuration options
...
│   ├── legend                  => legend specific settings
│   │   ├── type                => default
│   │   ├── position            => placement, outside, inside, left, center
│   │   ├── layout              => layout, rows, grids, alignment
│   │   ├── margin              => the css m/p pair, legend-scoped
│   │   └── format              => format, number, text, icons
...

Currently we only support the default novem legends, but the type endpoint is here to support expansion in the future. At the moment this endpoint is read-only and only here for reference.

The first main decision you need to take about your chart is where to place it. By default our ordinal legends are placed centered below where as our continous legends are place outside the chart area top left.

The three available dimensions are:

  • outside or inside the chart area
  • left, middle or right of the horizontal dimension
  • top, center or bottom of the vertical dimension

There is also auto which tries to place the legend where there is the most available space (this is only really relevant when combined with inside).

And there is none: no legend at all. Nothing renders, no space is reserved, and the automatic legend a by color instruction creates is suppressed too. Your format and layout files are untouched, so removing the none restores the legend as it was.

Finally there is fixed position expressed in two percentage numbers, x and y.

0,0 means top left corner, where as -0,-0 means bottom right. + and - can be combined at will so a -10,20 will be 10% offset from the right side and 20% offset from the top.

Note: the offset will be from the closest side of the legend box, so if there is not enough space to fit the box to the left of the anchor point, the anchor will automatically flip to be on the right hand side. Same for top and bottom.

Outside top left
 xxxxxxxxxxxxxx
 +-------------------------------------------------------+
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 +-------------------------------------------------------+


Outside top middle
                      xxxxxxxxxxxxxx
 +-------------------------------------------------------+
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 +-------------------------------------------------------+


Outside top right
                                            xxxxxxxxxxxxxx
 +-------------------------------------------------------+
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 |                                                       |
 +-------------------------------------------------------+

Layout controls the inner structure of the legend, this is mostly applicable to ordinal lengeds where you might prefer certain structures to your layout.

TODO:

  • grid
  • rows
  • cols
  • auto

We also need to talk about width, number of columns and number of rows.

Things to think about:
- align left or right with a maxium widht an overflow
- spread out
- NxM grids
- maybe recylce some flexbox things
- manual breakpoints (break after n)

The margins vocabulary, scoped to the legend block — the same letters you know from the margins file and the label lm* family:

mt:12 mx:8      -- m*: space OUTSIDE the block. Moves the legend, and
                --     an outside legend's reserve grows with it: mb:8
                --     on a top legend pushes the plot 8px further down.
px:24 py:6      -- p*: space INSIDE — px between entries in a row,
                --     py between rows; pa: sets both.

Sides ml: mr: mt: mb: (px), bundles mx: my: ma:, gaps px: py: pa: (non-negative). The value grammar is the margins file's: the px suffix is optional, a value can ride the next token (mr: 15), whitespace and commas both separate, and auto clears an earlier value. Unsigned margins are exact; explicit +/- adjusts the zero default. Only % stays behind, since the legend never sees the svg box it would be a percentage of. The block places as its margin box, so every position mode — the keyword bands, inside, auto, fixed percentages — composes without special cases. The layout file's old gap: retired here: spacing belongs to the p* family.

Finally we have the format of the individual legend items.

The legend format is somewhat complicated, but once you learn it you'll have access to a very expressive grammar for formatting and structuring your legends.

Each format line is [selector] [k:v props]. For the common template-only case, omit l: entirely: %cs %l and %cl %l are valid lines. As soon as a colon appears, use the explicit l: directive: unquoted when it has no spaces (l:%l), quoted when it does (l:"%cs %l"). Quotes are ' or " and must match; an unterminated quote recovers loudly — the value runs to the end of the line while you type, and the entry lives. A line with no l: keeps the entry's default template, %cs %l — so 1 ltx:b bolds a legend entry while keeping its default text. An unquoted l:none (or l:0) means no entry text, and l:auto (or l:1) spells out the default; a quoted value is always literal, so l:"none" is the text "none". On a property-bearing line, unknown bare text drops the whole line, and an unknown k:v drops just that prop. One letter means label text across the whole platform — the same l: names axis titles, facet strips and datapoint labels — and the explicit directive keeps live editing stable: a half-typed word is never reclassified as entry text.

Entry props share the platform's l* label-box vocabulary, the same keys that style datapoint labels, facet strips, axis titles and complications: lc: overrides the swatch color, ltx: styles the entry text, and hide stays bare. The size trio works here too: lwx: caps an entry so long series names ellipsize, and lwi: floors the block for tabular alignment (min beats max, as everywhere). The old bare spellings (c:, tx:) are retired and error with a pointer to their l* replacement.

The first thing to know is that format instructions can consist of multiple lines, where each line is mapped against the corresponding index of data in the plot, where the last line will be used if there is more categories in the data than lines in the format endpoint.

If there are more lines in the format than categories in the data, the remainder of format instructions will be ignored. Thus adding a single format instruction will apply it to all categories in the data set.

Similar to colors it is also possible to address entries explicitly by starting a line with a selector: the category header (case-insensitive, spaces read as underscores), an index, a range or a comma list.

Lines apply in order and the last line to address an entry defines it. A selector-less line claims the next entry not yet addressed, and the final selector-less line extends to every entry still unclaimed.

performance l:"%cl %l"      -- give the performance series a line legend
l:"%cs %l"                  -- make all other categories a square

A legend captions your colors, so entries can address everything the color grammar can paint.

Two slicers make a region entry — one entry captioning a slice of the data, with its swatch resolved from whatever color those cells actually got. A region has no natural name, so the l: value carries literal text instead of %l:

: l:"%cs %l"                  -- per-series entries as usual
-10: : l:"%cs Recent dip"     -- one entry for the highlighted region

A leading by makes value entries — the legend side of coloring by category. A text column expands to one entry per distinct value; a quoted value patches a single entry; a numeric column yields one entry whose value tokens cover the column itself, which together with the %cg ramp mark makes the continuous legend:

by species l:"%cs %l"                    -- one entry per species
by species "Gentoo" l:"%cs The gentoos"  -- rename one value
by species "Chinstrap" hide              -- drop one value
by bill_length l:"%vi %cg5 %vx"          -- 40 [ramp] 60, the continuous key
by 0 l:"%cs %l"                          -- one entry per X VALUE (col-0 rule)

Column 0 — the x column — keys value entries like any other column (by 0, or its header name): one entry per distinct x value, the group stats covering that row group's series cells.

Inside the l: value, the primary formatting instructions are indicated by %c which means the element should be colored and displayed according to the following legend format instruction.

There are currently 5 marks or symbols available for legend display:

  • %cs - Square
  • %cr - Rectangle
  • %cl - Line
  • %cc - Circle
  • %cg - Gradient

You can also suffx the label with a number indicating the relative width of the label. If you want the line to be a little longer you can use %cl1.2 or if you want a lot longer gradient you can do %cg5

In addition you have %v which represents the value of that category along with %l representing the label header.

The standalone %v represents the latest value in the given column for which the label represents, but it can be suffixed by the following:

  • %vl - last
  • %vf - first
  • %vi - minimum
  • %vx - maximum
  • %ve - median
  • %va - average (mean)

A value token can carry a format spec — the table cell-format vocabulary, appended directly to the token: %v,.1% renders the last value as a percentage with one decimal, %vx,.2m the maximum as money in millions.

Alignment belongs to columns, not tokens. A | splits the template into columns laid out side by side, and a leading <, > or - on a column sets its alignment (left, right, middle; left is the default). Column widths are computed across all entries, so values align tabularly.

l:"%cr|>%v,.1%|<%l (%vx,.2m)"
[ ] 15.1% Brazilian Real (225M)
[ ]  5.4% Polish Zloty (25M)
[ ] 34.2% United States Dollar (589M)
[ ] 25.8% European Euro (417M)
[ ]  8.9% Japanese Yen (72M)
l:">%v,.1%|%cr|<%l (%vx,.2m)"
15.1% [ ] Brazilian Real (225M)
 5.4% [ ] Polish Zloty (25M)
34.2% [ ] United States Dollar (589M)
25.8% [ ] European Euro (417M)
 8.9% [ ] Japanese Yen (72M)
exposure l:">%vi,.1m|%cg5|<%vx,.1m %l"
l:"%cr %l"
1.4M [*******] 2.8M Exposure
[ ] Above risk threshold
[ ] Not applicable