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/colorsland transparently on/config/colors, and reads return the same content. - Writes to
/config/colors/typeare accepted and discarded; reads always answerix.
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
- Selectors - the row and column selectors
- Plane - foreground or background
- 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
planeof theslicegiven 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.