-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathlayer.typ
More file actions
107 lines (104 loc) · 3.39 KB
/
Copy pathlayer.typ
File metadata and controls
107 lines (104 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
///! Layer record helpers and default stat and position wiring.
///!
///! Every `geom-*` constructor returns the dict shape this module builds.
///! Centralising it here keeps the record's keys discoverable in one place
///! and stops drift between geoms when fields are added or renamed.
#import "aes-keys.typ": AES-KEYS
#import "utils/errors.typ": fail, fail-enum
#import "utils/types.typ": split-stroke-shorthand
/// Collect a geom's trailing `..args` into a constant-aesthetic param dict.
///
/// \@internal
/// Geoms forward their `..args` here so any aesthetic key can be pinned as a
/// constant directly on the geom (e.g. `geom-label(nudge-x: 0.5)`), matching
/// the param-first precedence the scale channels already enjoy. A declared
/// parameter binds before `..args`, so this only ever captures keys the geom
/// does not name. A positional or unknown-named arg is a user error.
///
/// \@param scope Geom name used in error messages (e.g. `"geom-label"`).
///
/// \@param args The geom's captured `arguments` value.
///
/// \@returns Dict of aesthetic key to constant value, to merge into `params`.
#let split-aes-params(scope, args) = {
if args.pos().len() != 0 {
fail(
scope,
"positional arguments are not supported",
hint: "Pass aesthetic constants as named arguments.",
)
}
let aes-params = (:)
for (key, value) in args.named().pairs() {
if key in AES-KEYS {
aes-params.insert(key, value)
} else {
fail-enum(
scope,
"argument",
key,
AES-KEYS,
hint: "Pass geom knobs as declared parameters; only aesthetic keys may be set as ad-hoc constants.",
)
}
}
aes-params
}
/// Build a layer record consumed by `plot()`.
///
/// \@internal
/// \@param geom Renderer dispatch key (e.g., `"point"`, `"col"`, `"line"`).
///
/// \@param mapping Layer-specific aesthetic mapping, or `none` to inherit.
///
/// \@param data Layer-specific data array, or `none` to inherit.
///
/// \@param params Geom-specific parameter dict (fixed aesthetics, knobs).
///
/// \@param stat Stat name or stat-object selecting the pre-render transform.
///
/// \@param position Position-adjustment name or position-object.
///
/// \@param key Legend-glyph override; `auto` falls back to the geom's default.
///
/// \@param inherit-aes Whether to merge the plot-level mapping into this layer.
///
/// \@param clip Whether the layer's marks are clipped to the panel area.
/// `true` (default) follows the usual panel clip; `false` lets the marks
/// overflow the panel, set by \@annotate for deliberate out-of-panel marks.
///
/// \@returns Layer dictionary consumed by `plot()`.
#let make-layer(
geom,
mapping: none,
data: none,
params: (:),
stat: "identity",
position: "identity",
key: auto,
inherit-aes: true,
clip: true,
) = {
// Desugar a native `1.3pt + accent` stroke into the split `stroke`/`colour`
// params the resolvers expect; other stroke forms pass through untouched.
let params = if type(params.at("stroke", default: none)) == stroke {
let split = split-stroke-shorthand(
params.stroke,
params.at("colour", default: auto),
auto,
)
params + (stroke: split.stroke, colour: split.colour)
} else { params }
(
kind: "layer",
name: geom,
mapping: mapping,
data: data,
params: params,
key: key,
stat: stat,
position: position,
inherit-aes: inherit-aes,
clip: clip,
)
}