Composite-pose chrome layout, composite-pose->draft, and
composite-draft->plan. Pure data-side: shared-scale reconciliation,
chrome geometry computation, per-leaf opt adjustment. The
plan-to-membrane rendering for composites lives in
render/composite.clj, keeping this namespace free of membrane
dependencies.
Shared scales are reconciled before drafting by stamping a forced domain on matching leaves (impl.pose/inject-shared-scales).
When the composite root carries a legend-producing mapping (:color/:size/:alpha), the chrome reserves a strip on the right of the grid; the per-leaf opts get :suppress-legend true so each cell hides its own legend, and the rendering side (render/ composite.clj) draws ONE shared legend in the reserved strip.
Composite-pose chrome layout, composite-pose->draft, and composite-draft->plan. Pure data-side: shared-scale reconciliation, chrome geometry computation, per-leaf opt adjustment. The plan-to-membrane rendering for composites lives in `render/composite.clj`, keeping this namespace free of membrane dependencies. Shared scales are reconciled before drafting by stamping a forced domain on matching leaves (impl.pose/inject-shared-scales). When the composite root carries a legend-producing mapping (:color/:size/:alpha), the chrome reserves a strip on the right of the grid; the per-leaf opts get :suppress-legend true so each cell hides its own legend, and the rendering side (render/ composite.clj) draws ONE shared legend in the reserved strip.
Malli schemas for the draft data model -- the records returned by
pj/pose->draft (and pj/draft).
Drafts are the intermediate stage between pose and plan. They are
user-observable (the pj/draft shortcut and the predicates
pj/leaf-draft? / pj/composite-draft? are public) but are
primarily inspected, not traversed programmatically. The schemas
here document the top-level structure -- the keys on the records
themselves -- without enumerating every key a layer map may carry.
That post-scope-merge layer shape would essentially duplicate the
pose mapping schema in a different state, and the drift risk
would outweigh the documentation value.
Backend authors who consume drafts directly should rely on
destructuring :layers and :opts on a leaf draft, or
:sub-drafts / :chrome-spec / :layout on a composite, and
apply the layer-type registry to interpret each layer's
:layer-type, :mark, and :stat.
Malli schemas for the draft data model -- the records returned by `pj/pose->draft` (and `pj/draft`). Drafts are the intermediate stage between pose and plan. They are user-observable (the `pj/draft` shortcut and the predicates `pj/leaf-draft?` / `pj/composite-draft?` are public) but are primarily inspected, not traversed programmatically. The schemas here document the top-level structure -- the keys on the records themselves -- without enumerating every key a layer map may carry. That post-scope-merge layer shape would essentially duplicate the pose mapping schema in a different state, and the drift risk would outweigh the documentation value. Backend authors who consume drafts directly should rely on destructuring `:layers` and `:opts` on a leaf draft, or `:sub-drafts` / `:chrome-spec` / `:layout` on a composite, and apply the layer-type registry to interpret each layer's `:layer-type`, `:mark`, and `:stat`.
Extract data-space geometry from resolved draft layers and stat results. Produces layer descriptor maps — plain Clojure maps with mark type, style, and groups of data-space coordinates.
Extract data-space geometry from resolved draft layers and stat results. Produces layer descriptor maps — plain Clojure maps with mark type, style, and groups of data-space coordinates.
Layout inference pipeline. Three pure functions turn scene facts and configuration into concrete pixel dimensions.
Under the new semantics, :width and :height in opts mean the
TOTAL SVG dimensions. Panel dimensions are derived by subtracting
layout overhead (titles, axis labels, legends, facet strips) from
the total. :panel-width/:panel-height are escape hatches: when
set, they pin the panel size on that axis and :width/:height
become the derived total.
The classic width→tick-count→label-width→y-label-pad→panel-width
cycle is broken by a single reformulation: max-label-pixel-width
runs the tick picker at a pixel budget equal to the user-supplied
:height (or :width), not the actual panel size. Label width is
monotonic non-decreasing in tick count across every scale type we
support (verified at the REPL), so this over-estimate is always safe.
Pipeline:
compute-scene scene data from resolved draft layers + opts (no pixels) compute-padding scene + cfg + opts -> padding map (no pixel dims yet) compute-dims scene + padding + cfg + opts -> pw/ph/total-w/total-h
None of these need actual per-panel tick positions -- the tick
budget is baked into y-label-pad in compute-padding via the
over-estimate trick. Real per-panel ticks are computed AFTER
compute-dims when the final pw/ph are known.
Layout inference pipeline. Three pure functions turn scene facts and configuration into concrete pixel dimensions. Under the new semantics, `:width` and `:height` in opts mean the TOTAL SVG dimensions. Panel dimensions are derived by subtracting layout overhead (titles, axis labels, legends, facet strips) from the total. `:panel-width`/`:panel-height` are escape hatches: when set, they pin the panel size on that axis and `:width`/`:height` become the derived total. The classic width→tick-count→label-width→y-label-pad→panel-width cycle is broken by a single reformulation: `max-label-pixel-width` runs the tick picker at a pixel budget equal to the user-supplied `:height` (or `:width`), not the actual panel size. Label width is monotonic non-decreasing in tick count across every scale type we support (verified at the REPL), so this over-estimate is always safe. Pipeline: compute-scene scene data from resolved draft layers + opts (no pixels) compute-padding scene + cfg + opts -> padding map (no pixel dims yet) compute-dims scene + padding + cfg + opts -> pw/ph/total-w/total-h None of these need actual per-panel tick positions -- the tick budget is baked into y-label-pad in `compute-padding` via the over-estimate trick. Real per-panel ticks are computed AFTER `compute-dims` when the final pw/ph are known.
Malli schema for the metadata Plotje stamps onto the vector
returned by plan->membrane (and so by pj/membrane).
The vector itself is a Membrane drawable tree -- a sequence of
things satisfying membrane.ui/IOrigin. That structural contract
belongs to Membrane (see membrane.ui protocols) and is not
modeled here. This schema is strictly Plotje's own contract: the
keys we attach as metadata so that membrane->plot defmethods
(and any third-party backend) can read plan-derived dimensions
and title without re-walking the tree.
Malli schema for the metadata Plotje stamps onto the vector returned by `plan->membrane` (and so by `pj/membrane`). The vector itself is a Membrane drawable tree -- a sequence of things satisfying `membrane.ui/IOrigin`. That structural contract belongs to Membrane (see `membrane.ui` protocols) and is not modeled here. This schema is strictly Plotje's own contract: the keys we attach as metadata so that `membrane->plot` defmethods (and any third-party backend) can read plan-derived dimensions and title without re-walking the tree.
Draft-to-plan pipeline: domains, ticks, legends, layout, and grid inference. Takes draft maps (from pose/leaf->draft) and produces a Plan record with all geometry needed for rendering.
Draft-to-plan pipeline: domains, ticks, legends, layout, and grid inference. Takes draft maps (from pose/leaf->draft) and produces a Plan record with all geometry needed for rendering.
Malli schemas for the plan data model.
Malli schemas for the plan data model.
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}Malli schema for the Pose data model.
A pose is a plain recursive map. A leaf pose has no :poses (or an empty :poses vector). A composite pose has :poses and an optional :layout describing how sub-poses tile a bounding rectangle.
Validation is not wired into any runtime path yet; Phase 6 of the pre-alpha refactor adds validation at public API boundaries. Until then, impl.pose operates on structurally-valid poses by convention; this schema is the authoritative definition of that convention.
Decisions made in Phase 2:
Malli schema for the Pose data model. A pose is a plain recursive map. A leaf pose has no :poses (or an empty :poses vector). A composite pose has :poses and an optional :layout describing how sub-poses tile a bounding rectangle. Validation is not wired into any runtime path yet; Phase 6 of the pre-alpha refactor adds validation at public API boundaries. Until then, impl.pose operates on structurally-valid poses by convention; this schema is the authoritative definition of that convention. Decisions made in Phase 2: - A leaf with no :data and no :mapping is valid -- leaves inherit context from ancestors via impl.pose/resolve-tree. - :share-scales is structurally allowed on any pose; it is a no-op on leaves (nothing to share). - :layout :weights length is not required to equal (count :poses); impl.pose/compute-layout tolerates short/long weight vectors.
Position adjustment — composable transforms on layer descriptors. Runs between extract-layer and build-panels in the plan pipeline.
Position types: :identity — no adjustment (default) :dodge — side-by-side within a categorical band (annotation) :stack — cumulative y-values across groups (data transform) :fill — normalized cumulative y, sums to 1.0 (data transform)
Position adjustment — composable transforms on layer descriptors. Runs between extract-layer and build-panels in the plan pipeline. Position types: :identity — no adjustment (default) :dodge — side-by-side within a categorical band (annotation) :stack — cumulative y-values across groups (data transform) :fill — normalized cumulative y, sums to 1.0 (data transform)
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 |