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 ¶
- Variables
- type ColorGuide
- type Datum
- type Decimation
- type Desc
- type Describer
- type Faceter
- type Frame
- type Geom
- func Area(src data.Source, opts ...Option) Geom
- func Bar(src data.Source, opts ...Option) Geom
- func Boxplot(src data.Source, opts ...Option) Geom
- func FromDesc(d Desc) (Geom, error)
- func HBand(y0, y1 float64, opts ...Option) Geom
- func HLine(y float64, opts ...Option) Geom
- func Line(src data.Source, opts ...Option) Geom
- func Note(x, y float64, text string, opts ...Option) Geom
- func Region(x0, y0, x1, y1 float64, opts ...Option) Geom
- func Scatter(src data.Source, opts ...Option) Geom
- func Segment(x0, y0, x1, y1 float64, opts ...Option) Geom
- func Step(src data.Source, opts ...Option) Geom
- func VBand(x0, x1 float64, opts ...Option) Geom
- func VLine(x float64, opts ...Option) Geom
- type Guided
- type LegendEntry
- type Mark
- type Missing
- type Option
- func Align(h ir.HAlign, v ir.VAlign) Option
- func BarWidth(f float64) Option
- func Baseline(v float64) Option
- func Budget(n int) Option
- func Color(col ir.Color) Option
- func ColorBy(col string, s scale.ColorScale) Option
- func Dash(pattern ...float32) Option
- func Decimate(d Decimation) Option
- func DensityCells(px float64) Option
- func Extend(on bool) Option
- func Fill(col ir.Color) Option
- func FontSize(pt float64) Option
- func Label(s string) Option
- func OnMissing(m Missing) Option
- func Opacity(f float64) Option
- func Outliers(show bool) Option
- func Rotate(radians float64) Option
- func Shape(m ir.Marker) Option
- func Size(s float32) Option
- func Steps(where StepPos) Option
- func Tension(t float64) Option
- func Whisker(k float64) Option
- func Width(w float32) Option
- func X(col string) Option
- func Y(col string) Option
- func Y2(col string) Option
- type Rows
- type StepPos
- type SwatchKind
Constants ¶
This section is empty.
Variables ¶
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.
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.
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 and Y2 name the columns mapped to the axes. ColorCol and
// ColorScale are [ColorBy]'s two halves.
X, Y, Y2 string
ColorCol string
ColorScale scale.ColorScale
// 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 ir.Marker
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.
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
// 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.
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
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.
func Boxplot ¶ added in v0.2.0
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
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
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 Note ¶ added in v0.3.0
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 Region ¶ added in v0.3.0
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 Segment ¶ added in v0.3.0
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
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.
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.
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" 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 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
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 ¶
BarWidth sets bar width as a fraction of the spacing between adjacent bars, in (0, 1]. The default is 0.8.
func Budget ¶ added in v0.4.0
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 ColorBy ¶ added in v0.2.0
func ColorBy(col string, s scale.ColorScale) Option
ColorBy maps a numeric column through a colour scale, giving every mark its own colour. It applies to Scatter and Bar; geoms whose mark is one connected shape ignore it.
A layer coloured this way contributes a colourbar rather than a legend entry — a single swatch cannot represent a continuum. See ColorGuide.
func Dash ¶
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
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 Extend ¶ added in v0.3.0
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 FontSize ¶ added in v0.3.0
FontSize sets the type size of a text annotation in device units. The default is the theme's label size.
func Opacity ¶ added in v0.2.0
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 Outliers ¶ added in v0.2.0
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
Rotate turns a text annotation about its anchor, in radians clockwise.
func Tension ¶
Tension smooths a line. 0 (the default) is a plain polyline; values in (0, 1] progressively round the corners using a Catmull-Rom spline.
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 StepPos ¶ added in v0.2.0
type StepPos uint8
StepPos is where a step geom changes value between two rows.
type SwatchKind ¶
type SwatchKind uint8
SwatchKind is how a legend entry draws its sample.
const ( SwatchLine SwatchKind = iota SwatchMarker SwatchBox )
The swatch kinds.