Documentation
¶
Overview ¶
Package render lowers a resolved chart into IR.
This is the one place that knows the drawing order of a chart — background, grid, axes, data, guides — and the one place that turns a layout rectangle and a set of scales into actual primitives. Geoms emit their own marks; everything around them is built here.
Who it is for ¶
The root package is the supported way to draw a chart: it resolves a Plot into the Chart this package takes and handles the parts that are plumbing — Chart.Serial, Chart.Observer, Chart.RowSink. This package is public for the caller that has no Plot: a tool that assembles a chart from the model packages directly, or that wraps a backend to watch what a render emits through Observer. Chart and Panel grow by gaining fields, and a zero field always means what it meant.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Draw ¶
Draw lowers c into b. It does not call Flush: the caller owns the backend's lifecycle.
The order is fixed here and nowhere else: background, then every panel's grid and axes, then the titles, then every panel's data inside its own clip, then the guides. Data is drawn after the furniture so that a mark is never hidden by a grid line, and the guides last because they sit outside every panel and must not be clipped by one.
Types ¶
type Chart ¶
type Chart struct {
Width, Height int
DPR float64
Theme theme.Theme
Title string
XTitle string
YTitle string
// Y2Title labels the secondary vertical axis, down the right-hand side,
// and X2Title the secondary horizontal one, along the top.
Y2Title string
X2Title string
X, Y scale.Scale
// Y2 is the chart's secondary vertical axis, drawn down the right-hand
// side, and X2 its secondary horizontal one, drawn along the top. Each is
// read by the layers that asked for it with
// [github.com/timzifer/refract/geom.OnY2] or OnX2, and each is nil for a
// chart with one axis in that direction — which is every chart written
// before there were two.
//
// They are the chart's rather than a panel's for the reason the coord is:
// the panels of a facet are one plot over different rows, and a second
// axis that meant different things in different panels would be a
// different chart. A grid of subplots gives each panel its own through
// [Panel.Y2] and [Panel.X2].
Y2 scale.Scale
X2 scale.Scale
// Coord is the stage between the scales and the IR: what the interval a
// scale maps into means. Nil is [coord.Cartesian], which is the identity,
// so a chart that names no coord draws exactly what it always drew.
//
// It belongs to the chart rather than to a panel: the panels of a facet are
// the same plot over different rows, and one of them in a different
// coordinate system would be a different chart.
Coord coord.Coord
Layers []geom.Geom
// ShowLegend requests a legend. Entries come from the layers.
ShowLegend bool
// Description is what the chart says about itself in words, for a backend
// that can carry it — see [ir.Semantics]. It is announced before anything
// is drawn and never affects what is.
Description ir.Description
// Math typesets the notation in this chart's labels. It is nil for a chart
// whose labels are text, which is the default and costs nothing: the
// backend is wrapped only when there is a typesetter to wrap it with.
Math mathtext.Typesetter
// Panels, Rows and Cols describe a multi-panel chart: subplots, or the
// facets of one plot. When Panels is empty the chart is the single panel
// described by X, Y and Layers.
Panels []Panel
Rows, Cols int
// RowHeights fixes the height of a grid row in device units, leaving the
// solver to size any row whose entry is zero or absent. It is how a track
// is given the height it was asked for, and how a subplot grid is told
// that one of its rows is a strip rather than a panel.
RowHeights []float32
// ColWidths fixes the width of a grid column in device units, leaving the
// solver to size any column whose entry is zero or absent. It is
// RowHeights turned a quarter turn, and it is what a left or right track
// is given its width by.
ColWidths []float32
// Serial draws the panels one at a time. The zero value builds them
// concurrently where that is possible and worth it — see [drawData] — and
// produces the same output either way.
Serial bool
// Observer is told which panel and which layer is drawing, so that a
// caller watching the backend can attribute a mark to the layer that made
// it. It is nil for an ordinary render, and setting it forces the serial
// path: the observer is told things in order, and two panels drawing at
// once have no order to be told in.
Observer Observer
// RowSink, when non-nil, collects which source row is behind each mark. It
// is separate from Observer because it is a separate cost: a layer does
// the bookkeeping only when someone is listening, so this is what turns it
// on. See [geom.Rows].
//
// It is not called Rows because that name is already the facet grid's.
RowSink geom.Rows
}
Chart is a fully specified chart, ready to be drawn.
type LayerAxes ¶ added in v1.3.0
type LayerAxes interface {
// LayerAxes names the scales the next layer opened by [Observer.Layer] is
// drawn against. It is called immediately before it, and both are ranged
// for the panel already announced.
LayerAxes(x, y scale.Scale)
}
LayerAxes is an optional interface beside Observer: an observer that implements it is told which scales the layer about to be opened reads.
It exists because those are not always the panel's own. A layer bound to a secondary axis with github.com/timzifer/refract/geom.OnY2 or OnX2 is drawn against a different scale, and an index that inverted its marks through the panel's own would report a value from the wrong axis — a tooltip naming 4 200 on a chart whose right axis reads 12 %.
It is optional rather than a third method on Observer because Observer is implemented outside this package and never gains one ([CONCEPT §15](../CONCEPT.md#15-versioning--stability)). An observer that does not implement it sees exactly what it saw before there were two axes.
type Observer ¶ added in v0.5.0
type Observer interface {
// Panel opens a panel: its index in the chart, the rectangle it occupies,
// the scales that place values in it, and the coord that turns a pair of
// mapped positions into a point there. The scales are ranged for this
// panel and must not be modified; the coord is framed for it and is what
// turns a device position back into a pair — which is the only way a
// tooltip over a pie slice names a value rather than a pixel.
Panel(i int, area ir.Rect, x, y scale.Scale, cd coord.Coord)
// Layer opens a layer within the panel just announced: its index among
// that panel's layers, and its legend label if it has one.
Layer(i int, label string)
}
Observer is told the structure a render is drawing, as it draws it.
It is how hit-testing gets built without widening the IR. A backend sees primitives — a polyline, some markers — and nothing about which layer of which panel emitted them; an Observer is told that separately, so a caller wrapping the backend can tag what it sees. Nothing here draws, and nothing here can change what is drawn.
The calls come in paint order: one Panel, then a Layer for each of its layers, then the next Panel. Only the data pass is announced — the grid, the axes and the guides are furniture, and a pointer landing on a grid line has not landed on anything.
An Observer is implemented outside this package, so it never gains a method.
type Panel ¶ added in v0.3.0
type Panel struct {
// Row and Col place the panel in the grid.
Row, Col int
// Strip and RightStrip are the labels naming the panel, above it and
// beside it.
Strip, RightStrip string
// X and Y are this panel's scales. Panels sharing an axis share the scale
// object, which is what makes the axis shared rather than merely similar.
X, Y scale.Scale
// Y2 and X2 are this panel's secondary vertical and horizontal axes, or
// nil for a panel with one in that direction. The layers that read them
// are the ones that answer
// [github.com/timzifer/refract/geom.OnSecondaryY] and OnSecondaryX.
Y2 scale.Scale
X2 scale.Scale
// Coord overrides the chart's coordinate system for this panel, and is nil
// for the panels that use it — which is every panel of a facet, because
// the panels of a facet are one plot over different rows and one of them
// in a different coordinate system would be a different chart.
//
// A grid of subplots is the case that needs it: those panels are separate
// plots that happen to share a canvas, each with its own scales and its
// own axes, so a pie beside a bar chart is two coords beside each other.
Coord coord.Coord
// Layers are this panel's marks.
Layers []geom.Geom
// ShowX and ShowY report whether this panel writes its own tick labels. A
// panel that shares an axis with its neighbour leaves the labels to the
// edge of the grid. ShowY2 and ShowX2 are the same question for the
// secondary axes, answered at the right-hand and top edges of the grid
// rather than the left and bottom ones.
ShowX, ShowY, ShowY2, ShowX2 bool
// HideGrid suppresses this panel's grid lines while leaving its fill, its
// axes and its tick marks alone. A track — a band on the panel's own X,
// whose rows are lanes rather than a quantity — is what it is for: a grid
// line through a gantt strip is a rule drawn across a solid bar.
HideGrid bool
}
Panel is one Cartesian area of a multi-panel chart.