Plot

Colors

This document shows you how to change the colors of your novem plot, including themes and types.

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

Plot colors live in a single configuration file: /config/colors. Its content is a set of index color instructions (the novem ix grammar), a line-based format that selects part of your data and assigns colors to it.

In addition novem supports theming, where you can select from several pre-made themes or define your own (for professional or above subscribers) via the /config/theme endpoint.

The last thing to keep in mind is that novem also supports dark-mode, so all themes and colors can have a corresponding dark-mode variant.

Structure

As can be seen from the config overview, the color configuration is one file alongside the other single-file grammars (type, axis, margins, facets).

en_letter_frequency      => Name
...
├── config               => Configuration options
...
│   ├── colors           => The color instructions (ix grammar)
│   ├── theme            => Color theme to use
...
├── data                 => data to chart
...

Like most novem API end-points the above values can be modified with a POST request containing the desired values.

Theme

The theme endpoint allows you to refer to a global, organisational or personal theme. Custom theme support is available for professional and above subscriptions.

Everyone has access to the default theme novem along with novem-light and novem-dark.

Deprecated: the colors folder

/config/colors used to be a folder holding two files: a grammar selector (type) and the instructions (colors). The ix grammar is the only grammar, so the selector is gone and the instructions moved up to /config/colors itself. The old paths keep answering for existing integrations:

  • Writes to /config/colors/colors land transparently on /config/colors, and reads return the same content.
  • Writes to /config/colors/type are accepted and discarded; reads always answer ix.

New integrations should use /config/colors directly.

Index Colors

If you want to add colors to any of your novem tables then you'll be using index colors: write your instructions to the /config/colors endpoint.

Index colors use the novem slicing notation to describe an area of the table and then the color instructions to color the selected segment.

Index colors consists of three core components

  1. Selectors - the row and column selectors
  2. Plane - foreground or background
  3. Color Instruction - the colors and associated information
: :
Selector

A novem selector which denotes which rows and columns the color should be applied to.

bg
Plane instruction

There are two dimensions or planes you can color in a table, foreground and background.

blue-200
Novem Color Instruction

Color plane of the slice given by the row and column selectors.

See below for an example of a complex color instruction.

Static colors

A single novem color makes a static instruction: the color is applied, unchanged, to the part of the table defined by the given selector.

Both light and darkmode colors can be supplied explicitly; when the dark color is omitted it is inferred from the theme.

<rows> <cols> bg|fg color-light [color-dark]

Creating a pattern

This example shows a static background color pattern reminiscent of a picnic blanket using three shades of blue.

Here we use four selectors and take particular advantage of the step instructor. First we color alternating rows starting at different offsets. Then we add alternating columns, overwriting select colors.

-- Create a "picnic blanket" using 4 shades of blue
 ::2  :     bg blue-100 -- color every other row starting at 0
1::2  :     bg blue-200 -- color every other row starting at 1
1::2 1::2   bg blue-300 -- color every other col starting at row 1 col 1
 ::2 1::2   bg blue-200 -- color every other row starting at row 0 col 1

Adding a hierarchy

Here we emulate a hierarchical structure in our table by coloring rows in descending shades of gray, a common strategy when wanting to preserve space.

Unlike in the picnic example above, here we use a comma separated list of rows to shade, this is because there is no geometric pattern to the hierarchy, rather it depends on the data itself.

-- Color rows by "hierarchy"
1           : bg gray-500 -- world
2,5,8       : bg gray-400 -- region 1
3,6,9,12,19 : bg gray-300 -- region 2

Playing with squares

Later instructions will override the colors of the previous instructions, below is an example drawing several "squares" in different colors.

As you can see the purple square is drawn on top of the blue and red squares, this is because colors are applied in the order they are defined.

-- Create some colorful "squares"
  2:4    1:3   bg blue-200
  6:8    1:3   bg red-200
 10:12   1:3   bg green-200
  4:6    3:5   bg purple-200
  2:5    7:9   bg pink-200
  8:11   5:7   bg yellow-200

-- Creating our "gradient"
  3    11:-1   bg orange-100
  4    11:-1   bg orange-200
  5    11:-1   bg orange-300
  6    11:-1   bg orange-400
  7    11:-1   bg orange-500
  8    11:-1   bg orange-600
  9    11:-1   bg orange-700
 10    11:-1   bg orange-800
 11    11:-1   bg orange-900

Changing the instructions above from bg to fg will color the text located in the cells instead of the background as can be seen below (with 400 instead of 200 colors along with a global gray-300).

Dynamic colors

So far we've only shown static colors, but novem also supports colors based on the value contained in the cells, we call this dynamic colors. Dynamic colors are created by providing a novem color instruction consisting of a color range along with optional domain and scale.

A color range is a comma separated list of novem colors which is linearly interpolated over the corresponding cell values.

To spell the scale explicitly, add a caret ^ followed by the scale to the end of the color instruction, e.g. ^lin. Tables support linear interpolation only; charts additionally accept ^log and ^exp.

Finally you can also control the domain of the underlying values, by default the values in the cells selected by the row and column selectors are used, but this can be overridden by supplying a domain function.

The domain function consists of sequence of comma separated numbers contained in a bracket. The numbers will be matched to the corresponding color in the order supplied.

If you want to mix and match values from the underlying dataset along with specific domain values, such as forcing 0 for a neutral color in a diverging dataset, you can use the _ (underscore) operator. The _ gets replaced with the corresponding value from the dataset based on its position in the domain.

Below you can see an example of a Novem Color Instruction that creates a linear heatmap from red -> gray -> green with red for numbers below zero and green for numbers above. Numbers in the zero range are colored gray.

Note: Please be aware that Novem Color Instructions should contain no space!

bad,neutral,good
range

Create a color range of three novem theme colors. Here we use the subjective bad, neutral and good colors which are mapped to red, gray and green.

(,0,)
domain

If we didn't specify a domain, the linear midpoint in our data could be located below or above zero. Here we use the domain function to force neutral to be zero whilst using the underlying value for the lowest (bad) and highest (good) colors.

^lin
scale

Finally we state that this should be a linear color interpretation. This is the default behavior, so technically this instruction could be skipped.

Creating a heatmap

Here we use the above information to create a heatmap over a given dataset.

As you can see we get a nice linear spread across our predefined color range. In addition we've included a separate selection in the center showing that those colors are interpolated within the designated value selection.

  :     :    bg bad,neutral,good(_,0,_)^lin
 4:9   4:9   bg bad,neutral,good(_,0,_)^lin

A real world example

Whilst the above example can look a bit stylized, below is a table using real world data from our Novem Example Index dataset. If you're curious about how we made this visual check out our blog.

Coloring charts

Everything above applies to tables and charts alike — the same selector, plane, and color grammar. Charts add two abilities on top: coloring by a column's values, and a few chart-specific planes and targets. Both are covered below.

Coloring by category

Often you don't want to color a fixed selection — you want each value of a column to get its own color. Colour a scatter of measurements by species, a bar chart of sales by region: one color per distinct value, drawn from an ordinal palette. That is the by instruction.

by <selector> <palette>

by reads like the rest of the grammar — a leading keyword, then the selector naming the column whose distinct values drive the color, then the palette. Because a single slicer addresses a column, by species and by : species are the same instruction.

by
Value-color keyword

Marks the line as data-driven: color by the values in a column rather than a fixed row/column selection.

species
Key column

A selector naming the column whose distinct values become the color groups. The values are taken in first-appearance order.

tab10
Palette

A named ordinal palette (tab10, tab20, tab20b, tab20c) or an inline comma list of colors. Each distinct value takes the palette entry at its index, cycling if there are more values than colors.

The three ways to supply colors:

-- Automatic: sample an ordinal palette across the distinct values
by species tab10

-- Automatic from an inline palette
by species blue,green,orange

-- Explicit: pin specific values, the rest fall to the palette
by species tab10 Gentoo:red

-- The x column is a first-class key (the col-0 rule): every bar
-- takes its own category's color
by 0 tab10

Column 0 — the x column — is addressable like any other key, by index (by 0) or by its header name: each mark takes the color of its row's x value, the each-bar-its-own-color spelling. The synthesized legend captions the categories.

Explicit value:color pairs pin a color to a named value; any value you don't pin is filled from the palette (or the theme's categorical colors when no palette is given). Both light and dark variants are carried by named palettes automatically — and by theme tokens (blue-500) in inline palettes and pins, so dark mode comes for free unless you write raw hex.

by also gives you the key to read it with: when your plot has no legend configured, the distinct values and their swatches render as an automatically placed legend. A legend you configure yourself always wins.

by composes with facets, including on the facet key itself. Every row in a panel shares the key value, so the whole panel takes that value's color — the marks by default, or the panel background with the bg plane:

-- with facets:  x : sex  /  y : species
by : sex tab10               -- marks colored by the sex facet
by : sex bg tab10            -- panel backgrounds tinted by sex
by : sex bg red,green(_,_)   -- panels tinted by their group average

The last form makes a heatmap of the facet grid: each panel's background samples the gradient at its group's average value, on a domain shared across all panels.

Dynamic magnitude within a group

A gradient after by colors each mark by its magnitude, grouped by the key column — the color scale runs across the values, the grouping decides the domain:

-- one shared color scale across all groups (comparable)
by species red,blue^lin

-- each group scaled to its own range (self-scaled small multiples)
by species red,blue^lin ax:0

A numeric key column is the magnitude itself: by bill_length red,blue^lin colors every mark by its bill length, over that column's own range. (The ^scale — or an explicit (domain) — is what makes a color list a gradient; without it red,blue is a discrete palette cycling over the column's distinct values.)

The ax: modifier is the same one facets use for sharing scales: ax:xy (the default) takes the magnitude range across all the data, so colors are comparable between groups and read against one legend; ax:0 gives each group its own min-to-max.

Planes

Where a table cell has a foreground and a background, a chart mark has a few planes. Name one between the selector and the color:

[selector] [plane] color
fill
The mark body — a bar's fill, a point's disc, a line's stroke. This is the default when no plane is given.
stroke
The outline — a point or bar border.
text
Data-label color.
: 1 stroke black      -- the first series: a black outline
: 1 fill  blue-500    -- the first series: a blue body

Marks whose ink is a stroke — scatter points, line strokes — take stroke-plane colors as their ink directly; on a bar the stroke plane draws an outline around the body. A plane a plot doesn't draw is silently ignored.

The table planes bg and fg still parse on a chart (they mean fill), so a color string authored for a table pastes onto a chart unchanged.

Background

A leading bg colors the plot's background surface instead of any data — light and dark:

bg gray-50 slate-900

This is the per-plot override of the theme's surface color. On a faceted plot the lone form washes the whole surface — panels and the gaps between them alike. To tint individual facet cells instead, lead with the slicer pair over the facet grid — the same cell addressing the margins file uses: one slicer is columns, two are rows × columns, negatives count from the end. The older r:/c: key spelling keeps working:

bg 0 : blue-100 slate-900       -- the top facet row
bg -1 1 red-100 red-900         -- one cell: last row, second column
bg red-100 red-900 r:-1 c:1     -- the same cell, the older spelling

Palette cycling

A line of exactly three dots hands the unassigned series to the theme palette, cycling from its first color:

0 red-500        -- the first series is explicit
...              -- everything else cycles the theme palette from c1

Without the sentinel, unassigned series keep their theme default (the palette color of their own series index). The sentinel stands alone on its line and is position-independent.

bgi is the inner sibling: it washes the area inside the plot frame — the region an axis o instruction outlines — leaving the margins and axis furniture on the page surface. On a faceted plot each panel washes its own inner box, and r:/c: scope it to specific cells the same way:

bgi blue-100 slate-800          -- every inner box
bgi amber-100 amber-900 c:0     -- the first column's inner boxes only

Panel tints layer sensibly: a cell's bg paints the whole cell, its bgi paints only the frame's interior on top of it, and the marks draw above both. The by … bg form (color-by-facet, above) remains the value-driven way to tint panels.