Skip to content

Scale expansion is computed in linear data space and then clipped, collapsing log domains #512

Description

@thomasp85

This issue came out of work on the Hephaestus writer

Summary

resolve_common_steps expands a continuous scale's input range with expand_numeric_range_selective — plain linear arithmetic on the untransformed values — and only afterwards calls clip_to_transform_domain (src/plot/scale/scale_type/mod.rs:2017-2018, same order in src/plot/scale/types.rs:233). When the padded minimum crosses the transform's valid boundary, the clip replaces it with the boundary's smallest representable value rather than a sensible padded bound. For log that is f64::MIN_POSITIVE, which turns a 3-decade axis into a 311-decade one.

Reproduction

SELECT * FROM (VALUES (1), (10), (100), (1000)) AS t(v)
VISUALISE v AS x, v AS y
DRAW point
SCALE y VIA log

Resolved y scale in the Vega-Lite output:

"scale": { "type": "log", "base": 10, "domain": [2.2250738585072014e-308, 1049.95], "zero": false }
"axis": { "values": [4.999999999999997e-308, 1.999999999999999e-256, 9.999999999999996e-205,
                     9.999999999999996e-153, 4.999999999999999e-101, 2e-49, 1000.0] }

The data occupies the top ~1% of the axis, and the label expression VL emits for those breaks is 2498 characters of denormal decimal literals.

Expansion is not broken for log in general — it works whenever the padded bound stays positive (body_mass VIA log on ggsql:penguins resolves [2520, 6480], a real 5% pad). The trigger is min − mult·span − add <= 0, i.e. data spanning decades.

Impact

Both writers, differently:

  • Vega-Lite — every point crushed against the top of the panel, plus the 2498-char axis.labelExpr above.
  • hephaestus — an essentially empty figure: chrome consumes the layout and only the y title survives.

Suggested fix

Expand in transform space: transform the range, apply the multiplicative and additive expansion there, then invert. The padded bound then stays inside the transform's domain by construction, and clip_to_transform_domain becomes a safety net instead of the thing producing the domain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions