Skip to content

Commit 421abb4

Browse files
authored
fix: wire distribution through string-stat geoms (#56)
2 parents 3151db0 + ccd7971 commit 421abb4

6 files changed

Lines changed: 50 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- feat: `labs()` fields default to `auto`; pass `none` to suppress an axis or legend title and reclaim the space it reserved. (#12)
1313
- feat: `element-blank()` on a text surface (axis, plot, or legend title) collapses the space the text would reserve. (#12)
1414
- feat: `width`/`height` accept `auto` to fill the available space of a bounded container. (#10)
15+
- fix: `geom-qq()`/`geom-qq-line()` honour the `distribution` argument (`uniform`/`exponential`) instead of always plotting against the normal reference. (#56)
1516
- fix: the `font` set on `element-text`/`element-typst`/`element-geom` is applied to every text surface and to the text-drawing geoms, inheriting the base `text` font when unset. (#54)
1617
- fix: a `stage`/`after-scale` channel now trains its scale on the marker's source column, so the closure receives the channel's scale-resolved value as documented. (#52)
1718
- fix: grouped geoms (e.g., `geom-smooth`) no longer panic when a grouping aesthetic is mapped via `after-scale` or `stage`. (#51)

src/render.typ

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
)
1010
#import "scale/expansion.typ": DISCRETE-AUTO-DATA-PAD, normalise-expansion
1111
#import "scale/oob.typ": filter-oob
12-
#import "stat/apply.typ": apply-stat, setup-stat, stat-default-params
12+
#import "stat/apply.typ": apply-stat, resolve-stat-spec, setup-stat
1313
#import "stat/info.typ": stat-info
1414
#import "position/apply.typ": apply-position, position-name-of
1515
#import "theme/current.typ": _theme-state
@@ -400,14 +400,9 @@
400400
// its own name and params. Match the same pattern used for `position:` below.
401401
let stat-spec = layer.at("stat", default: "identity")
402402
let params = layer.at("params", default: (:))
403-
let stat-name = if type(stat-spec) == str {
404-
stat-spec
405-
} else { stat-spec.at("name", default: "identity") }
406-
let stat-params = if type(stat-spec) == str {
407-
stat-default-params(stat-name)
408-
} else {
409-
stat-spec.at("params", default: (:))
410-
}
403+
let resolved-stat = resolve-stat-spec(stat-spec, params)
404+
let stat-name = resolved-stat.name
405+
let stat-params = resolved-stat.params
411406
let stripped = _strip-mapping-refs(mapping)
412407

413408
let stat-identity = stat-name == none or stat-name == "identity"

src/stat/apply.typ

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,21 @@
153153
if ctor == none { (:) } else { ctor().params }
154154
}
155155

156+
// Resolve a layer's `stat:` field into a `(name, params)` pair. A string name
157+
// inherits the geom's own params over the stat constructor defaults, mirroring
158+
// the `position:` string form, so geom params such as `distribution` reach the
159+
// stat. A `stat-*()` dict carries its own name and params instead.
160+
#let resolve-stat-spec(stat-spec, geom-params) = {
161+
if type(stat-spec) == str {
162+
(name: stat-spec, params: stat-default-params(stat-spec) + geom-params)
163+
} else {
164+
(
165+
name: stat-spec.at("name", default: "identity"),
166+
params: stat-spec.at("params", default: (:)),
167+
)
168+
}
169+
}
170+
156171
#let setup-stat(name, data, mapping, params) = {
157172
let entry = _STATS.at(name, default: none)
158173
if entry == none { return params }

tests/unit/test-qq.typ

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Q-Q statistic and helper tests.
22

33
#import "../../src/utils/normal.typ": qnorm, theoretical-quantile
4-
#import "../../src/stat/apply.typ": apply-stat
4+
#import "../../src/stat/apply.typ": apply-stat, resolve-stat-spec
5+
#import "../../src/geom/qq.typ": geom-qq
6+
#import "../../src/geom/qq-line.typ": geom-qq-line
57

68
// --- qnorm: classic 95 % quantile -----------------------------------------
79
// Acklam's approximation matches the standard z to about 1.15e-9.
@@ -123,4 +125,31 @@
123125
< 1e-9,
124126
)
125127

128+
// --- resolve-stat-spec: string stat inherits the geom's own params --------
129+
// Regression: geom-qq stores `distribution` in its params and dispatches the
130+
// stat by string name, so the resolver must forward those params to the stat
131+
// rather than dropping them for the constructor defaults.
132+
133+
#let qq-layer = geom-qq(distribution: "uniform")
134+
#let qq-resolved = resolve-stat-spec(qq-layer.stat, qq-layer.params)
135+
#assert.eq(qq-resolved.name, "qq")
136+
#assert.eq(qq-resolved.params.distribution, "uniform")
137+
138+
#let qq-line-layer = geom-qq-line(distribution: "exponential")
139+
#let qq-line-resolved = resolve-stat-spec(
140+
qq-line-layer.stat,
141+
qq-line-layer.params,
142+
)
143+
#assert.eq(qq-line-resolved.name, "qq-line")
144+
#assert.eq(qq-line-resolved.params.distribution, "exponential")
145+
146+
// --- resolve-stat-spec: stat-*() dict carries its own name and params ------
147+
148+
#let dict-resolved = resolve-stat-spec(
149+
(kind: "stat", name: "boxplot", params: (coefficient: 1.0)),
150+
(width: 0.6),
151+
)
152+
#assert.eq(dict-resolved.name, "boxplot")
153+
#assert.eq(dict-resolved.params.coefficient, 1.0)
154+
126155
QQ tests passed.
5.58 KB
Loading
11.9 KB
Loading

0 commit comments

Comments
 (0)