Public API for plotje -- composable plotting in Clojure.
Public API for plotje -- composable plotting in Clojure.
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)
Layer-type registry — keyword → layer-type map (mark + stat + position).
Layer types are plain data maps. The registry makes them discoverable
and extensible. Use lookup to get a layer type by keyword, registered
to enumerate all layer types, and register! to add new ones.
Layer-type registry — keyword → layer-type map (mark + stat + position). Layer types are plain data maps. The registry makes them discoverable and extensible. Use `lookup` to get a layer type by keyword, `registered` to enumerate all layer types, and `register!` to add new ones.
Render membrane drawable trees to java.awt.image.BufferedImage via membrane's Java2D backend. Faster than SVG for large plots and produces raster output that Clay renders automatically.
Render membrane drawable trees to java.awt.image.BufferedImage via membrane's Java2D backend. Faster than SVG for large plots and produces raster output that Clay renders automatically.
Composite plan -> membrane rendering. The CompositePlan dispatch of the plan->membrane multimethod, plus the membrane drawables for composite chrome (title, strip labels, shared legend).
Pure data-side composite logic -- chrome geometry, layout
computation, composite-pose->draft, composite-draft->plan -- stays
in impl/compositor.clj. This namespace only handles the rendering
side and depends on membrane, keeping impl/ free of membrane
dependencies.
Composite plan -> membrane rendering. The CompositePlan dispatch of the plan->membrane multimethod, plus the membrane drawables for composite chrome (title, strip labels, shared legend). Pure data-side composite logic -- chrome geometry, layout computation, composite-pose->draft, composite-draft->plan -- stays in `impl/compositor.clj`. This namespace only handles the rendering side and depends on membrane, keeping `impl/` free of membrane dependencies.
No vars found in this namespace.
Build a membrane drawable tree from a plan. Plan → membrane is format-agnostic: the drawable tree can be converted to SVG, PNG, or any other format membrane supports.
Build a membrane drawable tree from a plan. Plan → membrane is format-agnostic: the drawable tree can be converted to SVG, PNG, or any other format membrane supports.
Convert membrane drawable trees to SVG hiccup.
Convert membrane drawable trees to SVG hiccup.
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 |