Pose substrate -- the recursive plain-map type that is the library's spec vocabulary. This namespace holds the pure tree operations (resolve, layout, shared-scale injection) and the leaf->draft emitter that feeds plan.clj.
Shape of a pose: {:data ? dataset (inherited from ancestor if absent) :mapping ? aesthetic mappings (merges with ancestors) :layers ? layers at this level (accumulate into leaves) :poses ? sub-poses; absence = leaf :layout ? {:direction :horizontal|:vertical :weights [pos-num ...]} :opts ? plot options (inheritable) :share-scales ? #{:x :y} for composites}
Pose substrate -- the recursive plain-map type that is the
library's spec vocabulary. This namespace holds the pure tree
operations (resolve, layout, shared-scale injection) and the
leaf->draft emitter that feeds plan.clj.
Shape of a pose:
{:data ? dataset (inherited from ancestor if absent)
:mapping ? aesthetic mappings (merges with ancestors)
:layers ? layers at this level (accumulate into leaves)
:poses ? sub-poses; absence = leaf
:layout ? {:direction :horizontal|:vertical
:weights [pos-num ...]}
:opts ? plot options (inheritable)
:share-scales ? #{:x :y} for composites}(check-explicit-mapping! k v)Throw when an explicit mapping is malformed. Nothing here reads the
layer's data, which is what lets api run it at the pj/pose or
lay-* call rather than at pj/draft: a mistyped key inside the
map is a mistake about the form, and the form is fully visible
where it is written. The checks that need the data -- whether the
column is there, whether the value is one the aesthetic can draw --
stay in validate-columns.
Called both from the call site and from
normalize-explicit-mapping, so a pose built by hand and threaded
straight to pj/draft is held to the same rules.
Throw when an explicit mapping is malformed. Nothing here reads the layer's data, which is what lets `api` run it at the `pj/pose` or `lay-*` call rather than at `pj/draft`: a mistyped key inside the map is a mistake about the form, and the form is fully visible where it is written. The checks that need the data -- whether the column is there, whether the value is one the aesthetic can draw -- stay in `validate-columns`. Called both from the call site and from `normalize-explicit-mapping`, so a pose built by hand and threaded straight to `pj/draft` is held to the same rules.
(combine-scales outer inner)Combine an outer scale statement with an inner one.
Scale settings accumulate down the scope chain: where both name a spec the two merge key by key, so a pose that sets a range and a layer that names a type give a plot with both. The innermost wins per key.
true and false are not specs and do not merge. false says the
value passes through no scale at all, so it replaces whatever was
set above; true says it does pass through one without saying
which, so it leaves an outer spec standing.
Combine an outer scale statement with an inner one. Scale settings accumulate down the scope chain: where both name a spec the two merge key by key, so a pose that sets a range and a layer that names a type give a plot with both. The innermost wins per key. `true` and `false` are not specs and do not merge. `false` says the value passes through no scale at all, so it replaces whatever was set above; `true` says it does pass through one without saying which, so it leaves an outer spec standing.
(composite? f)A composite pose has at least one sub-pose.
A composite pose has at least one sub-pose.
(compute-layout pose rect)(compute-layout pose [x y w h] path)Walk the pose tree and assign a pixel rectangle to each leaf. Returns a map of path -> [x y w h].
Composite :layout is {:direction :horizontal|:vertical|:matrix :weights [pos-num ...]}. Defaults: :direction :horizontal :weights (repeat n 1) (equal share)
Matrix layout (:direction :matrix) places leaves on a grid
derived from their :x / :y mappings -- distinct x-cols become
grid columns, distinct y-cols become grid rows, leaves land at
their (x, y) intersection cell. Duplicate (x, y) pairs stack
into new rows in DFS order. Empty cells get no entry. See
matrix-axes for the full algorithm and the corresponding
strip-label derivation. :weights are ignored under :matrix.
Rectangle arithmetic is in doubles; callers that need integer pixels should coerce at the render boundary (see pj/plot's width/height coercion).
Walk the pose tree and assign a pixel rectangle to each leaf.
Returns a map of path -> [x y w h].
Composite :layout is {:direction :horizontal|:vertical|:matrix
:weights [pos-num ...]}. Defaults:
:direction :horizontal
:weights (repeat n 1) (equal share)
Matrix layout (`:direction :matrix`) places leaves on a grid
derived from their :x / :y mappings -- distinct x-cols become
grid columns, distinct y-cols become grid rows, leaves land at
their (x, y) intersection cell. Duplicate (x, y) pairs stack
into new rows in DFS order. Empty cells get no entry. See
`matrix-axes` for the full algorithm and the corresponding
strip-label derivation. :weights are ignored under :matrix.
Rectangle arithmetic is in doubles; callers that need integer
pixels should coerce at the render boundary (see pj/plot's
width/height coercion).The keys an explicit mapping map may carry: one source, and optionally the scale to read it through.
The keys an explicit mapping map may carry: one source, and optionally the scale to read it through.
(explicit-mapping? v)True of a mapping value written in the explicit form -- a map naming
its source, as {:column :species} or {:value "red" :scale true},
or naming only a scale, as pj/scale writes.
A map is unambiguous here because no aesthetic takes one as a value: a color is a string or a keyword, a size is a number, a shape is a symbol from a fixed list.
True of a mapping value written in the explicit form -- a map naming
its source, as `{:column :species}` or `{:value "red" :scale true}`,
or naming only a scale, as `pj/scale` writes.
A map is unambiguous here because no aesthetic takes one as a value:
a color is a string or a keyword, a size is a number, a shape is a
symbol from a fixed list.(inject-shared-scales pose)(inject-shared-scales pose inherited-domains inherited-mapping inherited-data)Walk a pose tree. For each composite with :share-scales, compute a union domain per (axis, effective-column) bucket across descendant leaves, and stamp those domains onto matching leaves' :opts as :x-scale-domain / :y-scale-domain. Returns a new tree.
:share-scales may live in (:opts pose) (the canonical location;
set via pj/options or pj/arrange) or directly at the top of the
pose (legacy location for hand-built composites). The :opts entry
wins if both are present.
inherited-domains carries {axis {col-ref [lo hi]}} down the
tree. inherited-mapping carries the ancestor-merged mapping so a
leaf can resolve its effective axis column from (inherited + own +
layer) when deciding which bucket to claim. inherited-data is
the nearest-ancestor dataset, threaded through so a leaf can
predict whether its layers' y axis is stat-driven (count/density)
and skip the shared y-domain stamp on such leaves -- e.g., the
diagonal histogram cells of a SPLOM.
Walk a pose tree. For each composite with :share-scales, compute a
union domain per (axis, effective-column) bucket across descendant
leaves, and stamp those domains onto matching leaves' :opts as
:x-scale-domain / :y-scale-domain. Returns a new tree.
:share-scales may live in `(:opts pose)` (the canonical location;
set via pj/options or pj/arrange) or directly at the top of the
pose (legacy location for hand-built composites). The :opts entry
wins if both are present.
`inherited-domains` carries `{axis {col-ref [lo hi]}}` down the
tree. `inherited-mapping` carries the ancestor-merged mapping so a
leaf can resolve its effective axis column from (inherited + own +
layer) when deciding which bucket to claim. `inherited-data` is
the nearest-ancestor dataset, threaded through so a leaf can
predict whether its layers' y axis is stat-driven (count/density)
and skip the shared y-domain stamp on such leaves -- e.g., the
diagonal histogram cells of a SPLOM.(last-leaf-path pose)Return the path vector of the last leaf visited in left-to-right depth-first order. Nil if the pose is itself a leaf with no path context (the caller is the root leaf).
Return the path vector of the last leaf visited in left-to-right depth-first order. Nil if the pose is itself a leaf with no path context (the caller is the root leaf).
(last-matching-leaf-path pose position-mapping)Walk pose in left-to-right DFS order. Return the :path of the
last leaf whose effective :x and :y (after ancestor-merge of
:mapping) match position-mapping. Matching is strict equality:
:x and "x" are different column references. Returns nil if
no leaf matches.
position-mapping may carry either or both of :x and :y; a
nil value matches a leaf whose effective mapping has no entry
for that axis. Matching is against resolved positional mappings
only -- a bare leaf (no :x/:y) matches a bare position
mapping.
What a mapping names decides the match, not how it is written: a position carrying a scale names the same position as the plain form, and a mapping that names only a scale names no position at all.
Walk `pose` in left-to-right DFS order. Return the `:path` of the last leaf whose effective `:x` and `:y` (after ancestor-merge of `:mapping`) match `position-mapping`. Matching is strict equality: `:x` and `"x"` are different column references. Returns `nil` if no leaf matches. `position-mapping` may carry either or both of `:x` and `:y`; a `nil` value matches a leaf whose effective mapping has no entry for that axis. Matching is against resolved positional mappings only -- a bare leaf (no `:x`/`:y`) matches a bare position mapping. What a mapping names decides the match, not how it is written: a position carrying a scale names the same position as the plain form, and a mapping that names only a scale names no position at all.
(leaf->draft leaf)Emit a draft vector from a leaf pose. A draft has one entry per
applicable layer; each entry is a flat map carrying the merged
aesthetic mapping (pose < layer-type-info < layer), the layer's
:stat/:position/:mark as first-class siblings, each aesthetic's
resolved scale spec under its own key, and plot-level :coord
stamped from :opts. The scales come from the mapping, which is
where pj/scale writes them; only :coord is still an option.
If the leaf's :opts carry :facet-col or :facet-row, the draft is multiplied over distinct facet values. Each variant carries a filtered :data plus :facet-col / :facet-row labels that plan.clj detects to build the facet grid.
The leaf's :opts is passed through to plan/draft->plan; in particular the compositor uses :suppress-legend on grid cells.
An empty :layers vector yields one {:mark :infer ...} placeholder so downstream inference can still choose a layer type from the data.
Data precedence: layer :data > leaf :data.
Every emitted draft carries :__panel-idx 0 because a single leaf is a single panel; plan.clj uses the key to group layers by panel, and a leaf has no sub-panel structure.
Emit a draft vector from a leaf pose. A draft has one entry per
applicable layer; each entry is a flat map carrying the merged
aesthetic mapping (pose < layer-type-info < layer), the layer's
:stat/:position/:mark as first-class siblings, each aesthetic's
resolved scale spec under its own key, and plot-level :coord
stamped from :opts. The scales come from the mapping, which is
where `pj/scale` writes them; only :coord is still an option.
If the leaf's :opts carry :facet-col or :facet-row, the draft is
multiplied over distinct facet values. Each variant carries a
filtered :data plus :facet-col / :facet-row labels that plan.clj
detects to build the facet grid.
The leaf's :opts is passed through to plan/draft->plan; in
particular the compositor uses :suppress-legend on grid cells.
An empty :layers vector yields one {:mark :infer ...} placeholder so
downstream inference can still choose a layer type from the data.
Data precedence: layer :data > leaf :data.
Every emitted draft carries :__panel-idx 0 because a single leaf is
a single panel; plan.clj uses the key to group layers by panel, and
a leaf has no sub-panel structure.(leaf-at pose path)Fetch the leaf at path in pose. Returns nil if the path does
not land on a leaf.
Fetch the leaf at `path` in `pose`. Returns nil if the path does not land on a leaf.
(leaf? f)A leaf pose has no sub-poses. (An empty :poses vector also counts as leaf because it has nothing to tile.)
A leaf pose has no sub-poses. (An empty :poses vector also counts as leaf because it has nothing to tile.)
(mapping-source v)What a mapping value names, with the full form unwrapped: the
:column, the :value or the :from. A mapping that names only a
scale names no source, and answers nil.
Identity is decided on this rather than on the written value, so
that {:x {:from :a :scale ...}} and a plain {:x :a} are the same
position -- which they are, since pj/scale produces the first from
the second.
What a mapping value names, with the full form unwrapped: the
`:column`, the `:value` or the `:from`. A mapping that names only a
scale names no source, and answers nil.
Identity is decided on this rather than on the written value, so
that `{:x {:from :a :scale ...}}` and a plain `{:x :a}` are the same
position -- which they are, since `pj/scale` produces the first from
the second.(matrix-axes composite)For a composite whose layout is :matrix, walk its leaves in
DFS order and compute the grid axes:
Univariate leaves (missing :x or :y) use the no-x-key / no-y-key sentinels so they get their own grid lane.
Returns {:col-keys [...] :row-keys [...] :col-labels [...|nil] :row-labels [...|nil] :positions {path -> [col-idx row-idx]} :x-vars [...] :y-vars [...]}.
The compositor consumes :positions for rect math and the labels for strip rendering; :x-vars / :y-vars surface in plan introspection.
For a composite whose layout is `:matrix`, walk its leaves in
DFS order and compute the grid axes:
- col-key per leaf: the leaf's :x mapping. Two leaves sharing
(x, y) keep the same col-key.
- row-key per leaf: the leaf's :y mapping, with a DFS-occurrence
discriminator when (x, y) repeats. The first (a, b) gets row
b; the second (a, b) gets row [b 1]; the third [b 2]; etc.
Same column, new row in DFS order.
- col-keys / row-keys: distinct keys in order of first appearance.
- col-labels / row-labels: human-readable strings via
defaults/fmt-name; nil when only one column or one row exists
so we don't render a redundant strip header.
Univariate leaves (missing :x or :y) use the no-x-key / no-y-key
sentinels so they get their own grid lane.
Returns {:col-keys [...] :row-keys [...]
:col-labels [...|nil] :row-labels [...|nil]
:positions {path -> [col-idx row-idx]}
:x-vars [...] :y-vars [...]}.
The compositor consumes :positions for rect math and the labels
for strip rendering; :x-vars / :y-vars surface in plan introspection.(merge-mappings outer inner)Merge an outer mapping into an inner one, the way pose mappings have
always merged -- the inner value wins -- except for the scale, which
accumulates. See merge-mapping-value.
Merge an outer mapping into an inner one, the way pose mappings have always merged -- the inner value wins -- except for the scale, which accumulates. See `merge-mapping-value`.
(path->update-in-path path)Translate a leaf path like [0 1] into the get-in / update-in navigation [:poses 0 :poses 1]. A root path [] translates to [].
Translate a leaf path like [0 1] into the get-in / update-in navigation [:poses 0 :poses 1]. A root path [] translates to [].
(pose? x)True if x looks pose-shaped: a map carrying at least one of :layers or :poses. Permissive by design -- schema-level validation lives in impl.pose-schema.
True if x looks pose-shaped: a map carrying at least one of :layers or :poses. Permissive by design -- schema-level validation lives in impl.pose-schema.
(put-scale mapping aesthetic spec)Write a scale spec into mapping for aesthetic -- what pj/scale
does.
A scale lives with the mapping it reads, so this is an update of the
mapping rather than of the pose's options. Where the aesthetic is
mapped plainly there is no room for a scale, so the value is
rewritten in the full form under :from, which says the same thing:
ask the data. Where it is not mapped here at all, the scale is
written on its own and applies to whatever source is named below.
A mapping cancelled with nil stays cancelled: an aesthetic that
draws nothing has nothing to scale. A mapping that says :scale false on this same pose is refused rather than overridden: one pose
cannot both draw a value as it stands and read it through a scale.
Write a scale spec into `mapping` for `aesthetic` -- what `pj/scale` does. A scale lives with the mapping it reads, so this is an update of the mapping rather than of the pose's options. Where the aesthetic is mapped plainly there is no room for a scale, so the value is rewritten in the full form under `:from`, which says the same thing: ask the data. Where it is not mapped here at all, the scale is written on its own and applies to whatever source is named below. A mapping cancelled with `nil` stays cancelled: an aesthetic that draws nothing has nothing to scale. A mapping that says `:scale false` on this same pose is refused rather than overridden: one pose cannot both draw a value as it stands and read it through a scale.
(resolve-tree pose)(resolve-tree pose parent-ctx path)Walk the pose tree top-down, merging parent context into each descendant. Returns a vector of resolved leaves; each leaf carries merged :data, :mapping, :layers, :opts, and a :path vector of indices describing its position in the tree.
Context inheritance rules:
merge-mappings).Extra keys on a leaf (anything not in #{:data :mapping :layers :poses :layout :opts :share-scales}) pass through to the resolved leaf so callers can attach metadata like :path-labels from facet-style generators.
Walk the pose tree top-down, merging parent context into each
descendant. Returns a vector of resolved leaves; each leaf carries
merged :data, :mapping, :layers, :opts, and a :path vector of
indices describing its position in the tree.
Context inheritance rules:
- :data -- nearest ancestor wins (child overrides parent).
- :mapping -- merged, with child keys overriding parent keys, and
scale settings accumulating (see `merge-mappings`).
- :layers -- concatenated (ancestor layers distribute down, then
the leaf's own layers append).
- :opts -- merged (child overrides on key collision).
Extra keys on a leaf (anything not in
#{:data :mapping :layers :poses :layout :opts :share-scales})
pass through to the resolved leaf so callers can attach metadata
like :path-labels from facet-style generators.(scale-only-mapping? v)True of a mapping value that names a scale and no source -- what
pj/scale writes for an aesthetic this pose does not map. It says
how to read whatever source is named elsewhere, and where none is,
nothing is drawn and the scale is inert.
True of a mapping value that names a scale and no source -- what `pj/scale` writes for an aesthetic this pose does not map. It says how to read whatever source is named elsewhere, and where none is, nothing is drawn and the scale is inert.
The three ways an explicit mapping can name its source.
:column reads the value from the layer's data and :value is the
value itself; :from is the plain reading spelled out -- ask the
data, and take whichever answer it gives, exactly as a mapping
written plainly does. :from is what lets a plain mapping carry a
:scale, since a bare :size :weight has no room for one.
The three ways an explicit mapping can name its source. `:column` reads the value from the layer's data and `:value` is the value itself; `:from` is the plain reading spelled out -- ask the data, and take whichever answer it gives, exactly as a mapping written plainly does. `:from` is what lets a plain mapping carry a `:scale`, since a bare `:size :weight` has no room for one.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |