Documentation
¶
Overview ¶
Package stat aggregates data before it is drawn.
Everything here answers the same question: a column has more rows than the plot has pixels, so which rows actually decide what the reader sees? The answers differ by mark. A line wants the rows that preserve its shape; a signal envelope wants the extremes of every pixel column, because a spike one sample wide is the reason someone opened the chart; a point cloud wants no rows at all but a count per cell, drawn as an image.
None of it changes a scale's domain. A geom trains on every row and aggregates only when it draws, so an axis reports what the data holds rather than what survived the reduction.
The functions come in pairs: LTTB and AppendLTTB, MinMax and AppendMinMax. The Append forms write into a caller-owned slice, which is how a chart redrawn every frame keeps its per-frame allocations flat.
Index ¶
- func AppendLTTB[F Float](dst []int, x, y []F, threshold int) []int
- func AppendMinMax[F Float](dst []int, x []F, columns int, ys ...[]F) []int
- func AppendStackOffsets(dst []float64, mode StackMode, series [][]float64) []float64
- func LTTB[F Float](x, y []F, threshold int) []int
- func MinMax[F Float](x []F, columns int, ys ...[]F) []int
- func StackOffsets(mode StackMode, series [][]float64) []float64
- type Float
- type Grid
- func (g *Grid) Add(x, y float64) bool
- func (g *Grid) At(col, row int) uint32
- func (g *Grid) Cell(x, y float64) (col, row int, ok bool)
- func (g *Grid) Fraction(count uint32, s Scaling) float64
- func (g *Grid) Raster(dst *image.NRGBA, s Scaling, paint func(t float64) color.NRGBA) *image.NRGBA
- func (g *Grid) Reset(cols, rows int, x0, y0, x1, y1 float64)
- type Scaling
- type StackMode
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AppendLTTB ¶
AppendLTTB is LTTB appending into dst.
func AppendMinMax ¶
AppendMinMax is MinMax appending into dst.
func AppendStackOffsets ¶ added in v0.7.0
AppendStackOffsets is StackOffsets writing into dst, which it truncates and grows as needed. It is the form a geom calls, because a chart redrawn every frame should not allocate a baseline per frame.
func LTTB ¶
LTTB reduces a series to at most threshold rows with the largest-triangle-three-buckets algorithm, returning the row numbers it kept in ascending order.
LTTB splits the rows into equal-count buckets and keeps, from each, the row forming the largest triangle with the row kept before it and the mean of the bucket after it. Area is a proxy for "how much of the line's shape this row carries", which is why peaks and inflections survive a reduction that drops nine points in ten while a running average flattens them.
It is lossy, and honestly so: the result is a subset of real rows, never an invented one, so every vertex the reader sees is a measurement that was taken. The first and last rows are always kept.
Rows are assumed to be ordered along x. That is not an extra condition — a line geom already connects consecutive rows, so a series it can draw is a series this can bucket.
func MinMax ¶
MinMax reduces a series to the rows that decide what each column of pixels looks like, returning the row numbers it kept in ascending order.
Each column keeps four rows at most: the one that entered it, the smallest and the largest value in it, and the one that left. Keeping the extremes is what makes this reduction visually lossless for a signal — a spike one sample wide still reaches full height, where LTTB would weigh it against its neighbours and might drop it. Keeping the entry and the exit is what makes the segments between columns land where the data actually crosses them.
ys is every value column the mark occupies: one for a line, two for a band, so that the kept rows bound the whole shape rather than one edge of it.
Rows are assumed to be ordered along x, as in LTTB.
func StackOffsets ¶ added in v0.7.0
StackOffsets returns the baseline of each column of a stack.
series[g][p] is group g's value at position p; every group must have the same number of positions, and the positions must be in axis order, because StackWiggle reads each column against the one before it. The result has one baseline per position: the first group's segment runs from base[p] to base[p] + series[0][p], the second from there, and so on.
It is a pure function of its input. Nothing here reaches for a map or for math/rand, so a parallel render stays byte-identical to a serial one — see docs/adr/0012-parallel-panels.md.
Types ¶
type Float ¶
Float is the coordinate type these functions accept.
Both widths are here because both are real: a geom decimates in device space, where coordinates are float32 and a pixel is the unit that matters, while a caller aggregating before it ever reaches a chart has float64 data. Converting one to the other to cross this boundary would cost a copy of the whole column.
type Grid ¶
type Grid struct {
// Cols and Rows are the grid's shape.
Cols, Rows int
// X0, Y0, X1, Y1 are the region the grid covers. X0 may exceed X1, and Y0
// may exceed Y1: a device-space Y axis runs downwards, and a grid over it
// has to bin the same way round as the axis it covers.
X0, Y0, X1, Y1 float64
// Counts holds Cols*Rows cells in row-major order.
Counts []uint32
// Max is the busiest cell's count, and N the number of rows binned.
Max uint32
N int
}
Grid is a two-dimensional histogram: how many rows fall in each cell of a regular grid over a rectangle.
It is the aggregate behind the density raster. A scatter of ten million points has no honest drawing as ten million markers — they overplot, the last one drawn wins, and the picture says more about row order than about the data. Counting per cell and painting the counts says how many rows are there, which is the thing the marks were standing in for.
The zero Grid is unusable; call Grid.Reset first. Reset keeps the count buffer, so a chart redrawn every frame bins into the same memory.
func Bin ¶
Bin counts every row of xs against ys into g, returning it.
The coordinates are whatever g's rectangle is in — device space for a geom binning what it was about to draw, data space for a caller aggregating before it builds a chart.
func (*Grid) Cell ¶
Cell returns the cell a position falls in, and whether it is inside the grid. A point on the far edge belongs to the last cell rather than to a cell that does not exist.
func (*Grid) Fraction ¶
Fraction maps a count onto [0, 1] against the busiest cell. An empty cell is 0 under every scaling, which is what keeps the background of a density raster empty rather than faintly painted.
func (*Grid) Raster ¶
Raster paints the grid, one pixel per cell, and returns the image.
paint is called once per non-empty cell with that cell's Grid.Fraction; empty cells are left fully transparent so the plot's own background and grid read through. dst is reused when it is large enough, so a chart redrawn every frame paints into the same pixels; pass nil to have one allocated.
type Scaling ¶
type Scaling uint8
Scaling is how a cell's count becomes a fraction of the busiest cell.
type StackMode ¶ added in v0.7.0
type StackMode uint8
StackMode is where the bottom of a stack sits.
Stacking itself is a running sum and needs no help. What differs between a stacked bar chart, a 100 % chart and a streamgraph is only the *baseline* each column of the stack is measured from, which is what this package computes: numbers in, numbers out, no scales and no geometry.
const ( // StackZero puts the bottom of every stack on zero. It is the ordinary // stacked bar or area. StackZero StackMode = iota // StackFill normalises each column to sum to one, so the stack fills the // axis and the chart reads as proportions. The baseline is still zero; // the caller scales the values. StackFill // StackSilhouette centres each column on zero, which is the symmetric // baseline a ThemeRiver is drawn about. StackSilhouette // StackWiggle minimises the total slope of the interior boundaries, which // is what makes a streamgraph readable: the eye follows a band by its // thickness, and a band that is also climbing steeply is hard to follow. // Byron & Wattenberg, "Stacked Graphs — Geometry & Aesthetics" (2008). StackWiggle )
The stack baselines.