theme

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package theme holds the visual tokens a chart is drawn with: colours, fonts, sizes and spacings.

A theme is plain data. It is resolved once, before lowering, and every decision that is "how should this look" reads from it rather than hardcoding a value — which is what makes a second theme a data change instead of a code change.

Tokens

The full Theme is wide, and most of it follows from a handful of choices. Tokens holds those choices and Build derives the rest, so a new theme is a dozen lines rather than fifty. Theme.With edits a built theme, and Register and ByName make a theme reachable by name — which is what a configuration file or a command-line flag needs.

Index

Constants

This section is empty.

Variables

Dark is the inverted theme: light ink on a near-black surface.

View Source
var DarkTokens = Tokens{
	Name:       "dark",
	Background: ir.RGB(0x14, 0x16, 0x1A),
	Panel:      ir.Transparent,
	Strip:      ir.RGB(0x22, 0x26, 0x2D),

	Ink:       ir.RGB(0xF2, 0xF3, 0xF5),
	InkMuted:  ir.RGB(0xC8, 0xCB, 0xD0),
	InkSubtle: ir.RGB(0xA0, 0xA4, 0xAB),

	Line:       ir.RGB(0x9A, 0x9E, 0xA6),
	LineSubtle: ir.RGB(0x2A, 0x2E, 0x35),

	FontSize: 12,
	Space:    4,

	Palette:    palette.OkabeIto,
	Sequential: palette.Viridis,
	Diverging:  palette.BlueOrange,
}

DarkTokens are the choices the dark theme makes: light ink on near-black.

The axis is deliberately not the same grey as the labels. On a dark surface a line at the label's lightness reads heavier than the same line does on white, because it is the brighter of the two against its background rather than the darker.

Light is the default theme: dark ink on a white surface.

View Source
var LightTokens = Tokens{
	Name:       "light",
	Background: ir.RGB(0xFF, 0xFF, 0xFF),
	Panel:      ir.Transparent,
	Strip:      ir.RGB(0xEE, 0xEF, 0xF1),

	Ink:       ir.RGB(0x1A, 0x1A, 0x1A),
	InkMuted:  ir.RGB(0x33, 0x33, 0x33),
	InkSubtle: ir.RGB(0x55, 0x55, 0x55),

	Line:       ir.RGB(0x33, 0x33, 0x33),
	LineSubtle: ir.RGB(0xDD, 0xDD, 0xDD),

	FontSize: 12,
	Space:    4,

	Palette:    palette.OkabeIto,
	Sequential: palette.Viridis,
	Diverging:  palette.BlueOrange,
}

LightTokens are the choices the light theme makes: dark ink on white.

Functions

func Names added in v0.3.0

func Names() []string

Names lists the registered theme names in order.

func Register added in v0.3.0

func Register(t Theme)

Register adds t to the registry under its own name, replacing any theme already registered under it. A theme with an empty name is ignored.

Types

type Option added in v0.3.0

type Option func(*Theme)

Option edits a theme. Options exist so that "the dark theme, in my typeface, without a grid" is a call rather than a copy of the struct with three fields changed and forty copied.

func AxisLines added in v0.3.0

func AxisLines(x, y bool) Option

AxisLines turns the axis rules on or off.

func Background added in v0.3.0

func Background(c ir.Color) Option

Background sets the canvas colour behind everything.

func Density added in v0.3.0

func Density(f float64) Option

Density scales every spacing by f, tightening or loosening the whole chart at once. Text sizes are left alone: a dense chart with unreadable labels is not denser, it is worse.

func FontFamily added in v0.3.0

func FontFamily(name string) Option

FontFamily sets the logical font family. It is a name, not a file: the backend resolves it, and the SVG backend passes it through to the viewer.

func FontSize added in v0.3.0

func FontSize(size float64) Option

FontSize rescales all three text sizes so that the label size becomes size, keeping the ratios between title, label and tick.

func Grid added in v0.3.0

func Grid(x, y bool) Option

Grid turns the horizontal and vertical grid lines on or off.

func Named added in v0.3.0

func Named(name string) Option

Named sets the theme's name, which is what Register keys it under.

func Palette added in v0.3.0

func Palette(p palette.Qualitative) Option

Palette sets the qualitative sequence layers take their colours from.

func PlotFill added in v0.3.0

func PlotFill(c ir.Color) Option

PlotFill sets the fill of the plot area itself, which is transparent by default so the background shows through.

func Ramps added in v0.3.0

func Ramps(sequential, diverging palette.Ramp) Option

Ramps sets the sequential and diverging ramps a colour scale falls back to. A nil argument leaves that ramp alone.

func TickCounts added in v0.3.0

func TickCounts(x, y int) Option

TickCounts sets how many ticks each axis aims for. They are hints: a scale rounds to whatever produces readable labels.

type Theme

type Theme struct {
	Name string

	// Surfaces.
	Background ir.Color // outside the plot area
	PlotFill   ir.Color // inside the plot area

	// Text.
	FontFamily string
	TitleSize  float64
	LabelSize  float64
	TickSize   float64
	TitleColor ir.Color
	LabelColor ir.Color
	TickColor  ir.Color

	// Axes and grid.
	AxisColor      ir.Color
	AxisWidth      float32
	GridColor      ir.Color
	GridWidth      float32
	GridDash       []float32
	TickLength     float32
	TickLabelPad   float32
	AxisTitlePad   float32
	ShowGridX      bool
	ShowGridY      bool
	ShowAxisLineX  bool
	ShowAxisLineY  bool
	TickCountHintX int
	TickCountHintY int

	// Legend.
	LegendSwatch  float32
	LegendPad     float32
	LegendGap     float32
	LegendColor   ir.Color
	LegendBG      ir.Color
	LegendBorder  ir.Color
	LegendPadding float32

	// Guides. A chart can carry more than one — a legend for the layers that
	// have a colour each, a colourbar for a layer whose colour comes from a
	// continuous scale — and they stack in one column beside the plot.
	GuideGap float32 // vertical gap between stacked guides

	// Colourbar.
	ColorbarThickness float32 // across the bar
	ColorbarFraction  float64 // of the plot height, before clamping
	ColorbarBorder    ir.Color
	ColorbarTickCount int

	// Annotations: reference lines, bands and labels that are not data.
	AnnotationColor   ir.Color
	AnnotationWidth   float32
	AnnotationDash    []float32
	AnnotationOpacity float64 // fill alpha for a shaded band

	// Panels: subplots and facets.
	PanelGap    float32 // between adjacent panels
	StripBG     ir.Color
	StripColor  ir.Color
	StripSize   float64
	StripPad    float32
	StripBorder ir.Color

	// Spacing.
	Margin float32 // outer margin on all four sides

	// Series colours, and the ramps a colour scale falls back to.
	Palette    palette.Qualitative
	Sequential palette.Ramp
	Diverging  palette.Ramp

	// Default geometry weights.
	LineWidth  float32
	MarkerSize float32
}

Theme is the full set of visual tokens for a chart.

func Build added in v0.3.0

func Build(t Tokens) Theme

Build derives a full theme from its tokens.

The type scale is a perfect fourth up for the title and one step down for ticks — 12 gives 16 and 11, which is where the hand-written v0.1 themes already were. The ratios are written as exact fractions rather than decimals so that a round base size produces round derived sizes rather than 15.999.

Spacings are multiples of Tokens.Space. With the default unit of 4 that reproduces the v0.1 numbers exactly, which is the point: a derived theme has to be able to express what was there before it, or the derivation is telling you what to want.

func ByName added in v0.3.0

func ByName(name string) (Theme, bool)

ByName returns the registered theme with the given name.

func (Theme) DivergingRamp added in v0.3.0

func (t Theme) DivergingRamp() palette.Ramp

DivergingRamp returns the theme's diverging ramp, falling back to the package default.

func (Theme) Font

func (t Theme) Font(size float64) ir.FontRef

Font builds a font reference from the theme's family at the given size.

func (Theme) SequentialRamp added in v0.3.0

func (t Theme) SequentialRamp() palette.Ramp

SequentialRamp returns the theme's sequential ramp, falling back to the package default so that a zero Theme still produces colours.

func (Theme) Tokens added in v0.3.0

func (t Theme) Tokens() Tokens

Tokens returns the tokens a theme was built from, as far as they can be read back off it.

It is not an inverse of Build — a theme that has been edited afterwards may no longer be derivable from any tokens at all. It is what makes "the dark theme, but in my brand's typeface" one call rather than a rebuild from scratch.

func (Theme) With added in v0.3.0

func (t Theme) With(opts ...Option) Theme

With returns a copy of t with the options applied. The receiver is not modified, so package-level themes stay what they are.

type Tokens added in v0.3.0

type Tokens struct {
	// Name identifies the theme, and is the key it registers under.
	Name string

	// Background is the canvas outside the plot area; Panel is the fill
	// inside it, normally transparent so the background shows through; Strip
	// is the band a facet's label sits in.
	Background ir.Color
	Panel      ir.Color
	Strip      ir.Color

	// Ink runs from the strongest text to the faintest: a title, a label, a
	// tick. Three levels is enough to build a hierarchy and few enough that a
	// second theme has to answer only three questions.
	Ink       ir.Color
	InkMuted  ir.Color
	InkSubtle ir.Color

	// Line is the axis; LineSubtle is the grid behind the data.
	Line       ir.Color
	LineSubtle ir.Color

	// FontFamily is the logical family; "" means the backend's default sans.
	FontFamily string

	// FontSize is the label size in device units. The title and tick sizes
	// derive from it — see [Build].
	FontSize float64

	// Space is the spacing unit every padding and gap is a multiple of.
	Space float64

	// Palette colours the layers of a chart; Sequential and Diverging are the
	// ramps a colour scale falls back to when it is given none.
	Palette    palette.Qualitative
	Sequential palette.Ramp
	Diverging  palette.Ramp
}

Tokens is the small set of decisions a theme actually makes.

A Theme has around fifty fields, and almost all of them are consequences rather than choices: a tick label is the same colour as the other faint ink, the gap under an axis title is the same rhythm as the gap beside a legend. Writing all fifty out by hand is how two themes drift apart in ways nobody intended. Tokens holds the choices — four inks, two line weights, one type size, one spacing unit, the colours — and Build derives the rest.

Anything derived can still be overridden afterwards: Build returns a plain struct, and Theme.With edits it. Tokens is the starting point, not a cage.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL