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.
(compute-dims scene padding cfg opts)Derive panel-width, panel-height, total-width, total-height from the user's size intent and the computed padding.
Precedence:
:panel-width in opts: pins the panel width. The
user's :width becomes the derived total (which can be
smaller or larger than what they asked for). :panel-height
is symmetric -- the two axes are independent.:width and :height are the total SVG dimensions
and apply to every layout type including SPLOMs. Panel
dimensions are derived by subtracting layout overhead
(labels, legend, facet strips, title, etc.) from the total
and dividing by the grid size.SPLOMs with many variables may end up with very small panels at
the default 600x400 total -- raise :width / :height or set
:panel-width / :panel-height explicitly when that happens.
The :panel-size cfg key is no longer consulted; it lingers as
an ignored legacy key for now.
Throws with a detailed error when the derived panel is smaller
than :min-panel-size (cfg, default 20) -- the user gave more
overhead budget than they left space for.
Derive panel-width, panel-height, total-width, total-height from
the user's size intent and the computed padding.
Precedence:
1. Explicit `:panel-width` in opts: pins the panel width. The
user's `:width` becomes the derived total (which can be
smaller or larger than what they asked for). `:panel-height`
is symmetric -- the two axes are independent.
2. Otherwise `:width` and `:height` are the total SVG dimensions
and apply to every layout type including SPLOMs. Panel
dimensions are derived by subtracting layout overhead
(labels, legend, facet strips, title, etc.) from the total
and dividing by the grid size.
SPLOMs with many variables may end up with very small panels at
the default 600x400 total -- raise `:width` / `:height` or set
`:panel-width` / `:panel-height` explicitly when that happens.
The `:panel-size` cfg key is no longer consulted; it lingers as
an ignored legacy key for now.
Throws with a detailed error when the derived panel is smaller
than `:min-panel-size` (cfg, default 20) -- the user gave more
overhead budget than they left space for.(compute-padding scene cfg opts)Pure function from scene + cfg + opts to padding map. Depends only on: cfg, opts, and data-derived scene properties. Does NOT depend on panel-width or panel-height.
The y-label-pad's tick-width input uses the user's :height as a
pixel budget rather than the (unknown) real panel height. This is
the key trick that breaks the panel-width ↔ y-label-pad cycle.
Pure function from scene + cfg + opts to padding map. Depends only on: cfg, opts, and data-derived scene properties. Does NOT depend on panel-width or panel-height. The y-label-pad's tick-width input uses the user's `:height` as a pixel budget rather than the (unknown) real panel height. This is the key trick that breaks the `panel-width ↔ y-label-pad` cycle.
(compute-scene
{:keys [layout-type grid-rows grid-cols eff-title subtitle caption eff-x-label
eff-y-label facet-row-vals facet-col-vals coord-type panel-x-domains
panel-y-domains x-scale-spec y-scale-spec x-temporal y-temporal
panel-row-labels panel-col-labels legend size-legend alpha-legend]})Pull layout-relevant facts out of the resolved draft layers, grid, labels, and legends. Result depends only on the data and the user's options, never on pixel math.
Inputs map keys:
:layout-type :single / :facet-grid / :multi-variable :grid-rows, :grid-cols grid dimensions from infer-grid :eff-title / :subtitle / :caption / :eff-x-label / :eff-y-label resolved title/label strings (or nil) :facet-row-vals / :facet-col-vals facet axis values :coord-type :cartesian / :flip / :polar / :fixed :panel-x-domains vector of x-domains, one per panel :panel-y-domains vector of y-domains, one per panel :x-scale-spec / :y-scale-spec representative scale specs :x-temporal / :y-temporal temporal extents, if any :panel-row-labels / :panel-col-labels strip labels per panel :legend / :size-legend / :alpha-legend already-built legends
Output map carries everything compute-padding and compute-dims need.
Pull layout-relevant facts out of the resolved draft layers, grid, labels,
and legends. Result depends only on the data and the user's options,
never on pixel math.
Inputs map keys:
:layout-type :single / :facet-grid / :multi-variable
:grid-rows, :grid-cols grid dimensions from infer-grid
:eff-title / :subtitle / :caption / :eff-x-label / :eff-y-label
resolved title/label strings (or nil)
:facet-row-vals / :facet-col-vals facet axis values
:coord-type :cartesian / :flip / :polar / :fixed
:panel-x-domains vector of x-domains, one per panel
:panel-y-domains vector of y-domains, one per panel
:x-scale-spec / :y-scale-spec representative scale specs
:x-temporal / :y-temporal temporal extents, if any
:panel-row-labels / :panel-col-labels strip labels per panel
:legend / :size-legend / :alpha-legend already-built legends
Output map carries everything compute-padding and compute-dims need.(max-label-pixel-width domain
scale-spec
temporal-extent
pixel-budget
tick-spacing
font-size)Conservative upper bound on the widest tick label, in pixels, for a single domain on one axis.
Runs the tick picker at pixel-budget (typically the user-supplied
:height for the y-axis or :width for the x-axis) and returns the
widest resulting label in pixels. Because label character width is
monotonic non-decreasing in tick count, the pixel budget is always
= the eventual panel size and the answer is always >= the real max label width.
For categorical domains the label set is the domain itself; for numeric/log/temporal domains the tick picker produces labels that depend on the tick count, which in turn depends on the budget.
Conservative upper bound on the widest tick label, in pixels, for a single domain on one axis. Runs the tick picker at `pixel-budget` (typically the user-supplied `:height` for the y-axis or `:width` for the x-axis) and returns the widest resulting label in pixels. Because label character width is monotonic non-decreasing in tick count, the pixel budget is always >= the eventual panel size and the answer is always >= the real max label width. For categorical domains the label set is the domain itself; for numeric/log/temporal domains the tick picker produces labels that depend on the tick count, which in turn depends on the budget.
(max-label-pixel-width-over-panels panel-domains
scale-spec
temporal-extent
pixel-budget
tick-spacing
font-size)Maximum of max-label-pixel-width across a collection of panel
domains. Faceted layouts with free scales have different per-panel
domains but one shared label pad; this reserves enough space for
the widest panel.
Maximum of `max-label-pixel-width` across a collection of panel domains. Faceted layouts with free scales have different per-panel domains but one shared label pad; this reserves enough space for the widest panel.
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 |