Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,72 @@
- Scale resolution now detects and converts temporal-unit mismatches and errors
on unrepresentable ranges instead of producing silently wrong scales (#569).
- `Plot.labels` field is no longer `Option<Labels>` but plain `Labels`.
- Scale internals now build on the `zygos` crate: numeric transform math,
break/tick selection, color ramps, the named palette tables,
shape/linetype lookups, and out-of-bounds policies live there and are
delegated to. Break and tick selection comes from zygos wholesale, so tick
positions and counts may differ from previous releases:
- the default `breaks` count is 5 (was 7), which also changes the default
number of bins on binned scales;
- pretty breaks use zygos's Wilkinson Extended weights, favouring steps of 2
and ticks that stay within the data (e.g. 0–100 gives 0, 20, …, 100);
`square` and `exp*` scales use them too, in data space;
- log breaks follow the 1-2-5 pattern on every decade rather than being
thinned to the requested count (3 and 7 join it over narrow spans when
more breaks are asked for; very wide spans keep only powers);
`pretty => false` no longer switches log scales to powers only;
- `asinh`/`pseudo_log*` breaks reach closer to zero on each side, and
`pseudo_log2`/`pseudo_ln` place them at powers of their own base;
- log-family minor breaks are the whole-number multiples between powers
(2, 3, …, 9 for base 10) rather than even steps in log space; a
`minor_breaks` count only switches them on or off;
- temporal breaks, automatic or from an interval, start from a calendar grid
sized to the interval (e.g. "3 months" from Feb 15 → Jan, Apr, Jul), and
`TIME` interval breaks step across minute/hour boundaries; automatic
temporal minor breaks fall on the calendar sub-interval (quarters under
years, weeks under months, …) instead of three evenly spaced per interval.
- Break labels are rendered by `zygos`'s label engine: the `RENAMING` clause
(explicit renames, `NULL` suppressions, and the `* => '...'` template) is
kept as a value-keyed `zygos::LabelSpec` on `Scale` and re-rendered against
the current breaks on every read instead of being folded into a static
string map at resolution time. Consequences:
- shared `{}` decimal precision is derived from the un-renamed breaks only;
- negative numbers render with U+2212 MINUS SIGN everywhere;
- `{:time}` and `{:num}` use zygos's hand-rolled strftime/printf subsets
(the `sprintf` dependency is gone); `%c %x %X %v` and the timezone tokens
`%z %Z` are unsupported, as before;
- new template extensions: `{:num %S}` for SI-prefixed notation
(`{:num %.2S}` → `1.2M`), the `!` flag for Unicode scientific notation
(`{:num %!.2e}` → `1.23 × 10³`), and further placeholders (`{:abbr N}`,
`{:bytes}`, `{:duration}`, `{:ordinal}`); placeholder keywords are now
ASCII-case-insensitive;
- unquoted numeric RENAMING keys are typed (`RENAMING 10 => 'ten'` matches
the string category `"10"`); quoted keys keep matching breaks by their
text form as before.
- **Breaking (library API):** `Scale::label_mapping` and `Scale::label_template`
are replaced by a single `label_spec: zygos::LabelSpec` field; the folded
string map is available on demand via the `Scale::label_mapping()` method,
and `format::apply_label_template` is gone (`format::label_elements` takes
its place).
- **Breaking (library API):** the scale module's public surface shrank with
the move to `zygos`. Removed: the `plot::scale::shape` module including
`shape_to_svg_path`; the ~85 named palette table constants in
`plot::scale::palettes` (`GGSQL10`, `VIRIDIS`, … — the `get_*_palette`
lookups remain); the hand-rolled break functions `wilkinson_extended`,
`pretty_breaks`, `log_breaks`, `exp_pretty_breaks`, `minor_breaks_log`,
`minor_breaks_sqrt`, `minor_breaks_symlog`, `trim_breaks`,
`trim_temporal_breaks` and `derive_minor_interval` (their zygos
counterparts are re-exported where ggsql still uses them);
`colour::ColorSpace` is now a re-export of zygos's, whose linear-RGB
variant is named `Srgb` rather than `LinearRgb`.
- Replaced `CLAUDE.md` files with `AGENTS.md` files.

### Fixed

- A scale whose transform has a restricted domain (e.g. `log`, `sqrt`) now
infers its data range from in-domain values only, so non-positive data on a
log scale no longer drags the lower limit to the transform's minimum bound
and produces an unreadable axis.
- `SCALE DISCRETE` now accepts integer columns, matching the documented ability
to force numeric data into a discrete scale. Integer columns mapped to
grouping aesthetics (e.g. `fill` in `DRAW bar`) can now be declared discrete
Expand Down Expand Up @@ -52,6 +114,19 @@
- Fixed schema-less `validate()` rejecting spatial layers that omit an explicit
`geometry` mapping, even though execution auto-detects the geometry column
(#587)
- Log axes carry decade ticks again (#512). Position scales now expand their
domain in transformed space (via zygos) rather than in data space, so the
pad can no longer push a log or sqrt scale's lower bound below zero and
get clipped to the smallest positive float — a 1–100 `log` axis now spans
roughly 0.79–126 instead of 5e-308–105. As a consequence the `expand`
setting is measured in transformed units on transformed scales: the
multiplier is a share of the transformed span, and the additive part is in
transformed units too (`add = 0.1` on a `log` scale is a tenth of a decade).
A zero-width range pads as if it spanned one transformed unit (previously
its own absolute value). Where a pad would land outside a transform's image
(the lower end of a `sqrt`, `square` or `exp*` scale near zero), that end is
left unpadded rather than folding back inside the data range. Identity
scales are unaffected.

## 0.5.2 - 2026-09-11

Expand Down
56 changes: 10 additions & 46 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ clap = { version = "4.4", features = ["derive"] }
anyhow = "1.0"
thiserror = "1.0"

# Color interpolation
palette = { version = "0.7", default-features = false, features = ["std", "approx"] }

# Spatial
geozero = { version = "0.14", default-features = false }

Expand Down
14 changes: 12 additions & 2 deletions doc/syntax/clause/scale.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,16 @@ Apart from the direct renaming described above it is also possible to provide a
* `{:Title}` will title-case the value (make the first letter in each work upper case) before insertion, e.g. `* => '{:Title} species'` will become "Adelie species" for the "adelie" break.
* `{:UPPER}` will make the value upper-case, e.g. `* => '{:UPPER} species'` will become "ADELIE species" for the "adelie" break.
* `{:lower}` works much like `{:UPPER}` but changes the value to lower-case instead.
* `{:time ...}` will format a date/datetime/time value according to the format defined afterwards. The formatting follows strftime format using the Rust chrono library. You can see an overview of the supported syntax at the [chrono docs](https://docs.rs/chrono/latest/chrono/format/strftime/index.html). The basic usage is `* => '{:time %B %Y}` which would format a break at 2025-07-04 as "July 2025".
* `{:num ...}` will format a numeric according to the format defined afterwards. The format follows the printf format using the Rust sprintf library. The syntax is `%[flags][width][.precision]type` with the following meaning:
* `{:time ...}` will format a date/datetime/time value according to the format defined afterwards. The formatting follows a strftime subset: `%Y %y %m %d %e %H %I %M %S %p %P %b %B %h %a %A %j %s %t %n %u %w %U %W %V %G %%`, the composites `%F %T %R %D %r`, fractional seconds (`%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`), and the `%-` / `%_` / `%0` padding modifiers (`%V`/`%G` are ISO 8601 week and week-year). Locale composites (`%c %x %X %v`) and timezone tokens (`%z %Z`) are not supported. The basic usage is `* => '{:time %B %Y}` which would format a break at 2025-07-04 as "July 2025".
* `{:num ...}` will format a numeric according to the format defined afterwards. The format follows a printf subset. The syntax is `%[flags][width][.precision]type` with the following meaning:
- `flags`: One or more modifiers:
* `-`: left-justify
* `+`: Force sign for positive numbers
* ` `: (space) Space before positive numbers
* `0`: Zero-pad
* `#`: Alternate form (`0x` prefix for hex, etc)
* `'`: Thousands separator (`,` is accepted as an alias), e.g. `{:num %',.2f}` → `1,234.50`
* `!`: Unicode scientific notation on `e`/`E`/`g`/`G`, e.g. `{:num %!.2e}` → `1.23 × 10³`
- `width`: The minimum width of characters to render. Depending on the `flags` the string will be padded to be at least this width
- `precision`: The maximum precision of the number. For `%g`/`%G` it is the total number of digits whereas for the rest it is the number of digits to the right of the decimal point
- `type`: How to present the number. One of:
Expand All @@ -83,3 +85,11 @@ Apart from the direct renaming described above it is also possible to provide a
* `g`/`G`: Shortest form of `e` and `f`
* `o`: Unsigned octal
* `x`/`X`: Unsigned hexadecimal
* `s`: Stringifies the number
* `S`: SI-prefixed notation with precision as significant digits, e.g. `{:num %.2S}` → `1.2M`
* `{:abbr N}` will truncate the value to `N` characters with an ellipsis.
* `{:bytes}` will format a number as a base-1024 size (e.g. `1.5 KiB`), with an optional significant-digit count (`{:bytes 2}`).
* `{:duration}` will format a number as a composite span (e.g. `1h 30m`), read in seconds unless a unit is given (`{:duration ms}`; one of `ns`, `us`, `ms`, `s`, `m`, `h`, `d`).
* `{:ordinal}` will format a number with an ordinal suffix (`1st`, `2nd`, `3rd`, …).

Formatter keywords are case-insensitive (`{:NUM}`, `{:upper}`, `{:TITLE}` all work); the `{:time}`/`{:num}` format payloads stay case-sensitive. Negative numbers render with a proper minus sign (−) throughout.
15 changes: 7 additions & 8 deletions doc/syntax/scale/type/binned.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,14 @@ Since breaks are not just presentational as it is with continuous scales the cho
* `linear`:
- `pretty => true`: Will use Wilkinsons Extended algorithm to attempt to find nice breaks in the given interval close to the number of breaks requested
- `pretty => false`: Will produce the requested number of evenly spaced breaks within the scale range
* `log`/`log2`/`ln`:
- `pretty => true`: Will use the 1-2-5 pattern and thin down to approximately the requested number of breaks
- `pretty => false`: Breaks will be exclusively at the power of the base (e.g. 1, 10, 100, 1000 for log10)
* `exp10`/`exp2`/`exp`: Same logic as the log breaks but in the inverse direction
* `sqrt`/`square`: Like `linear` but the range is first converted to sqrt space and the breaks are then converted back
* `asinh`/`pseudo_log`/`pseudo_log2`/`pseudo_ln`: Like `log` but includes zero and negates the breaks for the negative part
* `log`/`log2`/`ln`: Uses the 1-2-5 pattern on every power of the base (e.g. 1, 2, 5, 10, 20, 50 for log10), adding 3 and 7 on narrow ranges when many breaks are requested and keeping only the powers on very wide ranges, where powers are skipped to stay near the requested number of breaks. Multipliers above the base are dropped, so `log2` breaks are powers of 2. The `pretty` setting has no effect
* `exp10`/`exp2`/`exp`: Like `linear`, on the exponents
* `sqrt`: Like `linear` but the range is first converted to sqrt space and the breaks are then converted back
* `square`: Like `linear`
* `asinh`/`pseudo_log`/`pseudo_log2`/`pseudo_ln`: Like `log` (in base 10 for `asinh`, in their own base for the others) but includes zero and negates the breaks for the negative part
* `integer`: Like `linear` except disallowing breaks at fractional parts
* `date`/`datetime`/`time`:
- `breaks => <interval>`: If breaks are given as an interval (e.g. `week`, `30 seconds` or `5 years`) then the breaks will get that spacing aligned at the interval boundary (Jan 1 for years, etc). This ignores the `pretty` setting
- `breaks => <interval>`: If breaks are given as an interval (e.g. `week`, `30 seconds` or `5 years`) then the breaks will get that spacing aligned to a calendar grid of that size (Jan 1 for years, Mondays for weeks, and e.g. January/April/July/October for `3 months` or years divisible by 5 for `5 years`). This ignores the `pretty` setting
- `pretty => true`: An appropriate interval is chosen that approximates the requested number of breaks and then used as above
- `pretty => false`: Linear spacing in integer space as close to the requested number of breaks

Expand Down Expand Up @@ -125,7 +124,7 @@ SCALE BINNED x VIA date
## Settings
The following settings are recognised by binned scales:

* `expand` (only for `x`/`y`): Either a scalar number or 2-element array of numbers (values must be >= 0). Sets the expansion of the scale to either side of the range. If a scalar it gives the multiplicative expansion. If an array the first element is a multiplication factor and the second element is an additive constant. Defaults to `0.05` (5 %). Expansion is only applied to values that are not explicitly given by the user, i.e. if setting the range as `SCALE x FROM (0, null)` expansion will only be applied to the upper range.
* `expand` (only for `x`/`y`): Either a scalar number or 2-element array of numbers (values must be >= 0). Sets the expansion of the scale to either side of the range. If a scalar it gives the multiplicative expansion. If an array the first element is a multiplication factor and the second element is an additive constant. Defaults to `0.05` (5 %). Expansion is only applied to values that are not explicitly given by the user, i.e. if setting the range as `SCALE x FROM (0, null)` expansion will only be applied to the upper range. Expansion is applied in the transformed space of the scale, so on a transformed scale both parts are measured in transformed units: on a `log` scale a factor of `0.05` pads by 5 % of the visible decades and an additive `1` pads by one decade.
* `oob`: How should values outside of the scale input range be treated. One of `'censor'` (set to `null`), or `'squish'` (set to the nearest bin). Default is `'censor'`. When set to `'squish'` the terminal bin labels will be removed to reflect that they extend to -Inf and Inf.
* `breaks`: Either a scalar (whole number >= 1) as described in [the section on breaks](#breaks), or an array of values to place breaks at. Defaults to `5`.
* `pretty`: A boolean indicating which algorithm to use for automatic calculation of breaks as described in [the section on breaks](#breaks). Defaults to `true`.
Expand Down
Loading
Loading