24  API Reference

Complete reference for every public function in scicloj.plotje.api.

Each entry shows the docstring, a live example, and a test. For galleries of mark variations, see the Visualization Goals chapters (Distributions, Ranking, Change Over Time, Timelines, Relationships).

Sample Data

(def tiny {:x [1 2 3 4 5]
           :y [2 4 1 5 3]
           :group [:a :a :b :b :b]})
(def sales {:product [:widget :gadget :gizmo :doohickey]
            :revenue [120 340 210 95]})
(def measurements {:treatment ["A" "B" "C" "D"]
                   :mean [10.0 15.0 12.0 18.0]
                   :ci-lo [8.0 12.0 9.5 15.5]
                   :ci-hi [12.0 18.0 14.5 20.5]})

Construction

pj/pose is not a literal composition of the atomic steps: where the other four shortcuts (pj/draft, pj/plan, pj/membrane, pj/plot) compose them directly, pj/pose adds mapping inference, positional column-arg parsing, multi-pair composite construction, and pose extend-or-promote on top of pj/->pose. The examples below walk each shape.

pose

[]

[x]

[x y]

[x y z]

[x y z opts]

Construct or extend a pose.

On raw data (first argument is not itself a pose):

  • (pj/pose) – empty leaf.

  • (pj/pose data) – leaf with data; on 1-3 column datasets the mapping is auto-inferred (:x, then :y, then :color) so the pose renders without an explicit mapping call.

  • (pj/pose data {:color :species}) – leaf with aesthetic mapping.

  • (pj/pose data :x-col) – leaf with {:x :x-col}.

  • (pj/pose data :x-col {:color :c}) – univariate x with opts.

  • (pj/pose data :x-col :y-col) – leaf with :x and :y.

  • (pj/pose data :x-col :y-col {:color :c}) – positional x/y with opts.

  • (pj/pose data [[:a :b] [:c :d]]) – multi-pair: N bivariate panels.

  • (pj/pose data [:a :b :c]) – multi-pair: N univariate panels.

  • (pj/pose data (pj/cross cols cols) {:color :c}) – multi-pair plus aesthetic mapping at the composite root.

Threaded over an existing pose (first argument is a pose):

  • (pj/pose fr) – pass-through; lifts a literal map for notebook auto-render if it is not already tagged.

  • (pj/pose fr :x-col :y-col) – extend a leaf-without-position, or promote a leaf-with-position into a 2-panel composite, or append a panel to a composite.

  • (pj/pose fr :x-col :y-col {:color :c}) – same, with aesthetic routed to the composite root on promote.

  • (pj/pose fr {:color :c}) – aesthetic-only: extend mapping or (on leaf-with-position) promote.

  • (pj/pose fr [[:a :b] [:c :d]]) – multi-pair: append N panels.

  • (pj/pose fr (pj/cross cols cols)) – SPLOM N^2 panels in one call.

  • (pj/pose fr (pj/cross cols cols) {:color :c}) – SPLOM plus aesthetic mapping at the composite root.

  • (pj/pose fr {:data X :color :c}) – extend mapping AND replace the top-level data with X.

On a hand-built pose-shaped map (1-arity, input has :layers or :poses): the map is validated and tagged with Kindly auto-render metadata, but its keys are not reordered and its :data is not coerced – the typed shape is preserved verbatim. A flat composite (:poses of leaf maps) is supported; literal nested composites (any sub-pose itself has :poses) are rejected, matching pj/arrange’s rule that its elements must be leaves.

Create a leaf pose with data and columns:

(-> (rdatasets/datasets-iris)
    (pj/pose :sepal-length :sepal-width)
    pj/lay-point)
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Map form – include aesthetics on the pose so every layer sees them:

(-> (rdatasets/datasets-iris)
    (pj/pose :sepal-length :sepal-width {:color :species})
    pj/lay-point
    (pj/lay-smooth {:stat :linear-model}))
sepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

with-data

[pose data]

Supply or replace the top-level dataset on a pose. Useful for building a template once and applying it to different datasets:

   (def template (-> (pj/pose)
                     (pj/pose :x :y {:color :group})
                     pj/lay-point
                     (pj/lay-smooth {:stat :linear-model})))

   (-> template (pj/with-data my-data))
   (-> template (pj/with-data other-data))

At attach time, every keyword column reference in the template’s mapping, layers, sub-poses, and facet options must exist in the dataset – otherwise an error is thrown naming the missing columns and listing what is available. Per-layer / per-sub-pose :data still overrides the top-level data.

Attach or replace the top-level dataset on a pose. Useful for building a dataless template and applying it to many datasets:

(def scatter-template
  (-> (pj/pose nil {:x :x :y :y :color :group})
      pj/lay-point))
(-> scatter-template
    (pj/with-data tiny))
yxgroupab1.01.52.02.53.03.54.04.55.01.01.52.02.53.03.54.04.55.0

Multi-pair pose – a vector of [x y] pairs creates a composite with one sub-pose per pair:

(-> (rdatasets/datasets-iris)
    (pj/pose [[:sepal-length :sepal-width]
              [:petal-length :petal-width]])
    (pj/lay-point {:color :species}))
682345012sepal lengthpetal lengthsepal widthpetal widthspeciessetosaversicolorvirginica

Map form – explicit keys on a pose:

(-> (rdatasets/datasets-iris)
    (pj/pose {:x :sepal-length :y :sepal-width})
    pj/lay-point)
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

cross

[xs ys]

Build a vector of [x y] pairs from two column-name sequences. Pair with pj/pose for SPLOM grids: when an MxN rectangle of pairs is threaded through pj/pose, the result is an MxN composite with shared scales.

  • (pj/cross [:a :b] [:c :d]) returns [[:a :c] [:a :d] [:b :c] [:b :d]].
(pj/cross [:a :b] [1 2 3])
([:a 1] [:a 2] [:a 3] [:b 1] [:b 2] [:b 3])

Combine pj/cross with pj/pose to build a SPLOM:

(-> (rdatasets/datasets-iris)
    (pj/pose (pj/cross [:sepal-length :petal-length]
                       [:sepal-width :petal-width])
             {:color :species}))
234246234246sepal-widthpetal-widthsepal-lengthpetal-lengthspeciessetosaversicolorvirginica

Multi-column vector creates one panel per column:

(pj/lay-histogram (rdatasets/datasets-iris) [:sepal-length :sepal-width])
567805101520252.02.53.03.54.04.505101520253035404550sepal lengthsepal width

Layer Functions

lay

[pose-or-data layer-type-key]

[pose-or-data layer-type-key opts]

Add a root-scope layer. The layer attaches to :layers and flows to every descendant leaf at plan time (composite) or renders on the single panel (leaf).

The generic layer adder. pj/lay-point, pj/lay-bar, etc. are convenience wrappers around pj/lay with a registered layer-type key. Use pj/lay directly when you have a custom layer type (from pj/layer-type-lookup on a registered key, or a raw layer-type map from an extension):

(-> (rdatasets/datasets-iris)
    (pj/pose :sepal-length :sepal-width)
    (pj/lay :point))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

The above delegates to :point – equivalent to pj/lay-point. The intended use of pj/lay is with a layer type that isn’t a built-in convenience: a registered custom layer type from an extension, or a raw layer-type map. See the Waterfall Extension chapter for the full pattern – registering a :waterfall layer type and calling (pj/lay pose (layer-type/lookup :waterfall)) or wrapping it in a pj/lay-waterfall convenience function.

lay-point

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add a :point (scatter) layer to a pose. Without columns -> bare layer at the pose’s root (flows to every leaf). With columns -> position-bearing layer (attaches to the matching leaf via DFS-last identity, or appends a new sub-pose on miss).

  • (lay-point fr) – bare layer at root.
  • (lay-point fr {:color :species}) – bare layer with aesthetic opts.
  • (lay-point data :x :y) – coerce data to a leaf, then attach.
  • (lay-point data :x :y {:color :c}) – same with aesthetic opts.

Accepted options: :alpha :color :color-type :data :group :jitter :mark :nudge-x :nudge-y :position :shape :size :stat :text :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width {:color :species}))
sepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

lay-line

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :line layer type – connected line through data points. Requires x (numerical) and y (numerical). Accepts :color, :alpha, :size (stroke width), :nudge-x, :nudge-y.

Accepted options: :alpha :color :color-type :data :group :mark :nudge-x :nudge-y :position :size :stat :x :x-type :y :y-type.

(def wave {:x (range 30)
           :y (map #(Math/sin (* % 0.3)) (range 30))})
(-> wave
    (pj/lay-line :x :y))
yx051015202530-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

lay-histogram

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :histogram layer type – bin numerical values into bars. X-only: pass one column. Accepts :bins (count), :binwidth, :color, :normalize (:density for density-normalized heights).

Accepted options: :alpha :bins :binwidth :color :color-type :data :group :mark :normalize :position :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-histogram :sepal-length))
sepal length4.55.05.56.06.57.07.58.00510152025

lay-bar

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :bar layer type – count occurrences of each category. X-only: pass one categorical column. Accepts :color for grouped bars.

Accepted options: :alpha :color :color-type :data :group :mark :position :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-bar :species))
speciessetosaversicolorvirginica05101520253035404550

Stacked bars: pass {:position :stack} to pj/lay-bar.

(-> (rdatasets/palmerpenguins-penguins)
    (pj/lay-bar :island {:position :stack :color :species}))
islandspeciesAdelieGentooChinstrapTorgersenBiscoeDream020406080100120140160

100% stacked bars: pass {:position :fill} to pj/lay-bar.

(-> (rdatasets/palmerpenguins-penguins)
    (pj/lay-bar :island {:position :fill :color :species}))
islandspeciesAdelieGentooChinstrapTorgersenBiscoeDream0.00.10.20.30.40.50.60.70.80.91.0

lay-value-bar

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :value-bar layer type – bars with pre-computed heights. Requires categorical x and numerical y. Unlike :bar (which counts), :value-bar uses the y value directly as the bar height.

Accepted options: :alpha :color :color-type :data :group :mark :position :stat :x :x-type :y :y-type.

(-> sales
    (pj/lay-value-bar :product :revenue))
revenueproductwidgetgadgetgizmodoohickey050100150200250300350

Linear regression: pass {:stat :linear-model} to pj/lay-smooth.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-smooth {:stat :linear-model}))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

lay-smooth

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :smooth layer type – a smoothed trend line. Defaults to LOESS (local regression). Pass {:stat :linear-model} for ordinary least squares instead. Requires x and y (both numerical). Accepts {:confidence-band true} for a confidence ribbon.

Accepted options: :alpha :bandwidth :bootstrap-resamples :color :color-type :confidence-band :data :group :level :mark :nudge-x :nudge-y :position :size :stat :x :x-type :y :y-type.

(-> (let [r (rng/rng :jdk 42)
          xs (vec (range 50))]
      {:x xs
       :y (mapv #(+ (Math/sin (* % 0.2))
                    (* 0.3 (- (rng/drandom r) 0.5)))
                xs)})
    (pj/lay-point :x :y)
    (pj/lay-smooth {:bandwidth 0.2}))
yx05101520253035404550-1.2-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

lay-density

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :density layer type – kernel density estimate curve. X-only: pass one numerical column. Accepts :color, :bandwidth.

Accepted options: :alpha :bandwidth :color :color-type :data :group :mark :position :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-density :sepal-length))
sepal length3456789100.00.050.10.150.20.250.30.350.4

lay-area

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :area layer type – filled region between y and the baseline. Requires x and y (both numerical). Accepts :color, :alpha.

Accepted options: :alpha :color :color-type :data :group :mark :position :stat :x :x-type :y :y-type.

(-> wave
    (pj/lay-area :x :y))
yx051015202530-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

Stacked areas: pass {:position :stack} to pj/lay-area.

(-> {:x (concat (range 10) (range 10) (range 10))
     :y (concat [1 2 3 4 5 4 3 2 1 0]
                [2 2 2 3 3 3 2 2 2 2]
                [1 1 1 1 2 2 2 1 1 1])
     :group (concat (repeat 10 "A") (repeat 10 "B") (repeat 10 "C"))}
    (pj/lay-area :x :y {:position :stack :color :group}))
yxgroupABC02468012345678910

lay-text

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :text layer type – text labels at data coordinates. Requires x, y, and {:text :column} for label content.

Accepted options: :alpha :color :color-type :data :font-size :group :mark :nudge-x :nudge-y :position :stat :text :x :x-type :y :y-type.

(-> {:x [1 2 3 4] :y [4 7 5 8] :name ["A" "B" "C" "D"]}
    (pj/lay-text :x :y {:text :name}))
yxABCD1.01.52.02.53.03.54.04.04.55.05.56.06.57.07.58.0

lay-label

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :label layer type – text labels with background box at data coordinates. Like :text but with a rectangular background for readability.

Accepted options: :alpha :color :color-type :data :font-size :group :mark :nudge-x :nudge-y :position :stat :text :x :x-type :y :y-type.

(-> {:x [1 2 3 4] :y [4 7 5 8] :name ["A" "B" "C" "D"]}
    (pj/lay-point :x :y {:size 5})
    (pj/lay-label {:text :name}))
yxABCD1.01.52.02.53.03.54.04.04.55.05.56.06.57.07.58.0

lay-boxplot

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :boxplot layer type – box-and-whisker plot. Requires categorical x and numerical y. Shows median, quartiles, whiskers, and outliers. Accepts :color for grouped boxplots.

Accepted options: :alpha :box-width :color :color-type :data :group :mark :position :size :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-boxplot :species :sepal-width))
sepal widthspeciessetosaversicolorvirginica2.02.53.03.54.04.5

lay-violin

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :violin layer type – mirrored density estimate by category. Requires categorical x and numerical y. Accepts :color, :bandwidth.

Accepted options: :alpha :bandwidth :color :color-type :data :group :mark :position :size :stat :x :x-type :y :y-type.

(-> (rdatasets/reshape2-tips)
    (pj/lay-violin :day :total-bill))
total billdaySunSatThurFri-20-10010203040506070

lay-errorbar

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :errorbar layer type – vertical error bars from pre-computed bounds. Requires x, y, and {:y-min :col :y-max :col} for lower/upper bounds.

Accepted options: :alpha :cap-width :color :color-type :data :group :mark :nudge-x :nudge-y :position :size :stat :x :x-type :y :y-max :y-min :y-type.

(-> measurements
    (pj/lay-point :treatment :mean)
    (pj/lay-errorbar {:y-min :ci-lo :y-max :ci-hi}))
meantreatmentABCD8101214161820

lay-lollipop

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :lollipop layer type – dot on a stem from the baseline. Requires categorical x and numerical y. Like value-bar but with a circle+line instead of a filled rectangle.

Accepted options: :alpha :color :color-type :data :group :mark :position :size :stat :x :x-type :y :y-type.

(-> sales
    (pj/lay-lollipop :product :revenue))
revenueproductwidgetgadgetgizmodoohickey050100150200250300350

lay-tile

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :tile layer type – colored grid cells (heatmap). With :fill option: pre-computed tile colors from a column. Without :fill: auto-binned 2D histogram (stat :bin2d).

Accepted options: :alpha :color :color-type :data :density-2d-grid :fill :group :mark :position :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-tile :sepal-length :sepal-width))
sepal widthsepal lengthcount0.0009.0004.55.05.56.06.57.07.58.02.02.53.03.54.04.5

lay-density-2d

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :density-2d layer type – 2D kernel density heatmap. Requires x and y (both numerical). Produces a smoothed density surface as colored tiles with a continuous gradient legend.

Accepted options: :alpha :color :color-type :data :density-2d-grid :group :mark :position :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-density-2d :sepal-length :sepal-width))
sepal widthsepal lengthrelative density0.00027.1034567891.52.02.53.03.54.04.55.0

lay-contour

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :contour layer type – iso-density contour lines from 2D KDE. Requires x and y (both numerical). Accepts {:levels 10} for the number of contour levels.

Accepted options: :alpha :color :color-type :data :group :levels :mark :position :size :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-contour :sepal-length :sepal-width))
sepal widthsepal lengthrelative density0.00027.1034567891.52.02.53.03.54.04.55.0

lay-ridgeline

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :ridgeline layer type – stacked density curves by category. Requires categorical x and numerical y. Categories stack vertically with density curves rendered horizontally.

Accepted options: :alpha :bandwidth :color :color-type :data :group :mark :position :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-ridgeline :species :sepal-length))
speciessepal length456789setosaversicolorvirginica

lay-rug

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :rug layer type – short tick marks along the axis showing individual values. X-only: pass one column. Often layered with density or scatter.

Accepted options: :alpha :color :color-type :data :group :length :mark :position :side :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-rug {:side :both}))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

lay-step

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :step layer type – staircase line (horizontal then vertical). Requires x and y (both numerical).

Accepted options: :alpha :color :color-type :data :group :mark :position :size :stat :x :x-type :y :y-type.

(-> tiny
    (pj/lay-step :x :y)
    pj/lay-point)
yx1.01.52.02.53.03.54.04.55.01.01.52.02.53.03.54.04.55.0

lay-summary

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :summary layer type – mean +/- standard error per category. Requires categorical x and numerical y. Shows a point at the mean with error bars for +/- 1 SE. Accepts :color for grouped summaries.

Accepted options: :alpha :color :color-type :data :group :mark :position :size :stat :x :x-type :y :y-type.

(-> (rdatasets/datasets-iris)
    (pj/lay-summary :species :sepal-length))
sepal lengthspeciessetosaversicolorvirginica5.05.25.45.65.86.06.26.46.6

lay-interval-h

[pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :interval-h layer type – horizontal bar from x to x-end at categorical y. Each row becomes one rectangle; the y column is treated categorically so each distinct value occupies its own lane. Required: x (numeric or temporal start), y (categorical lane), :x-end column ref in opts (numeric or temporal end). Accepts :color, :alpha, :interval-thickness (band fill fraction, 0.0-1.0, default 0.7). (lay-interval-h data :start :task {:x-end :end :color :status})

Accepted options: :alpha :color :color-type :data :group :interval-thickness :mark :stat :x :x-end :x-type :y :y-type.

(-> {:start [#inst "2024-01-01" #inst "2024-03-01" #inst "2024-05-01"]
     :end   [#inst "2024-04-01" #inst "2024-06-01" #inst "2024-08-01"]
     :task  ["Design" "Build" "Test"]}
    (pj/lay-interval-h :start :task {:x-end :end}))
taskstartJan-02Jan-28Feb-23Mar-20Apr-15May-11Jun-06Jul-02Jul-28DesignBuildTest

Annotations

Reference lines and shaded bands are regular layers. Position comes from the options map (:y-intercept for lay-rule-h, :x-intercept for lay-rule-v; :y-min/:y-max for lay-band-h, :x-min/:x-max for lay-band-v); :color overrides the default annotation color, and bands additionally honor :alpha to override the default 0.15 opacity. Without x/y columns they attach at the root (every panel); with x/y columns they attach to one matching leaf.

Rule intercepts also accept temporal values (LocalDate, LocalDateTime, Instant, java.util.Date) so date-axis annotations need no manual conversion – see the second lay-rule-v example below.

Note on :y-min/:y-max. The same option keys carry two meanings depending on the layer kind. On lay-band-h/-v they are literal numeric bounds (the band sits at fixed coordinates, independent of the data). On lay-errorbar (above) they are column references – one row per error bar, with :y-min and :y-max naming columns that supply the lower and upper bounds. ggplot2 keeps these separate via aes() (column) versus literal arguments; Plotje overloads the keyword and dispatches by mark.

lay-rule-v

[_pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :rule-v layer – vertical reference line at x = x-intercept. Position comes from opts (not data columns); :x-intercept is required. Accepts :x-intercept (numeric or temporal – LocalDate, LocalDateTime, Instant, java.util.Date) and :color (literal string). Temporal values are converted internally to match the x-axis scale so date-axis annotations work without manual conversion. The 4-arity finds or creates a sub-pose with these x/y columns and attaches the rule there (only panels matching that leaf show it).

  • (lay-rule-v pose {:x-intercept 5}) – root-level, flows to every panel.

  • (lay-rule-v pose :x :y {:x-intercept 5}) – panel-scope (columns pick or create a sub-pose).

  • (lay-rule-v pose {:x-intercept 5 :color "red"}) – with override color.

  • (lay-rule-v pose {:x-intercept #inst "2008-09-15"}) – temporal intercept on a date axis.

Accepted options: :alpha :color :data :mark :stat :x :x-intercept :y.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-rule-v {:x-intercept 6.0}))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

A temporal rule on a date axis – the same pose pattern, with :x-intercept taking a LocalDate.

(-> {:date  [#inst "2024-01-01" #inst "2024-04-01" #inst "2024-08-01"]
     :value [3 5 9]}
    (pj/lay-line :date :value)
    (pj/lay-rule-v {:x-intercept (java.time.LocalDate/parse "2024-06-01")
                    :color "#c0392b"}))
valuedateJan-02Jan-28Feb-23Mar-20Apr-15May-11Jun-06Jul-02Jul-283456789

lay-rule-h

[_pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :rule-h layer – horizontal reference line at y = y-intercept. Position comes from opts (not data columns); :y-intercept is required. Accepts :y-intercept (numeric or temporal – LocalDate, LocalDateTime, Instant, java.util.Date) and :color (literal string). Temporal values are converted internally to match the y-axis scale so date-axis annotations work without manual conversion. The 4-arity finds or creates a sub-pose with these x/y columns and attaches the rule there (only panels matching that leaf show it).

  • (lay-rule-h pose {:y-intercept 3}) – root-level, flows to every panel.

  • (lay-rule-h pose :x :y {:y-intercept 3}) – panel-scope (columns pick or create a sub-pose).

  • (lay-rule-h pose {:y-intercept 3 :color "red"}) – with override color.

  • (lay-rule-h pose {:y-intercept (java.time.LocalDate/parse "2024-01-01")}) – temporal intercept on a date axis.

Accepted options: :alpha :color :data :mark :stat :x :y :y-intercept.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-rule-h {:y-intercept 3.0}))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

lay-band-v

[_pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :band-v layer – vertical shaded band between x = x-min and x = x-max. Position comes from opts (not data columns); :x-min and :x-max are required and :x-min must be <= :x-max. Accepts :x-min (required), :x-max (required), :color (literal string), :alpha. Bounds may be numeric or temporal (LocalDate, LocalDateTime, Instant, java.util.Date); temporal values are converted internally to match the x-axis scale. The 4-arity finds or creates a sub-pose with these x/y columns and attaches the band there (only panels matching that leaf show it).

  • (lay-band-v pose {:x-min 4 :x-max 6}) – root-level, flows to every panel.

  • (lay-band-v pose :x :y {:x-min 4 :x-max 6}) – panel-scope (columns pick or create a sub-pose).

  • (lay-band-v pose {:x-min 4 :x-max 6 :color "blue" :alpha 0.3}) – with color and opacity overrides.

Accepted options: :alpha :color :data :mark :stat :x :x-max :x-min :y.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-band-v {:x-min 5.5 :x-max 6.5}))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

lay-band-h

[_pose-or-data]

[pose-or-data x-or-opts]

[pose-or-data x y-or-opts]

[pose-or-data x y opts]

Add :band-h layer – horizontal shaded band between y = y-min and y = y-max. Position comes from opts (not data columns); :y-min and :y-max are required and :y-min must be <= :y-max. Accepts :y-min (required), :y-max (required), :color (literal string), :alpha. Bounds may be numeric or temporal (LocalDate, LocalDateTime, Instant, java.util.Date); temporal values are converted internally to match the y-axis scale. The 4-arity finds or creates a sub-pose with these x/y columns and attaches the band there (only panels matching that leaf show it).

  • (lay-band-h pose {:y-min 2 :y-max 4}) – root-level, flows to every panel.

  • (lay-band-h pose :x :y {:y-min 2 :y-max 4}) – panel-scope (columns pick or create a sub-pose).

  • (lay-band-h pose {:y-min 2 :y-max 4 :color "blue" :alpha 0.3}) – with color and opacity overrides.

Accepted options: :alpha :color :data :mark :stat :x :y :y-max :y-min.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-band-h {:y-min 2.5 :y-max 3.5}))
sepal widthsepal length4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Transforms

coord

[pose coord-type]

Set coordinate transform on a pose. Coord is plot-level – it applies across every panel. On a composite pose the coord attaches to the root so every descendant leaf inherits it at plan time.

Supported coord-types:

  • :cartesian – standard x-right, y-up mapping (the default).
  • :flip – swap x and y axes (horizontal bars / boxplots).
  • :fixed – equal aspect ratio (1 data unit = 1 data unit).
  • :polar – radial mapping: x to angle, y to radius.

Flip axes:

(-> (rdatasets/datasets-iris)
    (pj/lay-bar :species) (pj/coord :flip))
species05101520253035404550setosaversicolorvirginica

Polar coordinates:

(-> (rdatasets/datasets-iris)
    (pj/lay-bar :species) (pj/coord :polar))

scale

[pose channel scale-type]

Set scale on a pose. Scale is plot-level – it applies across every panel. Accepts a type keyword or a scale spec map with :type, optional :domain, optional :breaks (explicit tick locations), and optional :labels (custom tick text paired with :breaks). On a composite pose the scale attaches to the root so every descendant leaf inherits it at plan time.

Channels and accepted scale types:

  • Axis channels (:x, :y) accept :linear, :log, :categorical.

  • Continuous visual channels (:size, :alpha, :fill, :color) accept :linear and :log only – :categorical does not apply.

  • Discrete visual channels (:shape, :group) accept :categorical only – :linear and :log do not apply to a discrete encoding.

The :domain on a discrete scale gives explicit category order for the legend.

:labels requires :breaks and must match it in count. Use it to render numeric positions with custom text – for example, days of the week on a tile heatmap.

  • (scale pose :x :log) – log scale on x-axis.

  • (scale pose :x {:type :categorical :domain [...]}) – explicit category order.

  • (scale pose :y {:type :linear :breaks [0 5 10]}) – pin tick locations.

  • (scale pose :x {:type :linear :breaks [1 2 3 4 5 6 7] :labels ["Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"]}) – numeric positions with custom tick text.

  • (scale pose :y {:type :log :domain [1 1000]}) – log scale with explicit range.

  • (scale pose :size :log) – log-spaced point sizes.

  • (scale pose :fill :log) – log-spaced tile fill.

  • (scale pose :shape {:type :categorical :domain [...]}) – shape legend order.

Log scale:

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width) (pj/scale :x :log))
sepal widthsepal length52.02.53.03.54.04.5

Fixed domain:

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width) (pj/scale :x {:domain [3 9]}))
sepal widthsepal length34567892.02.53.03.54.04.5

Log scale on a visual channel (:size, :alpha, :fill, or :color):

(-> {:user [:a :b :c] :n [10 100 1000]}
    (pj/lay-point :user :n {:size :n :x-type :categorical})
    (pj/scale :size :log))
nusern10.0100.01000.0abc01002003004005006007008009001000

Custom tick labels on a numeric axis – pair :breaks with :labels:

(-> (for [d (range 1 8)] {:day d :v (mod d 3)})
    (pj/lay-point :day :v)
    (pj/scale :x {:type :linear
                  :breaks [1 2 3 4 5 6 7]
                  :labels ["Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"]}))
vdayMonTueWedThuFriSatSun0.00.20.40.60.81.01.21.41.61.82.0

Faceting

facet

[pose col]

[pose col direction]

Facet a pose by a column. direction is :col (default, horizontal row) or :row (vertical column). Faceting is plot-level – every panel is faceted the same way. Composite poses are not supported yet.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width {:color :species})
    (pj/facet :species))
sepal widthsepal lengthspeciessetosaversicolorvirginica682.02.53.03.54.04.56868setosaversicolorvirginica

facet-grid

[pose col-col row-col]

Facet a pose by two columns (2D grid). Faceting is plot-level – every panel is faceted the same way. Composite poses are not supported yet.

(-> (rdatasets/reshape2-tips)
    (pj/lay-point :total-bill :tip {:color :sex})
    (pj/facet-grid :smoker :sex))
tiptotal billsexFemaleMale51020405102040NoYesFemaleMale

Composition

arrange

[plots]

[plots opts]

Arrange multiple leaf poses in a grid. Returns a composite pose that renders through the compositor via membrane – so :svg, :bufimg, and any other membrane target work uniformly.

Inputs must be leaf poses. Pre-rendered hiccup is not accepted; build your own [:div ...] if you need to combine already-rendered values outside the library.

Opts:

  • :cols – explicit column count (default: min(4, n-plots)).

  • :title – centered title band above the grid.

  • :width – total composite width in pixels.

  • :height – total composite height in pixels.

  • :share-scales – subset of #{:x :y} shared across cells (default: #{}).

  • (arrange [fr-a fr-b]) – 1x2 row.

  • (arrange [fr-a fr-b fr-c] {:cols 2 :width 900}) – 2x2 grid (wraps).

  • (arrange [[fr-a fr-b] [fr-c fr-d]]) – explicit 2x2 grid.

(pj/arrange [(-> (rdatasets/datasets-iris)
                 (pj/lay-point :sepal-length :sepal-width {:color :species})
                 (pj/options {:width 250 :height 200}))
             (-> (rdatasets/datasets-iris)
                 (pj/lay-point :petal-length :petal-width {:color :species})
                 (pj/options {:width 250 :height 200}))]
            {:cols 2})
sepal widthsepal length682.02.53.03.54.04.5petal widthpetal length50.00.51.01.52.02.5speciessetosaversicolorvirginica

Rendering

plot

[pose]

[pose opts]

Render a pose to a figure. The format keyword in the pose’s :opts ({:format :svg} – default; {:format :bufimg} for raster PNG via Java2D; or any other registered backend) selects which membrane->plot defmethod runs.

On a composite pose, leaves are rendered individually and tiled via the layout in the resolved chrome, in the same chosen format. The pose flows through the canonical pose -> draft -> plan -> membrane -> plot pipeline for both leaf and composite shapes. pj/plot is a literal composition of the public atomic steps:

(let [pose (->pose x) opts (:opts pose {}) fmt (or (:format opts) :svg)] (-> pose pose->draft draft->plan (plan->membrane opts) (membrane->plot fmt opts)))

Plan-derived dimensions ride as record fields on the membrane (accessed via membrane.ui/width/membrane.ui/height); the title rides as :plotje/title. membrane->plot reads them from there.

  • (plot pose)
  • (plot pose {:width 800 :title "My Plot"})
  • (plot pose {:format :bufimg}) – returns a BufferedImage.

See the Customization notebook for options (title, theme, tooltip, brush, legend position, palette).

(-> tiny
    (pj/lay-point :x :y))
yx1.01.52.02.53.03.54.04.55.01.01.52.02.53.03.54.04.55.0

options

[pose opts]

Set plot-level options (title, labels, width, height, etc.). Nested maps (e.g. :theme) are deep-merged. :width and :height are coerced to long (rounded) so the plan carries integer pixel dimensions through to render. On a composite pose the options attach to the root so every descendant leaf inherits them at plan time.

Set render options on a pose:

(-> tiny
    (pj/lay-point :x :y)
    (pj/options {:width 400 :height 200 :title "Small Plot"}))
Small Plotyx1234524

Predicates

pose?

[x]

Return true if x is a pose-shaped plain map (a map carrying at least one of :layers or :poses).

Check whether a value is a pose (leaf or composite):

(pj/pose? (-> tiny (pj/pose :x :y) pj/lay-point))
true

plan?

[x]

Return true if x is a plan (leaf or composite) – the resolved geometry returned by pj/plan.

Check whether a value is a plan (from pj/plan):

(pj/plan? (pj/plan (pj/lay-point tiny :x :y)))
true

leaf-plan?

[x]

Return true if x is a leaf plan (single-pose resolved geometry).

Check whether a plan is a leaf (single-panel resolved geometry). A non-composite plan from a leaf pose is a leaf plan:

(pj/leaf-plan? (pj/plan (pj/lay-point tiny :x :y)))
true

composite-plan?

[x]

Return true if x is a composite plan (a tree of sub-plots with shared chrome).

Check whether a plan is a composite (a tree of sub-plots, returned by pj/plan on a composite pose like one from pj/arrange):

(pj/composite-plan?
 (pj/plan (pj/arrange [(pj/lay-point tiny :x :y)
                       (pj/lay-point tiny :x :y)])))
true

draft?

[x]

Return true if x is a draft – the intermediate representation produced by pj/pose->draft (and so by pj/draft). A draft is either a LeafDraft record (leaf pose) or a CompositeDraft record (composite pose). Used by cross-stage misuse guards on pj/plan and pj/plot.

Check whether a value is a draft (from pj/draft). True for both LeafDraft records and CompositeDraft records:

(pj/draft? (pj/draft (pj/lay-point tiny :x :y)))
true

leaf-draft?

[x]

Return true if x is a leaf draft (a LeafDraft record carrying :layers – a vector of layer maps – and :opts – the pose-level options that flow into the plan stage).

Check whether a draft is a leaf (a LeafDraft record carrying :layers and :opts, returned by pj/draft on a leaf pose):

(pj/leaf-draft? (pj/draft (pj/lay-point tiny :x :y)))
true

composite-draft?

[x]

Return true if x is a composite draft (a tree of sub-drafts with shared chrome-spec, returned by pj/draft on a composite pose).

Check whether a draft is a composite (a tree of sub-drafts, returned by pj/draft on a composite pose):

(pj/composite-draft?
 (pj/draft (pj/arrange [(pj/lay-point tiny :x :y)
                        (pj/lay-point tiny :x :y)])))
true

plan-layer?

[x]

Return true if x is a plan-layer (resolved geometry for one mark).

Check whether a value is a resolved plan layer:

(pj/plan-layer? (first (:layers (first (:panels (pj/plan (pj/lay-point tiny :x :y)))))))
true

layer-type?

[x]

Return true if x is a layer type (mark + stat + position bundle from the registry).

Check whether a value is a registered layer-type map:

(pj/layer-type? (pj/layer-type-lookup :point))
true

membrane?

[x]

Return true if x is a PlotjeMembrane – the value returned by pj/plan->membrane and pj/membrane. A PlotjeMembrane is a Membrane UI component (implements IOrigin, IBounds, IChildren) carrying the rendered drawables and plan-derived width/height; the plot title rides as :plotje/title.

Check whether a value is a PlotjeMembrane – the value returned by pj/plan->membrane and pj/membrane:

(pj/membrane? (pj/membrane (pj/lay-point tiny :x :y)))
true

Inspection

draft

[pose]

[pose opts]

Resolve raw input into a draft. Literal composition of the atomic steps: (-> x ->pose pose->draft). The 2-arity folds opts into the pose with pj/options first, mirroring pj/plan and pj/plot: (-> x ->pose (options opts) draft).

For a leaf pose, returns a LeafDraft record (:layers is a vector of flat maps, one per applicable layer with merged scope; :opts carries the pose-level options that flow into the plan stage). For a composite pose, returns a CompositeDraft carrying per-leaf drafts (each contextualized – shared-scale domains injected, suppress-* flags applied), the resolved chrome geometry, and the layout (path -> rect).

  • (draft pose)
  • (draft pose {:width 800 :title "Plot"})

Flatten a pose into a draft – a LeafDraft record holding :layers (one map per applicable layer, with all scope merged) and :opts (pose-level options). Useful for inspecting exactly what the plan stage will consume:

(-> (rdatasets/datasets-iris)
    (pj/pose :sepal-length :sepal-width)
    pj/lay-point
    pj/draft
    kind/pprint)
{:layers
 [{:x :sepal-length,
   :y :sepal-width,
   :mark :point,
   :stat :identity,
   :layer-type :point,
   :data
   https://vincentarelbundock.github.io/Rdatasets/csv/datasets/iris.csv [150 6]:

| :rownames | :sepal-length | :sepal-width | :petal-length | :petal-width |  :species |
|----------:|--------------:|-------------:|--------------:|-------------:|-----------|
|         1 |           5.1 |          3.5 |           1.4 |          0.2 |    setosa |
|         2 |           4.9 |          3.0 |           1.4 |          0.2 |    setosa |
|         3 |           4.7 |          3.2 |           1.3 |          0.2 |    setosa |
|         4 |           4.6 |          3.1 |           1.5 |          0.2 |    setosa |
|         5 |           5.0 |          3.6 |           1.4 |          0.2 |    setosa |
|         6 |           5.4 |          3.9 |           1.7 |          0.4 |    setosa |
|         7 |           4.6 |          3.4 |           1.4 |          0.3 |    setosa |
|         8 |           5.0 |          3.4 |           1.5 |          0.2 |    setosa |
|         9 |           4.4 |          2.9 |           1.4 |          0.2 |    setosa |
|        10 |           4.9 |          3.1 |           1.5 |          0.1 |    setosa |
|       ... |           ... |          ... |           ... |          ... |       ... |
|       140 |           6.9 |          3.1 |           5.4 |          2.1 | virginica |
|       141 |           6.7 |          3.1 |           5.6 |          2.4 | virginica |
|       142 |           6.9 |          3.1 |           5.1 |          2.3 | virginica |
|       143 |           5.8 |          2.7 |           5.1 |          1.9 | virginica |
|       144 |           6.8 |          3.2 |           5.9 |          2.3 | virginica |
|       145 |           6.7 |          3.3 |           5.7 |          2.5 | virginica |
|       146 |           6.7 |          3.0 |           5.2 |          2.3 | virginica |
|       147 |           6.3 |          2.5 |           5.0 |          1.9 | virginica |
|       148 |           6.5 |          3.0 |           5.2 |          2.0 | virginica |
|       149 |           6.2 |          3.4 |           5.4 |          2.3 | virginica |
|       150 |           5.9 |          3.0 |           5.1 |          1.8 | virginica |
,
   :__panel-idx 0}],
 :opts {}}

plan

[pose]

[pose opts]

Convert a pose into a plan. Literal composition of the atomic steps: (-> x ->pose pose->draft draft->plan). The 2-arity folds opts into the pose with pj/options first: (-> x ->pose (options opts) plan).

For a leaf pose, returns a Plan record with one panel per facet variant. For a composite pose, returns a CompositePlan record with :sub-plots tying each leaf path to its rect and sub-plan, plus :chrome carrying the resolved layout geometry (title-band, grid-rect, strip labels, shared-legend spec).

  • (plan pose)
  • (plan pose {:title "My Plot"})

Returns the intermediate plan data structure:

(def plan1 (-> tiny
               (pj/lay-point :x :y)
               pj/plan))
plan1
{:panels
 [{:coord :cartesian,
   :y-domain [0.8 5.2],
   :x-scale {:type :linear},
   :x-domain [0.8 5.2],
   :x-ticks
   {:values [1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0],
    :labels ["1.0" "1.5" "2.0" "2.5" "3.0" "3.5" "4.0" "4.5" "5.0"],
    :categorical? false},
   :col 0,
   :layers
   [{:mark :point,
     :style {:opacity 0.75, :radius 3.0},
     :size-scale nil,
     :alpha-scale nil,
     :groups
     [{:color [0.2 0.2 0.2 1.0], :xs #tech.v3.dataset.column<int64>[5]
:x
[1, 2, 3, 4, 5], :ys #tech.v3.dataset.column<int64>[5]
:y
[2, 4, 1, 5, 3], :row-indices #tech.v3.dataset.column<int64>[5]
:__row-idx
[0, 1, 2, 3, 4]}],
     :y-domain [1 5],
     :x-domain [1 5]}],
   :y-scale {:type :linear},
   :y-ticks
   {:values [1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0],
    :labels ["1.0" "1.5" "2.0" "2.5" "3.0" "3.5" "4.0" "4.5" "5.0"],
    :categorical? false},
   :row 0}],
 :width 600,
 :height 400,
 :caption nil,
 :total-width 600.0,
 :legend-position :none,
 :layout-type :single,
 :layout
 {:subtitle-pad 0,
  :legend-w 0,
  :caption-pad 0,
  :y-label-pad 42.5,
  :legend-h 0.0,
  :title-pad 0,
  :strip-h 0,
  :x-label-pad 38,
  :strip-w 0.0},
 :grid {:rows 1, :cols 1},
 :legend nil,
 :panel-height 362.0,
 :title nil,
 :y-label "y",
 :alpha-legend nil,
 :x-label "x",
 :subtitle nil,
 :panel-width 557.5,
 :size-legend nil,
 :total-height 400.0,
 :tooltip nil,
 :margin 10}

svg-summary

[svg-or-pose]

[svg-or-pose theme]

Extract structural summary from SVG hiccup for testing. Returns a map with :width, :height, :panels, :points, :lines, :polygons, :tiles, :visible-tiles, and :texts – useful for asserting plot structure. Accepts SVG hiccup or a pose (auto-renders to SVG first).

  • (svg-summary (plot fr)) – summary of rendered SVG.
  • (svg-summary my-pose) – auto-renders pose (leaf or composite).
(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width {:color :species}) pj/svg-summary)
{:sizes #{3.0},
 :texts
 ["sepal width"
  "sepal length"
  "species"
  "setosa"
  "versicolor"
  "virginica"
  "4.5"
  "5.0"
  "5.5"
  "6.0"
  "6.5"
  "7.0"
  "7.5"
  "8.0"
  "2.0"
  "2.5"
  "3.0"
  "3.5"
  "4.0"
  "4.5"],
 :width 600,
 :lines 0,
 :colors #{"none" "rgb(228,26,28)" "rgb(55,126,184)" "rgb(77,175,74)"},
 :points 150,
 :alphas #{0.75},
 :tiles 0,
 :polygons 0,
 :visible-tiles 0,
 :panels 1,
 :height 400,
 :shapes #{:rect}}

valid-plan?

[plan]

Check if a plan conforms to the Malli schema.

  • (valid-plan? (plan pose)) – true if valid.
(pj/valid-plan? plan1)
true

explain-plan

[plan]

Explain why a plan does not conform to the Malli schema. Returns nil if valid, or a Malli explanation map if invalid.

  • (explain-plan (plan pose))
(pj/explain-plan plan1)
nil

Pipeline

The pipeline is a composition of atomic single-step transitions. The user-facing functions (pj/draft, pj/plan, pj/membrane, pj/plot) are literal compositions of these steps – each one runs the chain up through its named stage.

Each atomic step is independently callable, so you can stop the pipeline at any point to inspect the intermediate value.

membrane

[pose]

[pose opts]

Resolve a pose into a PlotjeMembrane. Literal composition of the atomic steps: (let [pose (->pose x), opts (:opts pose {})] (-> pose pose->draft draft->plan (plan->membrane opts))). The let lifts the pose once so the chain can pluck pose-level opts and pass them to plan->membrane. The 2-arity folds opts into the pose with pj/options first.

Returns a PlotjeMembrane – a Membrane UI component (implements IOrigin, IBounds, IChildren) carrying the rendered drawables plus plan-derived width and height; the title, when set, rides as :plotje/title. Render-time options (:tooltip, :theme, :palette, :color-scale, :color-midpoint) ride along on the pose’s :opts and reach plan->membrane through this call.

Useful for exploring rendering targets beyond the SVG and Java2D backends Plotje wires in today: any Membrane backend can consume the result of pj/membrane via the standard Membrane protocols.

  • (membrane pose)
  • (membrane pose {:tooltip true})

Resolve a pose into a PlotjeMembrane – a format-agnostic Membrane UI component (a record implementing IOrigin, IBounds, IChildren). Useful for exploring rendering targets beyond the SVG and Java2D backends Plotje wires in today, and for composing Plotje plots into larger Membrane interfaces. The Membranes chapter walks the record’s anatomy and the protocols.

(let [m (pj/membrane (pj/lay-point tiny :x :y))]
  {:membrane?    (pj/membrane? m)
   :width        (membrane.ui/width m)
   :height       (membrane.ui/height m)
   :record-keys  (sort (filter keyword? (keys m)))})
{:membrane? true,
 :width 600,
 :height 400,
 :record-keys (:drawables :height :width)}

->pose

[x]

[x caller]

Lift the input to a pose. The first atomic step of the pipeline. Polymorphic on input:

  • a pose-shaped map flows through pose-kind (validated, *config* captured, Kindly auto-render metadata attached); idempotent on input that already carries the metadata, so repeated lifts are cheap;

  • raw data (a dataset, vector of row maps, or column map) becomes a leaf pose with :data set and no mapping, run through prepare-pose so the Kindly metadata is attached.

Throws on nil or non-collection scalars. Use (pj/pose) for an explicit empty leaf instead of passing nil.

The optional caller argument names the public-facing function shown in error messages, so users see β€œpj/lay-point requires data…” rather than an internal helper name. Defaults to β€œpj/->pose”.

  • (->pose data) – raw dataset becomes a leaf pose
  • (->pose pose) – already a pose; idempotent lift

Lift raw input to a pose. Raw data (datasets, vectors of row maps, column maps) becomes an empty leaf pose with :data set; an existing pose flows through unchanged (idempotent). The first atomic step of the pipeline.

(pj/pose? (pj/->pose tiny))
true

pose->draft

[pose]

Single-step transition: convert a pose into a draft. Dispatches on pose shape – a leaf pose becomes a LeafDraft (a record carrying :layers – a vector of one map per applicable layer with merged scope – and :opts – the pose-level options that flow into the plan stage); a composite pose becomes a CompositeDraft carrying per-leaf drafts (each contextualized with shared-scale domains and chrome-driven opt adjustments), the resolved chrome geometry, and the layout (path -> rect).

  • (pose->draft (pj/lay-point data :x :y))

Single-step transition: pose to draft. Dispatches on shape – leaf poses produce LeafDraft records, composite poses produce CompositeDraft records.

(pj/leaf-draft?
 (pj/pose->draft (pj/lay-point tiny :x :y)))
true

plan->membrane

[plan-data]

[plan-data opts]

Convert a plan into a PlotjeMembrane – a Membrane UI component carrying the rendered drawables, plan-derived width and height, and the plot title.

The 1-arity uses no rendering options. The 2-arity takes an opts map with optional :tooltip, :theme, :palette, etc.

The result implements membrane.ui IOrigin, IBounds, and IChildren, so width and height are accessible via (membrane.ui/width m) and (membrane.ui/height m). The title, when set, rides as :plotje/title. Future per-membrane attributes use the same :plotje/* namespaced-keyword convention. The shape is captured by the PlotjeMembraneSchema in scicloj.plotje.impl.membrane.

  • (plan->membrane (plan fr))
  • (plan->membrane (plan fr) {:tooltip true})
(def m1 (pj/plan->membrane plan1))
(pj/membrane? m1)
true

membrane->plot

[membrane-tree format opts]

Convert a PlotjeMembrane into a figure for the given format. Dispatches on format keyword; :svg is always available.

Reads width and height from the membrane via (membrane.ui/width m) / (membrane.ui/height m) (so any Membrane backend can introspect the canvas size), and the title from (:plotje/title m).

  • (membrane->plot (plan->membrane (plan pose)) :svg {})
(first (pj/membrane->plot m1 :svg {}))
:svg

plan->plot

[plan format opts]

Convert a plan into a figure for the given format. Dispatches on format keyword. Each renderer is a separate namespace that registers a defmethod; :svg is always available.

  • (plan->plot (plan fr) :svg {})
  • (plan->plot (plan fr) :plotly {})
(first (pj/plan->plot plan1 :svg {}))
:svg

The draft->* family lets the same pipeline start from a draft instead of a fully-resolved plan. Useful when you have a draft in hand (e.g. from inspection) and want to skip re-running the layer-flattening step.

draft->plan

[draft]

Single-step transition: convert a draft into a plan. Dispatches on draft shape – a LeafDraft carries :layers and pose-level :opts that flow into plan/draft->plan; a CompositeDraft goes through compositor/composite-draft->plan (which uses the chrome-spec already baked in at draft emission).

Plan-stage opts (:width, :height, :title, …) ride on the draft itself – on the LeafDraft’s :opts for leaves, on the CompositeDraft’s chrome-spec for composites. Set them on the pose via pj/options before drafting.

  • (draft->plan (draft pose))
(def draft1 (pj/draft (pj/lay-point tiny :x :y)))
(pj/plan? (pj/draft->plan draft1))
true

draft->membrane

[draft]

[draft opts]

Compose draft -> plan -> membrane. The 2-arity takes an opts map for plan->membrane (e.g. {:tooltip true}).

  • (draft->membrane (draft pose))
  • (draft->membrane (draft pose) {:tooltip true})
(pj/membrane? (pj/draft->membrane draft1))
true

draft->plot

[draft format opts]

Compose draft -> plan -> plot for the given format.

  • (draft->plot (draft pose) :svg {})
  • (draft->plot (draft pose) :bufimg {})
(first (pj/draft->plot draft1 :svg {}))
:svg

Configuration

config

[]

Return the effective resolved configuration as a map. Merges: library defaults < plotje.edn < set-config! < *config*. Useful for inspecting which values are in effect.

  • (config) – show current resolved config.
(pj/config)
{:strict false,
 :margin-multi 10,
 :validate true,
 :point-stroke "none",
 :title-offset 18,
 :tick-spacing-y 40,
 :panel-size 200,
 :min-panel-size 20,
 :label-offset 38,
 :label-font-size 13,
 :default-color "#333",
 :width 600,
 :legend-header-pad 20,
 :point-stroke-width 0,
 :annotation-dash [4 3],
 :legend-width 100,
 :legend-entry-height 18,
 :theme {:bg "#E8E8E8", :grid "#F5F5F5", :font-size 11},
 :bin-method :sturges,
 :domain-padding 0.05,
 :strip-height 16,
 :point-opacity 0.75,
 :line-width 2.5,
 :grid-stroke-width 0.6,
 :tick-spacing-x 60,
 :strip-font-size 10,
 :title-font-size 15,
 :band-opacity 0.15,
 :bar-opacity 0.85,
 :annotation-stroke "#333",
 :height 400,
 :margin 10,
 :point-radius 3.0}

set-config!

[m]

Set global config overrides. Persists across calls until reset.

  • (set-config! {:palette :dark2 :theme {:bg "#FFFFFF"}}) – override palette and background.

  • (set-config! nil) – reset to defaults.

with-config

[config-map & body]

Execute body with thread-local config overrides. Overrides take precedence over set-config! and defaults, but plot options still win.

  • (with-config {:theme {:bg "#FFF"}} (plot ...))
(pj/with-config {:palette :pastel1}
  (:palette (pj/config)))
:pastel1

Documentation Metadata

Three maps document the option keys at each scope level.

config-key-docs

Documentation metadata for configuration keys. Maps each config key to [category description]. Use with (pj/config) to build reference tables.

(count pj/config-key-docs)
37

plot-option-docs

Documentation for plot-level option keys. These are accepted by pj/options, pj/plan, and pj/plot but are inherently per-plot (text content or nested config override). Maps each key to [category description].

(count pj/plot-option-docs)
14

layer-option-docs

Documentation for layer option keys accepted by lay- functions. Maps each key to a description string.

(count pj/layer-option-docs)
29

Layer Type Registry

layer-type-lookup

[k]

Look up a registered layer type by keyword. Returns the layer-type map (with :mark, :stat, :position, :doc), or nil if not found.

  • (layer-type-lookup :histogram) returns {:mark :bar, :stat :bin, ...}.
(pj/layer-type-lookup :smooth)
{:mark :line,
 :stat :loess,
 :accepts
 [:confidence-band
  :level
  :bootstrap-resamples
  :bandwidth
  :size
  :nudge-x
  :nudge-y],
 :doc
 "Smoothed trend line β€” defaults to LOESS; pass {:stat :linear-model} for OLS."}

registered-layer-types

[]

Return all registered layer types as a map of keyword -> layer-type map. Useful for generating documentation tables.

(count (pj/registered-layer-types))
26
(first (pj/registered-layer-types))
[:smooth
 {:mark :line,
  :stat :loess,
  :accepts
  [:confidence-band
   :level
   :bootstrap-resamples
   :bandwidth
   :size
   :nudge-x
   :nudge-y],
  :doc
  "Smoothed trend line β€” defaults to LOESS; pass {:stat :linear-model} for OLS."}]

Documentation Helpers

Query the self-documenting dispatch tables for any extensible concept.

stat-doc

[k]

Return the prose description for a stat keyword. Returns "(no description)" if no [:key :doc] defmethod is registered.

  • (stat-doc :bin) returns "Bin numerical values into ranges".
(pj/stat-doc :linear-model)
"Linear model β€” OLS regression line + optional confidence band"

mark-doc

[k]

Return the prose description for a mark keyword. Returns "(no description)" if no [:key :doc] defmethod is registered.

  • (mark-doc :point) returns "Filled circle".
(pj/mark-doc :point)
"Filled circle"

position-doc

[k]

Return the prose description for a position keyword. Returns "(no description)" if no [:key :doc] defmethod is registered.

  • (position-doc :dodge) returns "Shift groups side-by-side within a band".
(pj/position-doc :dodge)
"Shift groups side-by-side within a band"

scale-doc

[k]

Return the prose description for a scale keyword. Returns "(no description)" if no [:key :doc] defmethod is registered.

  • (scale-doc :linear) returns "Continuous linear mapping".
(pj/scale-doc :linear)
"Continuous linear mapping"

coord-doc

[k]

Return the prose description for a coordinate type keyword. Returns "(no description)" if no [:key :doc] defmethod is registered.

  • (coord-doc :polar) returns "Radial mapping: x->angle, y->radius".
(pj/coord-doc :cartesian)
"Standard x-right, y-up mapping"

membrane-mark-doc

[k]

Return the prose description for how a mark renders to membrane drawables. Returns "(no description)" if no [:key :doc] defmethod is registered.

  • (membrane-mark-doc :point) returns "Translated colored rounded-rectangles".
(pj/membrane-mark-doc :point)
"Translated colored rounded-rectangles"

Export

save

[pose path]

[pose path opts]

Save a plot to a file. Format resolution, in precedence order: 1. :format in the 3-arity opts map wins (must be :svg or :png). 2. :format on the pose’s :opts (:svg or :png; legacy :bufimg is translated to :png). 3. Otherwise inferred from the path extension (.svg -> :svg, .png -> :png). 4. Default :svg.

When the resolved format and the path extension disagree, prints a warning – the file still gets the bytes the resolved format produces, but the extension is misleading.

The save vocabulary names the file format. The plot vocabulary (pj/plot’s :format) names the JVM return type – :svg for hiccup, :bufimg for a Java2D BufferedImage. A pose-level :format flows into both contexts; save reinterprets :bufimg as :png because the file on disk is a PNG.

Arguments:

  • pose – a pose.
  • path – file path (string or java.io.File).
  • opts – same options as plot, but :format accepts only :svg or :png.

Tooltip and brush interactivity are not included in saved files. Returns the path.

  • (save my-pose "plot.svg") – SVG.
  • (save my-pose "plot.png") – inferred PNG.
  • (save my-pose "plot.svg" {:format :png}) – opts override (warns).

Save a plot to an SVG file:

(let [path (str (java.io.File/createTempFile "plotje-example" ".svg"))]
  (-> (rdatasets/datasets-iris)
      (pj/lay-point :sepal-length :sepal-width {:color :species})
      (pj/save path {:title "Iris Export"}))
  (.contains (slurp path) "<svg"))
true

Save a plot to a PNG file. Extension inference picks the raster backend; the returned bytes start with the PNG magic header:

(let [path (str (java.io.File/createTempFile "plotje-example" ".png"))]
  (-> (rdatasets/datasets-iris)
      (pj/lay-point :sepal-length :sepal-width {:color :species})
      (pj/save path))
  (with-open [in (java.io.FileInputStream. path)]
    (let [bs (byte-array 8)]
      (.read in bs)
      (mapv #(bit-and ^int % 0xFF) (vec bs)))))
[137 80 78 71 13 10 26 10]

Pass {:format :png} explicitly when the path’s extension does not match the desired format, or when it is built dynamically:

(let [path (str (java.io.File/createTempFile "plotje-example" ".out"))]
  (-> (rdatasets/datasets-iris)
      (pj/lay-point :sepal-length :sepal-width {:color :species})
      (pj/save path {:format :png}))
  (with-open [in (java.io.FileInputStream. path)]
    (let [bs (byte-array 4)]
      (.read in bs)
      (mapv #(bit-and ^int % 0xFF) (vec bs)))))
[137 80 78 71]
source: notebooks/plotje_book/api_reference.clj