Documentation
¶
Overview ¶
Package scale maps data values onto visual positions and generates the ticks that label them.
A Scale owns two things: the domain→range mapping, and the choice of tick positions and labels for that domain. Keeping both in one place is what lets a time axis label itself in calendar units while a linear axis labels itself with round numbers, without either the geom or the layout knowing which is which.
How the options are named ¶
Each constructor has its own option type — LinearOption, LogOption, SymLogOption, TimeOption, OrdinalOption, SizeOption, ColorOption — because the choices differ: a log axis has a base and a linear one does not. The bare names belong to the default scale: Domain, Nice, Zero and Format configure Linear, which is what a plot has when nobody chose, and every other family carries its constructor's name as a prefix — LogDomain, [SymLogNice], SizeRange, ColorReverse — with Time's In and Origin the exceptions that read as English. A name that does not compile against the scale it was meant for is the intended failure: the option types are what keep Nice from being silently accepted and ignored by a log axis.
Index ¶
- Constants
- Variables
- func FromNanos(v float64) time.Time
- func InstantOf(s Scale, v float64) time.Time
- func Nanos(t time.Time) float64
- func Register(k Kind, build func(Desc) (Scale, error))
- func RegisterColor(k ColorKind, build func(ColorDesc) (ColorScale, error))
- func ValueOf(s Scale, t time.Time) float64
- type Band
- type Categorical
- type Cloner
- type ColorDesc
- type ColorDescriber
- type ColorKind
- type ColorOption
- type ColorScale
- type Definite
- type Desc
- type Describer
- type DiscreteColorScale
- type Kind
- type LinearOption
- type LogOption
- type OrdinalOption
- type Scale
- type SizeDesc
- type SizeDescriber
- type SizeOption
- type SizeScale
- type Snapshotter
- type SymLogOption
- type Temporal
- type Tick
- type TimeOption
- type Zoomer
Constants ¶
const DefaultMaxSize = 32
DefaultMaxSize is the largest bubble a size scale draws when nothing has set its range. A geom overrides it from the theme before it draws; this is what a scale used on its own, in a test or by a caller driving one directly, answers with.
Variables ¶
var ErrUnknownKind = fmt.Errorf("refract/scale: unknown scale kind")
ErrUnknownKind reports a Desc naming a scale this package does not have.
Functions ¶
func InstantOf ¶ added in v0.6.0
InstantOf converts a domain value of s back into an instant, falling back to FromNanos for a scale that is not Temporal.
func Register ¶ added in v1.0.0
Register makes a scale kind this package does not define buildable by FromDesc, and therefore readable from a JSON spec.
The kind is the one the scale's Describer reports, and the one a document carries as the scale's type. A kind this package owns — every Kind constant — is refused with a panic, because shadowing a built-in would change what every existing document means. Registering a kind twice replaces the earlier builder.
A registered scale is rebuilt from the fields of Desc alone: a fixed domain, the framing flags, a category list. A scale whose configuration does not fit in a Desc round-trips what does and no more, the way a scale with a Go formatter does — see Desc.Formatted.
Nothing iterates the registry and lookups are guarded by a lock, so registering from an init function is safe and registration order never changes what a chart draws.
func RegisterColor ¶ added in v1.0.0
func RegisterColor(k ColorKind, build func(ColorDesc) (ColorScale, error))
RegisterColor is Register for colour scales: it makes a ColorKind this package does not define buildable by ColorFromDesc.
Types ¶
type Band ¶ added in v0.2.0
type Band interface {
// Bandwidth returns the width of one slot in device units, after padding.
Bandwidth() float32
}
Band is implemented by scales that give each category a slot of finite width, which is what a bar or a boxplot needs in order to size itself.
A geom that finds a Band on its axis takes the width from the scale instead of guessing one from the spacing of the data.
type Categorical ¶ added in v0.2.0
type Categorical interface {
// Encode returns the domain value for a category label, registering the
// label if the scale has not seen it before.
Encode(label string) float64
// Labels returns the categories in axis order.
Labels() []string
}
Categorical is implemented by scales that position named categories rather than numbers, so that a geom can map a string column onto an axis.
The numeric domain of such a scale is the category index: Encode turns a label into the index that Scale.Map positions, which is what lets one Scale interface serve both continuous and categorical axes.
type Cloner ¶ added in v0.3.0
type Cloner interface {
// Clone returns an untrained copy with the same configuration. A fixed
// domain counts as configuration and is kept.
Clone() Scale
}
Cloner is implemented by a scale that can hand back a fresh copy of itself: the same configuration, with nothing trained into it yet.
Faceting needs it for free scales. "Each panel gets its own Y axis" means each panel gets its own scale object, configured the way the plot's scale was but trained only on that panel's rows — and the only thing that knows how a scale was configured is the scale.
It is an optional interface. A scale that does not implement it can still be shared across panels, which is the default and by far the common case; only a free axis needs a copy, and a facet says so rather than guessing.
type ColorDesc ¶ added in v0.5.0
type ColorDesc struct {
Kind ColorKind
Ramp string
Colors palette.Ramp
Min, Max float64
Fixed bool
Center float64
Reverse bool
Undefined ir.Color
}
ColorDesc is a colour scale reduced to what configures it.
Ramp is a name from palette.RampByName rather than a list of colours: a registered ramp is a word, and a chart that named one should read back as having named it. A ramp nobody registered has no name, so Colors carries it literally instead — an unregistered ramp is still a ramp, and losing it would be worse than spelling it out.
A KindQualitative scale uses the same two fields for its palette, named through palette.QualitativeByName. Min, Max, Fixed and Center have no meaning for one: its domain is the labels it has been shown, and those are the data rather than the scale — the same line Desc draws for a discovered ordinal domain.
func DescribeColor ¶ added in v0.5.0
func DescribeColor(s ColorScale) (ColorDesc, bool)
DescribeColor reports s's configuration, or ok == false if s cannot describe itself.
type ColorDescriber ¶ added in v0.5.0
type ColorDescriber interface {
// DescribeColor returns the scale's configuration.
DescribeColor() ColorDesc
}
ColorDescriber is implemented by a colour scale that can say what it is.
type ColorOption ¶ added in v0.2.0
type ColorOption func(*colorScale)
ColorOption configures a colour scale.
func ColorCenter ¶ added in v0.2.0
func ColorCenter(v float64) ColorOption
ColorCenter pins the value that lands on the middle of a diverging ramp. The default is 0.
It has no effect on a sequential scale, where there is no middle to pin.
func ColorDomain ¶ added in v0.2.0
func ColorDomain(min, max float64) ColorOption
ColorDomain pins the domain explicitly, disabling training.
func ColorReverse ¶ added in v0.2.0
func ColorReverse() ColorOption
ColorReverse runs the ramp the other way.
func ColorUndefined ¶ added in v0.2.0
func ColorUndefined(col ir.Color) ColorOption
ColorUndefined sets the colour for a value the scale cannot place — NaN, an infinity, or a category outside a fixed set. The default is fully transparent, which draws nothing.
type ColorScale ¶ added in v0.2.0
type ColorScale interface {
// Train extends the scale's domain to include vs, ignoring NaN and
// infinities. Calling Train repeatedly accumulates.
Train(vs ...float64)
// Domain reports the current data domain.
Domain() (min, max float64)
// Color returns the colour for a value. Values outside the domain clamp to
// its ends; NaN and infinities return the undefined colour.
Color(v float64) ir.Color
}
ColorScale maps data values onto colours, the way a Scale maps them onto positions.
It is a separate interface rather than a Scale because the two answer different questions and are trained from different columns: a chart commonly has two positional scales and one colour scale over a third column.
Like Scale, it is implemented outside this module and never gains a method; DiscreteColorScale is the optional interface beside it, and RegisterColor is how a kind this package does not define is read back.
func ColorFromDesc ¶ added in v0.5.0
func ColorFromDesc(d ColorDesc) (ColorScale, error)
ColorFromDesc builds the colour scale d describes. A kind this package does not define is built by whoever registered it — see RegisterColor.
func Diverging ¶ added in v0.2.0
func Diverging(ramp palette.Ramp, opts ...ColorOption) ColorScale
Diverging returns a colour scale that puts the middle of a ramp on a centre value and stretches both halves to the further end of the domain, so that equal deviations in either direction get equally strong colours.
It is the scale for a quantity read against a reference: a residual, a change, an anomaly. Use ColorCenter to move the centre off zero.
A nil ramp uses palette.BlueOrange.
func Sequential ¶ added in v0.2.0
func Sequential(ramp palette.Ramp, opts ...ColorOption) ColorScale
Sequential returns a colour scale that runs a ramp across the domain from end to end. It is the scale for a quantity with a natural low and high — a count, a duration, a temperature.
A nil ramp uses palette.DefaultRamp.
type Definite ¶ added in v0.2.0
type Definite interface {
// Defined reports whether v has a position on this scale.
Defined(v float64) bool
}
Definite is implemented by scales whose domain excludes some finite values. A log scale cannot place zero or a negative number anywhere on an axis, and Scale.Map returns NaN for one.
Geoms consult it so that such a value is treated as missing — subject to the layer's own missing-data policy — rather than being handed to a backend as a NaN coordinate. A scale that does not implement Definite accepts every finite value.
type Desc ¶ added in v0.5.0
type Desc struct {
// Kind is which scale this is.
Kind Kind
// Min and Max are the domain. They are meaningful only when Fixed is set;
// a trained domain belongs to the data, not to the scale.
Min, Max float64
// Fixed reports a domain pinned at construction by [Domain], [LogDomain]
// or [SymLogDomain], or afterwards by [Zoomer.SetDomain].
Fixed bool
// Nice and Zero are the linear and log framing options.
Nice, Zero bool
// Base is the log or symlog base, and Threshold the symlog linear region.
Base, Threshold float64
// MinorTicks reports the unlabelled subdivisions of a log or symlog axis.
MinorTicks bool
// Origin is a time scale's epoch, in Unix nanoseconds: the instant its
// domain values are measured from. It is zero for every other kind, and
// for a time scale left on the Unix epoch. It is not a formatting choice
// like Location is — it decides what the numbers in Min and Max *mean* —
// so a document that dropped it would read back a different axis. See
// [Origin].
Origin int64
// Categories is an ordinal scale's fixed category set, empty when it
// discovers its categories from the data. Padding is the fraction of each
// slot left blank.
Categories []string
Padding float64
// Location is a time scale's zone, by IANA name.
Location string
// Formatted reports a scale carrying a formatter that a Desc cannot hold.
Formatted bool
}
Desc is a scale reduced to what configures it.
It is the same bargain github.com/timzifer/refract/geom.Desc makes: a Scale is an interface over an unexported type, which is right for mapping values and useless for writing one down, so every scale here answers Describer and FromDesc builds one back.
What does not survive ¶
A tick formatter is a Go function. Format, LogFormat, SymLogFormat and TimeFormat therefore have no place in a Desc, and a scale carrying one says so through Formatted — a chart that is written down and read back labels its ticks the standard way. Nothing else about the scale is lost.
type Describer ¶ added in v0.5.0
type Describer interface {
// Describe returns the scale's configuration.
Describe() Desc
}
Describer is implemented by a scale that can say what it is. It is optional: a third-party scale that does not implement it still draws, and is simply not serializable.
type DiscreteColorScale ¶ added in v0.7.0
type DiscreteColorScale interface {
ColorScale
// Encode returns the index of a label, registering it if the scale has not
// seen it before. Registration is in order of first sight.
Encode(label string) float64
// ColorOf returns the colour of a label, registering it the same way.
ColorOf(label string) ir.Color
// Labels returns the categories in the order they were registered, which
// is the order a legend lists them in.
Labels() []string
}
DiscreteColorScale paints named categories rather than numbers: one colour per distinct label, taken from a qualitative palette.
It rides the ColorScale interface the way Categorical rides Scale, and for the same reason. A layer binds a column to a colour scale through one option — github.com/timzifer/refract/geom.ColorBy — and a geom that paints per mark already reads its colours through ColorScale.Color; a category is simply a value that had to be encoded before it was a number. Which *guide* the layer contributes then follows from which kind of scale it was handed: a ramp gets a colourbar, a qualitative scale gets one legend entry per label.
The numeric domain is the category index, so `Color(2)` is the third label's colour and a scale trained on the encoded column has the domain [0, n-1].
func Discrete ¶ added in v0.7.0
func Discrete(s ColorScale) (DiscreteColorScale, bool)
Discrete reports whether a colour scale paints categories, which is what decides whether a layer using it contributes legend entries or a colourbar.
func Qualitative ¶ added in v0.7.0
func Qualitative(p palette.Qualitative, opts ...ColorOption) DiscreteColorScale
Qualitative returns a colour scale that gives each distinct label a colour from p, in order of first appearance in the data.
First appearance rather than sorted order is the same convention faceting and the ordinal axis already use, and 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 map iteration is an order that depends on scheduling.
A domain larger than the palette wraps rather than fails — palette.Qualitative.At does — which is the behaviour a layer index already gets. A chart with more series than the palette has colours needs a second encoding channel, not a longer palette; see github.com/timzifer/refract/theme.Redundant.
A nil palette uses palette.Default. Of the colour options only ColorUndefined and ColorReverse mean anything here: there is no domain to pin and no middle to centre, so ColorDomain and ColorCenter are accepted and ignored, exactly as an option a geom has no use for is.
type Kind ¶ added in v0.5.0
type Kind string
Kind names a scale's type. It is the word a written-down chart carries in place of the constructor that built the scale.
type LinearOption ¶
type LinearOption func(*linear)
LinearOption configures a linear scale.
func Domain ¶
func Domain(min, max float64) LinearOption
Domain pins the data domain explicitly, disabling training.
func Format ¶
func Format(fn func(v float64) string) LinearOption
Format overrides tick label formatting.
func Nice ¶
func Nice() LinearOption
Nice expands the domain outwards to the tick sequence's own bounds, so the axis starts and ends on a labelled tick rather than on the extreme data value. This is what most charts want and what CONCEPT.md §13 shows.
func Zero ¶
func Zero() LinearOption
Zero forces the domain to include zero. Bar charts need this: a bar chart whose baseline is off-screen misleads.
type LogOption ¶ added in v0.2.0
type LogOption func(*logScale)
LogOption configures a log scale.
func LogBase ¶ added in v0.2.0
LogBase sets the base. The default is 10. Bases at or below 1 are ignored, because a logarithm to such a base is not a scale.
func LogDomain ¶ added in v0.2.0
LogDomain pins the data domain explicitly, disabling training. Both bounds must be positive; a non-positive bound is ignored, since a log scale has no position for it.
func LogMinorTicks ¶ added in v0.2.0
LogMinorTicks turns the unlabelled subdivisions inside each decade on or off. They are on by default: without them a reader has no way to judge where 3 sits between 1 and 10, which is the one thing a log axis is bad at.
type OrdinalOption ¶ added in v0.2.0
type OrdinalOption func(*ordinal)
OrdinalOption configures an ordinal scale.
func Categories ¶ added in v0.2.0
func Categories(labels ...string) OrdinalOption
Categories fixes the category set and its axis order.
Without it the scale discovers categories as it encodes them, in the order the data presents them — which is right for data that is already in the order you want to read it, and wrong for anything else. A chart whose bars reorder themselves because yesterday's export sorted differently is a chart nobody trusts.
A label outside a fixed set has no position: it encodes to NaN and the geom treats the row as missing under its own policy.
func OrdinalPadding ¶ added in v0.2.0
func OrdinalPadding(f float64) OrdinalOption
OrdinalPadding sets the fraction of each slot left blank, in [0, 1). The default is 0.2, which separates adjacent bars without making them look unrelated.
type Scale ¶
type Scale interface {
// Train extends the scale's data domain to include vs. Values that are
// NaN or infinite are ignored. Calling Train repeatedly accumulates.
Train(vs ...float64)
// SetRange sets the device-space output interval. lo may be greater than
// hi, which is how a Y axis is flipped so that larger values are higher on
// screen.
SetRange(lo, hi float32)
// Domain reports the current data domain, after any nicing.
Domain() (min, max float64)
// Map converts a data value to a device position. Values outside the
// domain map outside the range; clipping is the caller's business.
Map(v float64) float32
// Invert converts a device position back to a data value. It is the
// inverse of Map over the whole real line, not just the range.
Invert(pos float32) float64
// Ticks returns tick positions and labels, aiming for about want ticks.
// The result is ordered ascending by Value.
Ticks(want int) []Tick
}
Scale maps data values onto a device-space range.
A scale is trained on data, given a device range, and then queried. The order matters: Map and Ticks are only meaningful once both the domain and the range are set.
Stability ¶
Scale is implemented outside this module, so it never gains a method. What a scale can additionally do is an optional interface beside it — Definite, Categorical, Band, Temporal, Zoomer, Snapshotter, Cloner are the seven that exist — and a caller asks with a type assertion and falls back when the answer is no. A scale this package does not define is written down and read back through Describer and Register.
func FromDesc ¶ added in v0.5.0
FromDesc builds the scale d describes. A kind this package does not define is built by whoever registered it — see Register — and one nobody did is ErrUnknownKind.
func Log ¶ added in v0.2.0
Log returns a logarithmic scale.
The domain is strictly positive. Training ignores zero and negative values the same way it ignores NaN, and Scale.Map returns NaN for them, which geoms treat as missing data under the layer's own policy — a log chart that silently clamped a negative reading to the axis minimum would be inventing a measurement. Use SymLog for data that genuinely crosses zero.
func Ordinal ¶ added in v0.2.0
func Ordinal(opts ...OrdinalOption) Scale
Ordinal returns a categorical band scale: each category gets an equal slot, and a value sits at the centre of its slot.
It satisfies the same Scale interface as every continuous scale by carrying the category *index* as its numeric domain — see Categorical. A geom mapping a string column onto an ordinal axis encodes the labels through the scale; a geom mapping a numeric or time column onto one treats each distinct formatted value as a category, so a bar chart over the values 10, 20 and 30 gets three equally spaced bars rather than a numeric axis with gaps.
It also satisfies Band, so bars and boxplots take their width from the scale rather than inferring it from the spacing of the data.
func SymLog ¶ added in v0.2.0
func SymLog(opts ...SymLogOption) Scale
SymLog returns a symmetric log scale: linear within a threshold of zero, logarithmic outside it, and defined for every finite value including negative ones.
This is the scale for data that spans orders of magnitude *and* crosses zero — a signed residual, a profit and loss, a delta that is sometimes tiny. A plain Log cannot show such data at all and a Linear one buries everything small.
The transform is sign(v)·log_base(1 + |v|/threshold), which is smooth at the origin rather than merely continuous: there is no visible kink where the linear region hands over to the logarithmic one.
type SizeDesc ¶ added in v0.9.0
type SizeDesc struct {
// Min and Max are the domain, meaningful only when Fixed is set.
Min, Max float64
Fixed bool
// Zero is the value that maps to the minimum size, and ZeroSet reports
// whether the scale chose it. The pair is [Desc]'s Fixed again and for the
// same reason: zero is both the default anchor and an anchor somebody may
// have asked for.
Zero float64
ZeroSet bool
// MinSize and MaxSize are the diameters the domain's ends map to, and
// RangeSet reports whether the caller pinned them rather than leaving the
// theme to say. A range the theme set is not written down: it is a property
// of how the chart was drawn rather than of the chart.
MinSize, MaxSize float32
RangeSet bool
}
SizeDesc is a size scale reduced to what configures it, the way ColorDesc is for a colour scale.
func DescribeSize ¶ added in v0.9.0
DescribeSize reports s's configuration, or ok == false if s cannot describe itself.
type SizeDescriber ¶ added in v0.9.0
type SizeDescriber interface {
// DescribeSize returns the scale's configuration.
DescribeSize() SizeDesc
}
SizeDescriber is implemented by a size scale that can say what it is.
type SizeOption ¶ added in v0.9.0
type SizeOption func(*sizeScale)
SizeOption configures a size scale.
func SizeDomain ¶ added in v0.9.0
func SizeDomain(min, max float64) SizeOption
SizeDomain pins the domain explicitly, disabling training.
func SizeRange ¶ added in v0.9.0
func SizeRange(min, max float32) SizeOption
SizeRange sets the diameters, in device units, that the ends of the domain map to. It is what SizeScale.SetRange sets, given at construction so that a caller can override what the theme would have chosen.
The minimum is 0 by default, and that is the choice that makes the channel honest: a floor is a constant added to every area, so a value of zero draws a mark of some size and the ratio between two marks stops being the ratio between their values. Raise it when the smallest bubbles would otherwise vanish, knowing that is the trade being made.
A range given here is *pinned*: SizeScale.SetRange no longer moves it. That is the same line Zoomer.SetDomain draws between a domain the data decides and one the caller did — a geom sets the range from its theme on every frame, and a caller who named the sizes has not asked for the theme's.
func SizeZero ¶ added in v0.9.0
func SizeZero(v float64) SizeOption
SizeZero anchors the area scale at a value other than zero: the value whose mark has the minimum size.
Zero is the default and is what makes a bubble's ink proportional to its value. Anchoring at the smallest observation instead spreads the sizes over the data's own range, which shows small differences between large values — and stops the drawing being a proportion at all, so it is asked for rather than assumed.
type SizeScale ¶ added in v0.9.0
type SizeScale interface {
// Train extends the scale's domain to include vs, ignoring NaN and
// infinities. Calling Train repeatedly accumulates.
Train(vs ...float64)
// Domain reports the current data domain.
Domain() (min, max float64)
// SetRange sets the diameters the domain's ends map to, in device units.
// It is the size channel's answer to [Scale.SetRange], and a geom calls it
// with the sizes its theme asks for before it draws.
SetRange(min, max float32)
// Size returns the mark diameter for a value, in device units. A value the
// scale cannot place — NaN, an infinity — has no size and reports 0, which
// draws nothing.
Size(v float64) float32
}
SizeScale maps data values onto mark sizes, the way a ColorScale maps them onto colours.
It is a third interface rather than a Scale for the reason ColorScale is: it answers a different question, from a different column, and it does not place anything on an axis. A chart commonly has two positional scales, one colour scale and one size scale over four columns.
Area, not radius ¶
A reader compares two circles by how much ink is in them, not by how far across they are. Mapping a value to a radius therefore exaggerates it by the square: a bubble for twice the value drawn at twice the radius has four times the ink and reads as four times the quantity. So this maps a value to an *area* and reports the diameter that has it, which is what makes doubling a value multiply the diameter by √2.
Like Scale, it is implemented outside this module and never gains a method.
func Size ¶ added in v0.9.0
func Size(opts ...SizeOption) SizeScale
Size returns a scale that maps values onto mark diameters by area.
The domain runs from zero to the largest value trained into it, and the range from a diameter of zero to whatever SizeRange or SizeScale.SetRange names. With those defaults the mapping is exactly d(v) = D·√(v/vmax): the area is proportional to the value, so twice the value is twice the ink and √2 times the diameter.
Negative values have no area and no mark. A quantity that runs both ways is not a size channel — it is a colour channel over a diverging ramp, where a deviation of -3 and one of +3 are equally strong and visibly opposite.
func SizeFromDesc ¶ added in v0.9.0
SizeFromDesc builds the size scale d describes.
type Snapshotter ¶ added in v0.4.0
type Snapshotter interface {
// Snapshot returns an independent copy that maps, inverts and ticks
// identically to the receiver.
Snapshot() Scale
}
Snapshotter is implemented by a scale that can hand back an independent copy of itself exactly as it stands — configuration, trained domain, device range and all.
It is what makes parallel panels possible. Panels that share an axis share one scale object, and drawing sets that object's device range; two panels drawing at once would be writing the same field. A snapshot per goroutine removes the sharing without changing what any panel draws, because a snapshot maps every value exactly as its original does.
It differs from Cloner, which returns an *untrained* copy for a free facet axis. The two exist for opposite reasons: Clone is for a panel that must not inherit the plot's domain, Snapshot for one that must inherit it exactly.
It is an optional interface. A scale that does not implement it is drawn one panel at a time, which is always correct and is what a single-panel chart does anyway.
type SymLogOption ¶ added in v0.2.0
type SymLogOption func(*symlogScale)
SymLogOption configures a symmetric log scale.
func SymLogBase ¶ added in v0.2.0
func SymLogBase(b float64) SymLogOption
SymLogBase sets the base. The default is 10.
func SymLogDomain ¶ added in v0.2.0
func SymLogDomain(min, max float64) SymLogOption
SymLogDomain pins the data domain explicitly, disabling training.
func SymLogFormat ¶ added in v0.2.0
func SymLogFormat(fn func(v float64) string) SymLogOption
SymLogFormat overrides tick label formatting.
func SymLogMinorTicks ¶ added in v0.2.0
func SymLogMinorTicks(show bool) SymLogOption
SymLogMinorTicks turns the unlabelled subdivisions inside each decade on or off. They are on by default.
func SymLogThreshold ¶ added in v0.2.0
func SymLogThreshold(t float64) SymLogOption
SymLogThreshold sets the half-width of the linear region around zero. The default is 1.
Choose it to match the smallest magnitude that carries meaning: below the threshold the scale is linear, above it logarithmic, and the join is smooth.
type Temporal ¶ added in v0.6.0
type Temporal interface {
// Value converts an instant into the scale's domain space.
Value(t time.Time) float64
// Instant converts a domain value back into an instant. It is the inverse
// of Value.
Instant(v float64) time.Time
}
Temporal is implemented by scales whose domain is time.
It is the seam between an exact timestamp and the float64 a Scale maps: a scale that measures from an Origin converts across it in int64 and loses nothing, and one that does not is exactly Nanos and FromNanos. Anything turning a timestamp into a domain value — a geom reading a time column, a document writing an axis down — goes through it rather than assuming the Unix epoch, which is what ValueOf and InstantOf are for.
It is an optional interface, like every other in this package: a scale that does not implement it is not a time axis.
type Tick ¶
type Tick struct {
// Value is the tick's position in data space.
Value float64
// Pos is the tick's position in device space, already mapped.
Pos float32
// Label is the formatted text for the tick. An empty label means a tick
// mark and grid line are drawn but nothing is written.
Label string
// Minor marks a tick that subdivides the axis without a label.
Minor bool
}
Tick is one labelled position on an axis.
type TimeOption ¶
type TimeOption func(*timeScale)
TimeOption configures a time scale.
func In ¶
func In(loc *time.Location) TimeOption
In sets the location used to compute calendar ticks and format labels. The default is time.UTC, so that a chart renders identically wherever it is built — a server rendering in the client's local zone is a bug, not a feature.
func Origin ¶ added in v0.6.0
func Origin(t time.Time) TimeOption
Origin sets the instant the scale measures its domain from, so that a domain value is nanoseconds since t rather than nanoseconds since 1970.
It is what makes a deep zoom exact. A float64 holds 53 bits of mantissa, and a Unix nanosecond count in this century needs 61 — so two instants a nanosecond apart become the *same* float64, and an axis zoomed to a microsecond window has nothing left to separate them with. Measured from an origin near the data, the same two instants are 1.0 apart, and stay whole numbers of nanoseconds for the hundred days either side of it that a float64 counts exactly.
The origin is part of the scale's domain space: every value handed to Scale.Map or Scale.Train on this scale, and every value it returns from Scale.Invert or Scale.Domain, is measured from it. Use Temporal — or the package helpers ValueOf and InstantOf — to convert, rather than Nanos and FromNanos, which are the origin-free pair and stay so. A geom reading a time column does this for you.
The default origin is the Unix epoch, which is exactly Nanos.
func TimeFormat ¶
TimeFormat overrides tick label formatting. The unit the tick sequence settled on is passed so a caller can vary detail with zoom level.
type Zoomer ¶ added in v0.5.0
type Zoomer interface {
// SetDomain pins the data domain to [min, max]. A scale that cannot place
// part of that interval — a log scale given a negative bound — clamps to
// what it can place rather than refusing, because the caller is a pointer
// drag rather than a programmer.
SetDomain(min, max float64)
// Autoscale releases a pinned domain and forgets what was trained into it,
// so the next render establishes the domain from the data again.
//
// It releases a domain fixed at construction too. A scale built with a
// fixed domain and then autoscaled is a scale that was asked, twice, to do
// two different things; the later call wins.
Autoscale()
}
Zoomer is implemented by a scale whose domain can be set outright, which is what pan and zoom do.
It is an optional interface, and the third of its family: Cloner hands back an untrained copy for a free facet axis, Snapshotter an exact copy for another goroutine, and Zoomer changes the domain of the scale in place. A scale that does not implement it is simply not pannable — an ordinal axis is the honest example, because half a category is not a view of anything.
Pinning ¶
SetDomain pins the domain the way Domain does at construction: training stops moving it, and a linear scale stops nicing it. Both are what an interactive view needs — a chart whose axis snapped to round numbers after every wheel notch would not follow the pointer, and one that retrained on the next frame would undo the zoom the reader just asked for. [Autoscale] releases the pin.