Bland.Scale (Elixir Technical Drawing v0.6.0)

Copy Markdown View Source

Coordinate scaling between data space and canvas space.

A Scale maps a closed interval in the data domain onto a pixel range on the output canvas. linear/2 and log/2 are the two supported scale families.

The plotting pipeline holds one scale per axis on each Bland.Figure, and series use project/2 to translate data tuples into pixel coordinates.

iex> s = Bland.Scale.linear({0.0, 10.0}, {40.0, 440.0})
iex> Bland.Scale.project(s, 5.0)
240.0

Summary

Functions

Derives a reasonable data-space domain from a list of numbers by padding padding * span on each end. Zero-span inputs get ±1 padding so the resulting scale never collapses to a point.

Derives a domain for an axis of the given type.

Whether v can be represented on this scale. Always true for linear scales; false for non-positive values on a log scale.

Inverse of project/2. Converts a canvas-space coordinate back to data-space.

Builds a linear scale mapping domain to range.

Builds a log-base scale. base defaults to 10.

Projects a data-space value into canvas-space.

Types

domain()

@type domain() :: {number(), number()}

range()

@type range() :: {number(), number()}

t()

@type t() :: %Bland.Scale{
  base: number(),
  domain: domain(),
  range: range(),
  type: :linear | :log
}

Functions

auto_domain(values, padding \\ 0.05)

@spec auto_domain([number()], float()) :: domain()

Derives a reasonable data-space domain from a list of numbers by padding padding * span on each end. Zero-span inputs get ±1 padding so the resulting scale never collapses to a point.

domain_for(arg1, values, padding)

@spec domain_for(:linear | :log | :date, [number()], float()) :: domain()

Derives a domain for an axis of the given type.

Linear and date axes defer to auto_domain/2. Log axes drop non-positive observations first — they cannot be shown and would otherwise drag the domain to zero — and take no padding, since a log axis is padded by rounding out to whole decades at tick time.

iex> Bland.Scale.domain_for(:log, [0.0, 5.0, 500.0], 0.08)
{5.0, 500.0}

in_domain?(scale, v)

@spec in_domain?(t(), number()) :: boolean()

Whether v can be represented on this scale. Always true for linear scales; false for non-positive values on a log scale.

Series drawing uses this to drop points a log axis cannot show, rather than pinning them to the axis floor and drawing a line that dives off the bottom of the plot.

iex> s = Bland.Scale.log({1.0, 100.0}, {0.0, 200.0})
iex> {Bland.Scale.in_domain?(s, 10.0), Bland.Scale.in_domain?(s, 0.0)}
{true, false}

invert(scale, px)

@spec invert(t(), number()) :: float()

Inverse of project/2. Converts a canvas-space coordinate back to data-space.

linear(arg1, arg2)

@spec linear(domain(), range()) :: t()

Builds a linear scale mapping domain to range.

A collapsed domain (d0 == d1) is widened to a unit span rather than rejected — a constant series, or an explicit xlim: {5.0, 5.0}, is something a caller can legitimately arrive at and it should plot, not raise.

log(arg1, arg2, base \\ 10)

@spec log(domain(), range(), number()) :: t()

Builds a log-base scale. base defaults to 10.

A log axis cannot represent zero or negative values, but data that happens to contain a zero is ordinary — a count series, a decaying signal that bottoms out. Rather than raising, the domain is clamped to its positive part: a non-positive endpoint is pulled up to two decades below the high end. Values outside the resulting domain project to the axis floor; use in_domain?/2 to drop them instead.

project(scale, v)

@spec project(t(), number()) :: float()

Projects a data-space value into canvas-space.