geom

package
v0.8.0 Latest Latest
Warning

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

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

Documentation

Overview

Package geom holds the visual marks a chart is made of.

A geom knows what its shape is and never how it is rendered: it reads columns, asks the scales where values go, and emits IR. It has no reference to a backend, no knowledge of SVG or raster, and no opinion about layout beyond the rectangle it is given.

Index

Constants

This section is empty.

Variables

View Source
var ErrCategorical = errors.New("refract/geom: categorical column on a continuous scale")

ErrCategorical reports a text column mapped onto an axis that has no position for a name.

View Source
var ErrNoColumn = errors.New("refract/geom: column not found")

ErrNoColumn reports a column named by an option that the source does not have. It is returned rather than panicking because the column name usually comes from user input or a config file.

View Source
var ErrUnknownMark = fmt.Errorf("refract/geom: unknown mark")

ErrUnknownMark reports a Desc naming a mark this package does not have.

Functions

This section is empty.

Types

type ColorGuide added in v0.3.0

type ColorGuide struct {
	// Label titles the bar. It defaults to the name of the coloured column.
	Label string
	// Scale is the trained colour scale the bar shows.
	Scale scale.ColorScale
}

ColorGuide is the guide a layer needs when its colour comes from a continuous scale: a colourbar, not a legend swatch.

A single swatch cannot represent a ramp — it would have to pick one colour out of a continuum and label it with a column name — so a layer using ColorBy contributes one of these instead of a LegendEntry.

func (ColorGuide) Key added in v0.3.0

func (g ColorGuide) Key() string

Key identifies a guide by what it looks like rather than by which scale object produced it.

Two layers sharing one colour scale must produce one colourbar, and so must two layers whose separate scales agree about the label, the domain and the ramp — those would be drawn identically, so showing both would only take room from the chart. Comparing the scales themselves is not available: a ColorScale is an interface, and an implementation is free to be a type that == panics on.

type Datum added in v0.5.0

type Datum struct {
	X0, Y0, X1, Y1 float64
}

Datum carries the values an annotation is placed by. A rule uses X0 or Y0 alone, a band a pair on one axis, a segment and a region all four.

type Decimation added in v0.4.0

type Decimation uint8

Decimation is how a layer reduces its rows before it draws them.

A plot area is a few hundred pixels wide. A column with a million rows in it therefore has thousands of rows per pixel column, and drawing all of them emits thousands of segments that land on the same handful of pixels: slower to build, slower to rasterize, larger as a file, and not one pixel different. Reducing first is not a compromise for big data, it is the accurate way to draw it.

Nothing here touches a scale's domain. A geom trains on every row and reduces only when it draws, so the axes report the data rather than the subset that survived.

const (
	// AutoDecimation lets the layer choose — see [Decimate] for what it picks.
	AutoDecimation Decimation = iota

	// NoDecimation draws every row. It is what to ask for when the chart is
	// evidence and a reader will zoom into the vector output.
	NoDecimation

	// LTTB keeps the rows that carry a line's shape, using
	// largest-triangle-three-buckets. See [stat.LTTB].
	LTTB

	// MinMax keeps the extremes of every pixel column, so a spike one sample
	// wide still reaches full height. See [stat.MinMax].
	MinMax

	// DensityRaster counts rows per cell and draws the counts as an image
	// instead of drawing marks. See [stat.Grid].
	DensityRaster
)

The reductions. AutoDecimation is the default: a geom knows what its mark is and can see how many rows there are against how wide the plot is, which is exactly the information the choice needs.

type Desc added in v0.5.0

type Desc struct {
	// Mark is what the layer draws.
	Mark Mark
	// Source is the layer's data. It is nil for an annotation, which takes
	// values rather than columns.
	Source data.Source

	// X, Y, X2 and Y2 name the columns mapped to the axes. ColorCol and
	// ColorScale are [ColorBy]'s two halves.
	X, Y, X2, Y2 string
	ColorCol     string
	ColorScale   scale.ColorScale

	// Group names the series column, and Stack, Dodge, DodgePad, Order and
	// WidthCol are the position adjustment defined over it.
	//
	// Stack carries the adjustment the layer is actually using rather than the
	// one it was given — a grouped bar that was told nothing stacks, and a
	// Desc that said NoStack for one would describe a different chart — and
	// StackSet reports whether the layer chose it. The pair is [Dash] and
	// DashSet again, and for the same reason: NoStack is both the zero value
	// and an adjustment somebody may have asked for, and without the flag a
	// round trip through the spec would turn a grouped bar's default into a
	// pinned "do not stack".
	Group    string
	Stack    Stacking
	StackSet bool
	Dodge    bool
	DodgePad float64
	Order    Ordering
	WidthCol string

	// Explode is how far the layer's marks are broken out of the middle of the
	// coord, as a fraction of its outer radius, and ExplodeCol the column that
	// answers it per row. Zero and "" are a layer that stays where it is.
	Explode    float64
	ExplodeCol string

	// Label names the layer in the legend.
	Label string

	// Datum places an annotation, and Text is a note's text. Both are unused
	// by a layer that has a Source.
	Datum Datum
	Text  string

	// The styling options, one field per [Option]. A nil Color or Fill means
	// the layer takes its colour from the palette.
	Color   *ir.Color
	Fill    *ir.Color
	Width   float32
	Dash    []float32
	DashSet bool
	Tension float64
	Missing Missing
	// Marker is the shape a scatter draws, and MarkerSet reports whether the
	// layer chose it. The pair is [Dash] and DashSet again, and for the same
	// reason: a circle is both the zero value and a shape somebody may have
	// asked for, and a theme's redundant encoding replaces the first but not
	// the second.
	Marker    ir.Marker
	MarkerSet bool
	// Closed reports a connected layer that joins its last mark back to its
	// first — the radar contour of [Closed].
	Closed   bool
	Size     float32
	BarWidth float64
	Baseline float64
	Opacity  float64
	Steps    StepPos
	Whisker  float64
	Outliers bool
	Decimate Decimation
	Budget   int
	CellSize float64
	FontSize float64
	HAlign   ir.HAlign
	VAlign   ir.VAlign
	Rotation float64
	Extend   bool
}

Desc is a layer reduced to what configures it: its mark, its data, the columns it reads and the options it was given.

It exists so that a chart can be written down and read back. A Geom is an interface with three methods and no way to ask it what it is, which is right for drawing and useless for serialization — so every layer in this package answers Describer, and FromDesc turns the answer back into a layer that draws the same marks.

A field left at its zero value means "not set", exactly as leaving the corresponding Option out does. The fields that have a non-zero default — BarWidth, Whisker, Outliers, Opacity, Extend — are filled in by Describe with the default the layer is actually using, so a Desc is complete rather than partial.

func Describe added in v0.5.0

func Describe(g Geom) (Desc, bool)

Describe reports g's configuration, or ok == false if g cannot describe itself.

type Describer added in v0.5.0

type Describer interface {
	// Describe returns the layer's configuration.
	Describe() Desc
}

Describer is implemented by a layer that can say what it is.

It is an optional interface, like Faceter and Guided: a third-party geom that does not implement it still draws, and is simply not serializable. That is a better failure than a half-written spec — see github.com/timzifer/refract/spec.

type Faceter added in v0.3.0

type Faceter interface {
	// Source returns the layer's data, so a facet can read the column it
	// splits on.
	Source() data.Source

	// Subset returns a copy of this layer restricted to the given rows. The
	// copy shares its configuration with the original, including any colour
	// scale — which is what makes one colour mean one thing across every
	// panel, and one colourbar enough to say so.
	Subset(rows []int) Geom
}

Faceter is implemented by a layer that can be split into panels.

Faceting is a data operation, and a geom is the only thing that knows which data it holds — so the split happens here rather than in the facet package reaching into a layer. A layer that does not implement Faceter is drawn unchanged in every panel, which is exactly right for an annotation: a threshold line belongs on all of them.

type Frame

type Frame struct {
	// Area is the plot rectangle in device space. Scales already map into it.
	Area ir.Rect
	// X and Y are the trained, ranged scales.
	X, Y scale.Scale
	// Coord decides what the interval a scale maps into means, and therefore
	// where a mapped pair lands. A nil Coord is [coord.Cartesian], so a Frame
	// built by code that never heard of coordinate systems draws the chart it
	// always drew — reach for [Frame.Coords] rather than the field.
	Coord coord.Coord
	// Theme supplies defaults the geom did not override.
	Theme theme.Theme
	// Index is the geom's position among the chart's layers, used to pick a
	// default colour from the palette.
	Index int

	// Rows, when non-nil, collects which source row is behind each mark the
	// geom draws. It is nil for an ordinary render and a geom must check it
	// before doing the bookkeeping — see [Rows] and [Frame.Marks].
	Rows Rows
}

Frame is everything a geom needs to turn data into IR.

func (Frame) Coords added in v0.8.0

func (f Frame) Coords() coord.Coord

Coords is the frame's coordinate system, which is coord.Cartesian framed in the plot rectangle when it has none.

A geom asks for it once at the top of Build and uses what comes back: the answer never changes within one frame, and the nil check is not worth repeating per row. Framing the fallback rather than handing back a bare Cartesian is what makes a Frame built by hand — in a test, or by a caller driving a geom directly — behave as it always did: the coord it gets can answer coord.Coord.Extent, which is where a rule that spans the plot finds the far edge.

func (Frame) Marks added in v0.5.0

func (f Frame) Marks(at []ir.Point, rows []int)

Marks reports the rows behind a set of marks, if anyone asked. A geom calls it with the positions a row landed at, whatever it then draws through them.

type Geom

type Geom interface {
	// Train feeds the geom's data into the scales so they can establish their
	// domains. It runs before layout, because layout needs tick labels and
	// tick labels need a domain.
	Train(x, y scale.Scale) error

	// Build emits the geom's marks into b.
	Build(b ir.Backend, f Frame) error

	// Legend returns the entry this geom contributes, or ok == false if it
	// should not appear in the legend.
	Legend(f Frame) (LegendEntry, bool)
}

Geom is a layer of marks.

func Area added in v0.2.0

func Area(src data.Source, opts ...Option) Geom

Area fills the region between a series and a baseline, or — given Y2 — the band between two series.

The upper edge is stroked in the layer's full colour and the interior is filled with a faded version of it. A band drawn in one flat colour reads as a solid object; a band with a drawn edge reads as a series with uncertainty around it, which is what an area chart is for. Use Opacity to change the fill, Fill to set it outright.

Given GroupBy it draws one band per series, stacked: a stacked area chart. Stack chooses the baseline they are stacked about — StackFill for a 100 % chart, StackSilhouette for a ThemeRiver, StackWiggle for a streamgraph — and the fill is solid rather than faded, because the bands of a stack are read against each other rather than through each other.

func Bar

func Bar(src data.Source, opts ...Option) Geom

Bar draws a rectangle per row, from a baseline to the row's Y value.

Given GroupBy it draws one rectangle per row per series, stacked from the baseline up: a long table with a series column is a stacked bar chart, and Stack and Dodge are how it becomes a 100 % chart or a grouped one instead. See docs/adr/0019-position-adjustments.md.

The cross axis is the bar's width, and a row that names both of its edges with X and X2 gets exactly those rather than a share of the slot. In github.com/timzifer/refract/coord.Donut that axis is the radius, so the pair is a slice's inner and outer radius: a donut whose slices reach different distances is this layer with one more column, not another mark. Explode and ExplodeBy then break a slice out of the ring.

func Boxplot added in v0.2.0

func Boxplot(src data.Source, opts ...Option) Geom

Boxplot summarises the distribution of the Y column within each distinct X value: a box spanning the interquartile range, a line at the median, whiskers reaching to the furthest observation within Whisker times the IQR, and a marker for every observation beyond them.

The X column is the grouping key, so a boxplot wants many rows per X value — typically a categorical column against a scale.Ordinal axis, which also gives every box the same width. On a continuous axis the width comes from the closest pair of groups, as it does for a bar.

Whiskers stop at an observation, never at the theoretical fence. A whisker drawn out to 1.5·IQR when the data stops well short of it would be claiming a reading that does not exist.

func FromDesc added in v0.5.0

func FromDesc(d Desc) (Geom, error)

FromDesc builds the layer d describes.

It is the inverse of Describe over every layer in this package: describing a layer and rebuilding it produces one that draws the same marks. A layer with data needs a Source; an annotation ignores one.

func HBand added in v0.3.0

func HBand(y0, y1 float64, opts ...Option) Geom

HBand shades the horizontal strip between two Y values, across the whole plot. It is how a tolerance range or a target window is drawn.

func HLine added in v0.3.0

func HLine(y float64, opts ...Option) Geom

HLine draws a horizontal reference line across the plot at y.

func Line

func Line(src data.Source, opts ...Option) Geom

Line connects consecutive rows with a stroked path.

Given GroupBy it draws one path per series over a long table, each in its own colour and named in the legend — which is what a table of measurements with a series column plots as, and is one layer rather than N.

func Note added in v0.3.0

func Note(x, y float64, text string, opts ...Option) Geom

Note places a text label at a position in data space.

Alignment is about that position: the default puts the text's start on it. Use Align to hang the label off the other side of a line it is naming.

func Rect added in v0.7.0

func Rect(src data.Source, opts ...Option) Geom

Rect draws one rectangle per row, occupying an arbitrary box in data space.

It is the mark Bar is not: a bar grows from a shared baseline to a value and therefore always touches the axis, while a rect is bounded on both axes by the row itself. That one difference is what a heatmap, a gantt chart, a candle, a waterfall step and a waffle cell all need, and it is why they are recipes over this mark rather than five more geoms — see docs/chart-types.md.

An edge the row does not name is the slot the axis implies: a band scale's own bandwidth, or the closest spacing in the data narrowed by BarWidth. So a heatmap over two categorical axes needs two columns and a colour, and nothing else:

geom.Rect(src, geom.X("day"), geom.Y("hour"),
    geom.ColorBy("calls", scale.Sequential(palette.Viridis)))

while a gantt bar, which knows where it starts and stops, names both:

geom.Rect(src, geom.X("start"), geom.X2("end"), geom.Y("task"))

X2 gives the far edge on the horizontal axis and Y2 on the vertical one. A ColorBy column paints each cell separately, through a ramp or through a qualitative palette; without one the whole layer is one colour.

Under a polar coord a cell is an annular sector, so Explode and ExplodeBy break one out of the middle exactly as they do a slice of a donut — the two marks that draw a sector are the two that can leave one.

Cells that share an edge are drawn as separate shapes and antialiased separately, so a shared edge can show as a hairline of the background. That is compositing rather than a gap in the data — the cells are exactly contiguous, and the golden files say so — and removing it would need a mesh primitive in the IR, which is the thing ADR 0007 exists to refuse. Use BarWidth or the axis's own padding for a gap that is meant to be seen.

func Region added in v0.3.0

func Region(x0, y0, x1, y1 float64, opts ...Option) Geom

Region shades an axis-aligned rectangle in data space, bounded on both axes. HBand and VBand are the cases where one axis is unbounded.

func Scatter

func Scatter(src data.Source, opts ...Option) Geom

Scatter draws one marker per row.

Given GroupBy it draws one set of markers per series, each in its own colour and — where the theme asks for redundant encoding — its own shape.

func Segment added in v0.3.0

func Segment(x0, y0, x1, y1 float64, opts ...Option) Geom

Segment draws a straight line between two points in data space. Unlike HLine it does not span the plot, so it is what an arrow-less callout or a hand-placed trend line is made of.

func Step added in v0.2.0

func Step(src data.Source, opts ...Option) Geom

Step connects rows with horizontal and vertical segments instead of a straight line.

It is the honest shape for anything that holds a value and then changes it — a configuration, a queue depth, a price, a state machine. A plain line between two such samples draws a gradual transition that never happened. Use Steps to say where the change falls between the two rows.

func VBand added in v0.3.0

func VBand(x0, x1 float64, opts ...Option) Geom

VBand shades the vertical strip between two X values, across the whole plot. It is how a maintenance window or a highlighted interval is drawn.

func VLine added in v0.3.0

func VLine(x float64, opts ...Option) Geom

VLine draws a vertical reference line across the plot at x.

type Guided added in v0.3.0

type Guided interface {
	// ColorGuide returns the colour guide this layer contributes, or
	// ok == false if it has none.
	ColorGuide() (ColorGuide, bool)
}

Guided is implemented by a layer that paints from a continuous colour scale.

It is an optional interface rather than a method on Geom: a layer that colours itself one colour has nothing to say here, and every third-party geom would otherwise have to write a stub returning false.

type LegendEntry

type LegendEntry struct {
	Label  string
	Color  ir.Color
	Kind   SwatchKind
	Marker ir.Marker
	Dash   []float32
	Width  float32
}

LegendEntry is a geom's contribution to the legend.

func Legends added in v0.7.0

func Legends(g Geom, f Frame) []LegendEntry

Legends returns the legend entries of a layer: its own list where it has one, and its single entry otherwise.

It is what render calls, so that the preference between the two interfaces is written down once.

func LegendsOr added in v0.7.0

func LegendsOr(g Geom, f Frame, many []LegendEntry) []LegendEntry

LegendsOr is a layer's own entries, or its single one where it has no series and no categories to name.

Every geom here that can contribute many entries can also contribute one — the same Bar draws a stack of five series or one plain row of bars — and this is the one place that decides which.

type Legender added in v0.7.0

type Legender interface {
	// Legends returns the entries this layer contributes, in legend order.
	//
	// It is the whole answer: a layer that implements it is not asked for
	// [Geom.Legend] as well, so a layer with one entry returns that one entry
	// here, and returning none means the layer stays out of the legend. The
	// geoms in this package answer it through [LegendsOr], which is that
	// fallback written once.
	Legends(f Frame) []LegendEntry
}

Legender is implemented by a layer that contributes more than one legend entry.

A pie has N slices inside one layer. So does a stacked bar, a grouped bar, a waffle and anything painted from a discrete colour scale — and Geom.Legend's single entry cannot name them. It is an *optional* interface rather than a second method on Geom because most geoms have exactly one entry to contribute and would otherwise all need a stub: the same argument Guided already made for colourbars, and the reason the extension API can still freeze at v1.0 with three methods on Geom. See docs/adr/0020-discrete-colour-and-multi-entry-legends.md.

type Mark added in v0.5.0

type Mark string

Mark names what a layer draws. It is the one word that decides which constructor built a layer, and the hinge a serialized chart turns on.

const (
	MarkLine    Mark = "line"
	MarkScatter Mark = "scatter"
	MarkBar     Mark = "bar"
	MarkArea    Mark = "area"
	MarkStep    Mark = "step"
	MarkBoxplot Mark = "boxplot"
	MarkRect    Mark = "rect"

	MarkHLine   Mark = "hline"
	MarkVLine   Mark = "vline"
	MarkHBand   Mark = "hband"
	MarkVBand   Mark = "vband"
	MarkSegment Mark = "segment"
	MarkRegion  Mark = "region"
	MarkNote    Mark = "note"
)

The marks. These are refract's own names for its layers; the JSON spec translates them into its own vocabulary rather than the other way round, so that this package stays ignorant of any wire format.

type Missing

type Missing uint8

Missing is the policy for NaN and infinite values in a column.

const (
	Gap Missing = iota
	Interpolate
	Error
)

The missing-data policies. Gap is the default: a hole in the data should look like a hole, not like a straight line someone might read as real.

type Option

type Option func(*config)

Option configures a geom. Options are shared across geom constructors: an option a given geom has no use for is accepted and ignored, which keeps the API one namespace instead of six.

func Align added in v0.3.0

func Align(h ir.HAlign, v ir.VAlign) Option

Align sets how a text annotation sits about its position. The default is the run's start on the point, on the baseline.

func BarWidth

func BarWidth(f float64) Option

BarWidth sets bar width as a fraction of the spacing between adjacent bars, in (0, 1]. The default is 0.8.

func Baseline

func Baseline(v float64) Option

Baseline sets the value bars and areas grow from. The default is 0.

func Budget added in v0.4.0

func Budget(n int) Option

Budget caps how many marks a reduced layer draws. The default is derived from the width of the plot area, which is the only number that decides how many marks are distinguishable.

It has no effect on a layer that is not reducing, and none on DensityRaster, whose resolution is the plot area itself.

func Closed added in v0.8.0

func Closed(on bool) Option

Closed joins the last mark of a connected layer back to the first.

It is what turns a Line over an angular axis into a radar contour and an Area over one into a filled one: five axes drawn as an open line leave a gap between the last and the first, which is a hole in a shape that has none. It applies to Line, Area and Step; a layer whose marks are not connected ignores it.

It is an option rather than something coord.Polar decides, because whether a series wraps is a fact about the series and not about the transform: a radar covers every axis and closes, and a polar time series spiralling through three revolutions does not.

func Color

func Color(col ir.Color) Option

Color sets the mark colour, overriding the palette.

func ColorBy added in v0.2.0

func ColorBy(col string, s scale.ColorScale) Option

ColorBy maps a column through a colour scale, giving every mark its own colour. It applies to Scatter, Bar and Rect; geoms whose mark is one connected shape ignore it.

Which guide the layer then contributes follows from the kind of scale it was handed. A continuous scale — scale.Sequential, scale.Diverging — reads a numeric column and contributes a colourbar, because a single swatch cannot represent a continuum; see ColorGuide. A discrete one — scale.Qualitative — reads a column of categories and contributes one legend entry per category; see Legender.

The same scale also colours the series of a grouped layer, where the group label is the category. Sharing one across the layers of a chart, or across the panels of a facet, is what makes one colour mean one thing everywhere.

func Dash

func Dash(pattern ...float32) Option

Dash sets a dash pattern in device units. Dashing a series is redundant encoding: it keeps the chart readable in greyscale and for colourblind readers.

func Decimate added in v0.4.0

func Decimate(d Decimation) Option

Decimate sets how a layer reduces its rows before drawing.

The default, AutoDecimation, picks by mark and by size:

  • a line reduces with LTTB once it holds more than a few rows per pixel column, because a line is a shape and LTTB is the reduction that keeps one;
  • a step and a band reduce with MinMax, because their ink is their extremes — a staircase is its transitions and a band is its edges;
  • a scatter switches to a DensityRaster once its markers would cover the plot area several times over, because at that point the marks overplot and the picture says more about row order than about the data;
  • a bar or a boxplot never reduces: those marks already aggregate, and there is no row-level reduction that leaves a bar a bar.

A layer whose colour comes from a column is never reduced automatically: it draws one fact per mark, and a raster of counts would answer a different question. Ask for DensityRaster explicitly to override that.

func DensityCells added in v0.4.0

func DensityCells(px float64) Option

DensityCells sets the size of one density-raster cell in device pixels. The default is 1: one cell per pixel, which is as fine as the output can show. Larger cells trade resolution for a smaller image and a smoother picture.

func Dodge added in v0.7.0

func Dodge(padding float64) Option

Dodge places the groups of a slot side by side instead of stacking them, leaving padding of the slot blank between them — 0.1 is a tenth of the slot's width, and the default when Dodge is asked for with a padding outside [0, 1).

It is the other answer to the same question stacking answers, and a layer has one or the other: a dodged layer is not stacked, whatever Stack said. Comparing the parts is what dodging is for and comparing the totals is what stacking is for; a chart that needs both needs two layers.

func Explode added in v0.8.0

func Explode(f float64) Option

Explode breaks a layer's marks out of the middle of the coord, by the given fraction of its outer radius.

It is what pulls a slice out of a donut, and a tenth is about as far as one goes before the ring stops reading as a ring:

geom.Bar(src, geom.X("all"), geom.Y("share"), geom.GroupBy("browser"),
    geom.Explode(0.08))

Every mark of the layer moves, which is rarely what a chart means — one slice is usually the point. ExplodeBy is that, per row.

A coord with no middle to move away from ignores it: see github.com/timzifer/refract/coord.Exploder, which coord.Cartesian deliberately does not implement.

func ExplodeBy added in v0.8.0

func ExplodeBy(col string) Option

ExplodeBy reads the break-out per row from a column of fractions, so that one slice leaves the ring and the rest stay in it:

geom.Bar(src, geom.X("all"), geom.Y("share"), geom.GroupBy("browser"),
    geom.ExplodeBy("pull"))

A row whose value is zero or missing does not move. The column wins over a constant Explode where it has a value, which is how a layer says "this far, except here".

func Extend added in v0.3.0

func Extend(on bool) Option

Extend controls whether an annotation widens the axis domain to include itself. It is on by default: a threshold line the chart does not reach is still worth seeing, because "we are nowhere near the limit" is the answer the reader came for. Turn it off for an annotation that should appear only when the data reaches it.

func Fill

func Fill(col ir.Color) Option

Fill sets a fill colour separately from the stroke colour, for geoms that have both.

func FontSize added in v0.3.0

func FontSize(pt float64) Option

FontSize sets the type size of a text annotation in device units. The default is the theme's label size.

func GroupBy added in v0.7.0

func GroupBy(col string) Option

GroupBy splits a layer's rows into series by the values of a column.

One layer over a long table then draws one mark set per distinct value — N lines, N stacked segments, N bars in a slot — and contributes one legend entry per group rather than one for the layer. It is the prerequisite for every position adjustment: stacking is defined over the groups of a layer.

The column may hold text, numbers or instants; whichever it is, the group key is its formatted label, exactly as data.Labels spells it, so a group key and a categorical axis tick for the same value are the same string.

Colour comes from the layer's colour scale when it was given a discrete one — scale.Qualitative — and from the theme's palette otherwise. A faceted chart wants the scale: a palette index is per panel, and a panel missing one group would shift the colours of every group after it.

func Label

func Label(s string) Option

Label names the series in the legend. It defaults to the Y column's name.

func OnMissing

func OnMissing(m Missing) Option

OnMissing sets the NaN/Inf policy.

func Opacity added in v0.2.0

func Opacity(f float64) Option

Opacity scales the fill alpha, in [0, 1]. The default is 1 for an explicit Fill colour and 0.25 for an area that takes its colour from the palette — a filled band has to sit behind the lines it belongs to.

func Order added in v0.7.0

func Order(o Ordering) Option

Order sets the order a layer's groups are stacked and listed in.

func Outliers added in v0.2.0

func Outliers(show bool) Option

Outliers turns the individual points beyond the whiskers on or off. They are on by default: a boxplot that hides them is a boxplot that hides exactly the rows a reader opened the chart to find.

func Rotate added in v0.3.0

func Rotate(radians float64) Option

Rotate turns a text annotation about its anchor, in radians clockwise.

func Shape

func Shape(m ir.Marker) Option

Shape sets the marker shape for scatter geoms. Setting it explicitly opts the layer out of a theme's redundant-encoding ladder — see github.com/timzifer/refract/theme.Redundant.

func Size

func Size(s float32) Option

Size sets the marker diameter in device units.

func Stack added in v0.7.0

func Stack(s Stacking) Option

Stack sets the position adjustment for a grouped layer. It has no effect on a layer with no GroupBy: one series stacked on itself is the series.

A stacked layer takes its baseline from the adjustment rather than from Baseline, because that is what the adjustment decides: StackZero grows from zero, StackSilhouette centres each slot, and StackWiggle puts the baseline wherever the interior boundaries are flattest.

func Steps added in v0.2.0

func Steps(where StepPos) Option

Steps sets where a Step geom changes value.

func Tension

func Tension(t float64) Option

Tension smooths a line. 0 (the default) is a plain polyline; values in (0, 1] progressively round the corners using a Catmull-Rom spline.

func Whisker added in v0.2.0

func Whisker(k float64) Option

Whisker sets how far a boxplot whisker reaches, as a multiple of the interquartile range. The default is 1.5, Tukey's original choice.

func Width

func Width(w float32) Option

Width sets the stroke width in device units.

func WidthBy added in v0.7.0

func WidthBy(col string) Option

WidthBy reads a bar's width from a column instead of from the spacing of the data, in the axis's own units.

It is what a mosaic reads as: a slot whose width is itself a value. Combine it with Stack and X positions the caller has already accumulated — a marimekko is one layer whose X column holds each column's centre and whose width column holds its share — and the vertical proportions come out of StackFill.

A row with no width, or a negative one, falls back to the slot the spacing of the data implies.

func X

func X(col string) Option

X selects the column mapped to the horizontal axis.

func X2 added in v0.7.0

func X2(col string) Option

X2 selects a second X column, giving a Rect or a Bar its far edge on that axis: a gantt bar runs from a start column to an end column, a candle from open to close.

A mark with no X2 spans its slot instead — a band scale's own bandwidth, or the closest spacing in the data narrowed by BarWidth — which is what a heatmap wants and what makes the cell the size of the category.

Under github.com/timzifer/refract/coord.Donut the X axis is the radius, so the pair is a slice's inner and outer radius and both are dimensions of the data rather than a constant the coord chose:

geom.Bar(src, geom.X("floor"), geom.X2("reach"), geom.Y("share"),
    geom.GroupBy("browser"))

The angular extent is still the stacked Y value, so such a layer reads three columns as three dimensions: how far round the slice goes, where it starts and where it stops.

Dodge still divides the span between the layer's series, whether the span came from a column or from the slot: dodging is about sharing a mark's width, and a row that named its own edges named the width to share.

func Y

func Y(col string) Option

Y selects the column mapped to the vertical axis.

func Y2 added in v0.2.0

func Y2(col string) Option

Y2 selects a second Y column, turning an area into a band between the two series rather than between one series and a baseline. It is how a confidence interval or a min/max envelope is drawn, and how a Rect is given a per-row baseline.

type Ordering added in v0.7.0

type Ordering uint8

Ordering is the order the groups of a layer are stacked and listed in.

const (
	// OrderAppearance is the order the groups first appear in the table.
	OrderAppearance Ordering = iota

	// OrderValue puts the largest group at the bottom of the stack, measured
	// by its total.
	OrderValue

	// OrderInsideOut puts the groups that peak earliest in the middle of the
	// stack and works outwards, which is the ordering a streamgraph is read
	// with. Byron & Wattenberg pair it with [StackWiggle].
	OrderInsideOut
)

The group orders. OrderAppearance is the default and is the convention the rest of refract already uses: a facet's panels, a categorical axis and a boxplot's groups all come out in order of first appearance in the source table. ADR 0012 is why it is not negotiable — a parallel render must be byte-identical to a serial one, and an order that depends on hashing is an order that depends on scheduling.

type Rows added in v0.5.0

type Rows interface {
	// Marks attributes device positions to source rows: rows[i] is the row
	// behind at[i], and a row of -1 marks a position that is not a row.
	//
	// Both slices are lent for the duration of the call, like everything else
	// a geom hands out — they come from a pool, and the next frame writes over
	// them.
	Marks(at []ir.Point, rows []int)
}

Rows is where a layer reports which source row is behind each mark it draws.

It exists for the one question a hit test cannot answer from geometry alone: *which row is this*. A tooltip is happy with the values under the pointer, which come from inverting the scales; highlighting the matching row of a table is not, because a neighbouring row highlighted confidently is a wrong answer rather than a missing one.

Why it is separate from drawing

What a layer *draws* and what a reader can *point at* are not the same points. A smoothed line is a Bézier path whose control points are not measurements; a staircase draws two points per row; a bar is four corners. Attributing rows to the points of a drawing call would therefore mean attributing them to whichever encoding the geom happened to use. So a geom reports its marks — the positions a row actually landed at — separately, and the two are correlated afterwards.

Cost

Nothing, unless someone is listening: Frame.Rows is nil for an ordinary render, and every geom checks it before doing the bookkeeping. See Frame.Marks.

type Stacking added in v0.7.0

type Stacking uint8

Stacking is how the segments of one slot are stacked, or NoStack when they are not.

const (
	// NoStack draws every group from the layer's own baseline. Groups then
	// overplot unless [Dodge] separates them, which is right for a line or a
	// scatter and rarely what a bar wants.
	NoStack Stacking = iota

	// StackZero stacks each group on the one before it, from the baseline up.
	StackZero

	// StackFill stacks the groups and scales each slot to fill the axis, so
	// the chart reads as proportions rather than as magnitudes. The axis then
	// runs from 0 to 1.
	StackFill

	// StackSilhouette centres each slot on the baseline: the ThemeRiver.
	StackSilhouette

	// StackWiggle offsets each slot to minimise how much the interior
	// boundaries climb: the streamgraph. See [stat.StackOffsets].
	StackWiggle
)

The position adjustments. NoStack is the default for a layer with no groups; a grouped Bar or Area stacks from zero unless it is told otherwise, which is what a reader expects of a bar chart with a series column and what Vega-Lite does with the same document.

type StepPos added in v0.2.0

type StepPos uint8

StepPos is where a step geom changes value between two rows.

const (
	StepPost StepPos = iota
	StepPre
	StepMid
)

The step positions. StepPost is the default: the value holds from each row until the next one, which is what a sampled signal or a state over time actually did.

type SwatchKind

type SwatchKind uint8

SwatchKind is how a legend entry draws its sample.

const (
	SwatchLine SwatchKind = iota
	SwatchMarker
	SwatchBox
)

The swatch kinds.

Jump to

Keyboard shortcuts

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