Scales & Domains

If Encoding defines which data fields are connected to which visual channels, then the Scale defines how that connection is mathematically calculated. A Scale is a function that maps values from a Data Domain to a Visual Range.

Core Concepts: Domain and Range

Every Scale operates between two distinct spaces:

  1. Domain: The state of the data. For example, a temperature range in your dataset $[0^\circ\text{C}, 100^\circ\text{C}]$ or a set of categories like ["Apple", "Banana", "Orange"].
  2. Range: The state of the visual output. For an axis, this is typically physical pixels (e.g., $[0, 800]$); for a color encoding, it is a sequence of colors in a palette.

Scale Types

Charton provides specialized scale types based on the nature of the underlying data (Quantitative, Categorical, or Temporal):

Linear Scale

The most common scale for quantitative data. It preserves the original proportional relationships between data points using a linear transformation ($y = ax + b$).

  • Best for: Prices, lengths, weights, and counts.
  • Expansion: To prevent data marks (like scatter points) from being cut off at the edges, Charton automatically applies a $5%$ padding to both ends of the Domain by default.

Logarithmic (Log) Scale

When data spans multiple orders of magnitude (e.g., $1$ to $1,000,000$), a linear scale compresses small values. A Log Scale applies a logarithmic transformation, ensuring that each order of magnitude occupies an equal visual weight.

  • Note: The Domain of a Log Scale cannot contain zero or negative numbers.

Discrete (Ordinal) Scale

Used for categorical or ranked data. Unlike continuous scales, it partitions the visual range into discrete "slots" or "bins."

  • Best for: Country names, product categories, or ratings (e.g., Poor/Fair/Good).
  • Stability: The system uses stable sorting for categories to ensure that the order of items remains consistent across multiple renders.
  • Missing values: positional domains omit them; non-positional (colour) domains keep them as a reserved grey NA level. See Missing Values & Gaps.

Discrete position scales

A discrete axis normally maps category names to integer slots. A dodged violin instead needs numbers — an integer category plus a fractional offset. Charton supports both on the same scale: alt::x("x").with_category_labels("Sex") reads the positions numerically while drawing one integer tick per category, labelled from "Sex". A value that matches a known label still maps by label, so existing categorical axes are unchanged.

The scale is trained from the numeric positions as well as the category list, so geometry that reaches past the last category — an overlapping ridgeline, a fractional dodge at the edge — expands the axis automatically instead of being clipped. Expansion still describes the breathing room: the scale keeps the axis's normal per-side padding around the category indices, and applies the same padding when the data reaches past them, so the data itself is always covered. A manual expansion widens or tightens that padding.

Missing values

A discrete position scale never invents a level for a missing value: those rows are dropped, because there is no slot to place them in. A discrete colour scale is the opposite — it reserves one grey NA slot, listed last, so an observation with no category is still drawn. See Missing Values & Gaps.

Temporal Scale

Specifically designed for dates and times. It understands the irregular spans of days, hours, and minutes, and automatically generates human-readable axis ticks (e.g., showing months instead of raw timestamps).

Ticks and Label Generation

Scales do more than just map positions; they communicate meaning through Guides.

  • Tick Generation: The system calculates "pretty" numbers for axis marks. If your data range is $[0, 93]$, the scale will intelligently choose $[0, 20, 40, 60, 80, 100]$ as ticks rather than arbitrary values.
  • Formatting: Support for scientific notation, currency symbols, percentages, and custom date-time formats.

Scale Arbitration

In a multi-layered chart, Scales act as a "Single Source of Truth." When different layers share an axis, Charton performs Arbitration:

  1. Scanning: The engine identifies the "Global Union" of all data domains across all layers.
  2. Unification: A single, unified scale is created that is large enough to encompass the data from every layer.
  3. Distribution: This unified scale is injected back into each layer, ensuring that they are all drawn within the same mathematical coordinate system.

This is the default fixed behaviour. A faceted chart may opt out with with_strategy("free") (or "free_x" / "free_y"): each panel then trains its own positional scale from that panel's layers. Statistics are always computed per panel, so a cumulative summary (for example a stacked area) is never mixed across panels.

Manual Overrides

While Charton automates most scaling logic, you retain full control through explicit overrides:

  • Force Zero: Ensure an axis starts at $0$ even if the minimum data point is much higher.
  • Fixed Intervals: Fix a percentage axis strictly between $[0, 1]$ to prevent it from auto-scaling based on a subset of data.
  • Reverse: alt::y("rank").with_reverse(true) draws the axis backwards, so low values sit high (a ranking, a depth, a latitude). It is a scale concern, not a per-chart trick — expansion and tick generation follow automatically.

Key Takeaways

  • Domain is data; Range is pixels or colors.
  • Scale Type determines the "visual density" (Linear vs. Log).
  • Automatic Arbitration ensures that composite layers are perfectly aligned.
  • The Tick System translates abstract math into readable information.