29 Known Limitations
Gaps in the current Plotje release. None produce crashes on canonical inputs; each is documented and tracked for post-alpha work.
(ns plotje-book.known-limitations
(:require
;; Kindly -- notebook rendering protocol
[scicloj.kindly.v4.kind :as kind]))Layout and Visuals
Multi-layer overlays like
(-> data (pj/lay-point ...) (pj/lay-smooth {:stat :linear-model}) (pj/lay-smooth {:stat :loess}))do not auto-generate a layer-kind legend to distinguish the two regression curves. Workaround: color each layer explicitly.Histograms, stacked bars, step plots, and other stat-derived marks do not default to a
"count"or"density"y-label.Linear continuous color legends (numeric
:colormapping with:linearscale) label only the endpoint tick marks on the gradient bar. Intermediate values are unlabeled, making it hard to map interior colors back to data values. Log-scaled color and fill legends do carry intermediate ticks.SPLOMs with 6+ variables at the default 600x400 have tight panels. Increase
:width/:heightor pin:panel-width/:panel-height.Horizontal bars from
(pj/coord :flip)render the first row of data at the bottom of the chart, so a dataset sorted descending (natural “top N” order) produces the biggest bar at the bottom. The behavior matches ggplot2’scoord_flip(). Workaround: sort the dataset ascending before plotting, e.g.(tc/order-by data [:value] [:asc]). A future opt-in flag such as(pj/coord :flip {:reverse-categorical true})would spare users the sort.:fit-text-domainwidens a numeric domain so that text and label marks near its edge are drawn in full. Nothing does the same for a mark whose size is in drawing units for another reason: apj/lay-pointgiven a large:sizeat the extreme of its domain is cut at the panel edge, as is a long rug tick. The 5% domain padding absorbs this at ordinary radii, and a scale given a wide:range– say(pj/scale pose :size {:range [3 20]})– reaches past it. Workaround: widen the domain withpj/scale.Rotated x-tick labels (
:x-tick-angle) reserve extra vertical space below the panel, but not extra horizontal space. A long label rotated to a diagonal extends to the left of its tick, so very long category names on the leftmost tick can run past the left edge of the plotting area. Workaround: shorten the labels, reduce the angle, or widen the plot with:width.A categorical axis accepts its categories and nothing between them. ggplot2 places categories at 1, 2, 3 and lets a mark be drawn at 1.5 or 2.2, halfway along the gap; Plotje’s categorical axis is a band scale, which answers nothing for a value between two bands, so a mark asking for one is not drawn. Workaround: where the reason for wanting a fractional place was to clear another mark,
:offset-x/:offset-yshift by a distance on the page and work on any axis. Wanting a place genuinely between two categories has no workaround; it needs the categorical axis to become a continuous scale carrying a label table, which is designed but not implemented.A line has no arrowhead.
pj/lay-linedraws a plain stroke, with:stroke-dashfor dashed and dotted styles, so a leader line pointing from a note to the mark it describes ends bluntly. Workaround: end the line on a smallpj/lay-pointmarker, as the callout recipe in the Cookbook does.Nothing moves a label out of the way of another label. Two text marks at nearby positions are drawn on top of each other, and labelling every point of a dense scatter produces an unreadable pile. Workarounds: label a subset, or separate them by hand with
:offset-x/:offset-y. Automatic placement, as in R’sggrepel, is designed but not implemented.A long label at the right edge of a panel costs axis range.
:fit-text-domainmakes room for it by widening the domain, which is the only lever available – there is no way to reserve drawing-space room for labels outside the drawing area. Several sentence-length labels at the line ends can push the axis well past the data. Workarounds: shorten the end labels and put the sentences in a callout, or turn the widening off with{:fit-text-domain false}and accept the clipping.:inplaces a layer in the data or on the panel (:in :drawing-area), and nothing places one on the canvas as a whole. A note outside every panel – beside the legend, or under the title – has no position to be given. Workaround::captionand:subtitlefor text below and above the panels.
Marks
Aesthetic-gate versus mark-consumer asymmetry. Several aesthetics are accepted at the universal pose-mapping gate but consumed only by one or two mark extractors. Setting them on other marks does not vary the picture.
Aesthetic Consumed on Elsewhere :size(column ref)lay-pointwarned, and no legend is drawn for it :alpha(column ref)lay-pointwarned, and no legend is drawn for it (a written :alpha Nworks on most via:fixed-alpha)numeric (continuous) :colorlay-point,lay-interval-hevery other lay-* ignores it (a numeric column on a categorical-color path produces banded palette colors instead of a gradient) tooltip / row-indices plumbing lay-point,lay-interval-hevery other lay-* Only
lay-pointvaries a radius or an opacity from row to row, so{:size {:column :r :scale false}}– which asks for the column’s own values as radii – is refused elsewhere rather than warned: there is no reading for it at all. The warning and the refusal both namelay-point. Which marks those are is read from the registry, where each layer type declares what its mark varies under:varies, so an extension that draws a per-row size says so and is named alongsidelay-point.:shapeis not in this table: it is drawn only bylay-point, and passing it anywhere else is rejected rather than ignored – every other layer type warns and nameslay-pointas the one that takes it.:textis the same, and nameslay-textandlay-label.Workaround: pre-bin or convert the numeric column into a discrete color column where appropriate, or use
lay-pointfor the mark where the aesthetic must vary per row.:alphaonpj/lay-rule-h/pj/lay-rule-vis silently dropped at render time (the rendering path reads:coloronly). Bands honor:alpha. Workaround: use a lighter:colorto simulate the visual effect on rules.pj/lay-rule-hrendered under(pj/coord :flip)becomes a vertical line;pj/lay-rule-vbecomes a horizontal line. The mark name still reflects the unflipped semantics. Add a one-line note in the surrounding prose if the chart’s flipped state is non-obvious.:position :dodgeis ignored at render-time on several marks. The request fails in one of two ways: onlay-barandlay-summarythe dodge request is dropped at construction; onlay-pointandlay-linethe dodge metadata reaches the plan but no geometric offset is applied. Workaround: pre-compute dodge offsets viatc/group-by.Polar plots for bar-family marks don’t auto-emit category labels – rose charts currently render with zero text.
Stacked bars don’t split positive and negative values; all-positive data works, but mixed-sign data stacks incorrectly.
pj/lay-tile(and the underlying:bin2dstat) requires numeric x and y columns. Passing a categorical axis throws a clear “Stat :bin2d requires a numeric column” error at plan time. The recommended workaround is to render a numeric-indexed grid (1-N integers in place of the categorical column) and pair:breakswith:tick-labelson the axis – see Customization and Troubleshooting for a worked example. For a true categorical axis (binning over labels rather than numeric intervals),pj/lay-barwith aycolumn and{:color :value}gives a categorical “heatmap” look.Horizontal value bars (
pj/lay-barwith the category on:y) support plain and dodged layouts, but not:position :stackor:position :fill– stacking accumulates along the y-axis, which is the categorical band for a horizontal bar. Workaround: put the category on:xand add(pj/coord :flip), where stacking works.Numeric/temporal-position bars (
pj/lay-barwith both axes numeric or temporal) draw one bar per row at its x position; grouped bars (via:color) overlap rather than dodge, and:position :stackis not applied. Pre-aggregate or jitter positions to separate groups.Value bars (
pj/lay-barwith a value column) cannot use a log scale on the value axis: a bar rests on a zero baseline, and a log scale has no zero. Plotje raises a clear error pointing to the alternatives – a linear scale, or a baseline-free mark (pj/lay-point,pj/lay-line,pj/lay-lollipop). Count bars and histograms do support a log axis.Stack order in
pj/lay-areaandpj/lay-bar(with:position :stack) follows the sort order of the:colorcolumn. There is no:stack-order/:color-orderoption yet, so forcing a specific bottom-to-top order requires prefixing category labels with sort-stable ordinal characters ("01: ...","02: ..."), which leaks into the legend.Annotations are silently skipped under
(pj/coord :polar). A polar rule would need to render as a circle (fixed radius) or spoke (fixed angle); those shapes are not implemented. Use Cartesian or flip coords for annotated plots.Large scatters produce large SVGs (~220 bytes/point). For >10k points, use
:format :bufimgfor raster output.LOESS with confidence bands is O(n^2); subsample above ~5k rows.
Scales
One pose reads an aesthetic through one scale. Two layers asking for different scales on
:size,:alpha,:coloror:fillare refused rather than drawn, since a plot has one legend per aesthetic. Two layers naming different scales for:xor:yare refused as well, because a panel has one of each axis. Workaround in every case: put the layers in separate poses withpj/arrange, where each cell has its own scales and its own legend.Facet panels share their scale types.
{:scales :free}gives each panel its own domain, but there is no equivalent for giving one panel a log axis and another a linear one. Workaround:pj/arrangeagain.A
:sizeor:alphamark is scaled against its own panel, while the legend is scaled against the whole plot. Under faceting, two panels whose values cover different intervals are drawn alike: a panel whose values run from 1 to 3 gets the same three radii as one whose values run from 4 to 10, and the legend matches neither. Workaround: set the domain explicitly, as in(pj/scale pose :size {:domain [1 10]}), so every panel uses the same one.:alphaand a numeric:colortake the same workaround.
Options and Configuration
:panel-sizeis a legacy configuration key from the pre-total-first layout. It now emits a deprecation warning and is ignored. Use:panel-width/:panel-height(which set the panel size directly).The
:widthkey on apj/planresult preserves the user’s original request even when:panel-widthpins the real size – inspect:total-width/:total-heightfor the rendered canvas.pj/plancalled on a plan or on a hiccup value now throws a clear error. Callpj/planonly on poses.
Mixing Keyword and String Column References
- Mapping the same column with a keyword in one place and a string in another (e.g.
(pj/pose ds {:color :group})then(pj/lay-point :x :y {:color "group"})) is not normalized: the scope hierarchy treats them as different keys. The mismatch is reported rather than drawn – the spelling that does not match a column name is looked up, found missing, and named alongside the columns the data does carry. The exception is a string that happens to name something the aesthetic can draw:"red"on:coloris that color, so the mapping quietly stops grouping instead. Workaround: pick one form (keyword or string) and use it consistently within a pose.
Integer Column Names
A dataset built without column names gets integer ones, and Plotje reads them wherever a mapping is written:
(pj/lay-point ds {:x 0 :y 1}),(pj/lay-point ds 0 1)and(pj/pose ds {:x 0 :y 1})all plot those two columns, and the derived axis titles read as the names.What remains is that a number has two possible readings on
:xand:y, and the data decides between them: a number the data carries as a column name is that column, and any other number is a value to place a mark at. So the same code can change meaning on a dataset whose column names differ. Where that matters, write the mapping out in full –{:x {:column 0}}and{:x {:value 0}}are each unambiguous – or rename with(tc/rename-columns ds [:x :y])as shown in the Datasets chapter.The appearance aesthetics work the same way.
{:size 1}reads column 1 where the data has one, and is a radius where it does not;{:size {:value 1}}insists on the radius.
ggplot2 Features Not Yet Implemented
The
:fillaesthetic is currently consumed only bylay-tile(and the:bin2doutput beneathlay-density-2d). On filled marks likelay-bar,lay-area, andlay-violin,:colorpaints the interior; there is no separate stroke aesthetic.The
:linetypeaesthetic (ggplot2’saes(linetype=...)for solid vs. dashed lines) is not implemented. Workaround: encode the same distinction via:colorinstead.A layer’s own
:datais not split bypj/facet. The pose’s data is divided among the panels, but a layer carrying its own dataset is drawn whole in every panel, whether or not that dataset has the facet column. ggplot2 divides a layer’s data the same way it divides the plot’s, sogeom_hline(data = means, aes(yintercept = m))puts one line in each panel where Plotje puts all of them in each. Nothing errors; the picture simply shows every group’s mark in every panel. Workaround: build the panels withpj/arrangerather thanpj/facet, so each cell is its own pose with its own layer data.No
after_stat()analog. ggplot2 idioms likegeom_bar(aes(label=after_stat(count)))andgeom_histogram(aes(y=after_stat(density)))reference computed stat values inside aesthetic mappings; Plotje requires a pre-computed column. Workaround: aggregate the data first viatc/group-by+tc/aggregate, then map the count or density column directly.Theme support is shallow versus ggplot2: today the theme map carries
:bg,:grid,:font-size, and a small handful of other keys. Named theme presets (theme_minimal,theme_bw,theme_classic), panel borders, strip text styling, andlegend.positionby coordinate are not yet exposed. Rotating the x-axis tick labels is available, but as the:x-tick-angleplot option rather than through the theme – see Customization.Per-layer
data,guides()for per-aesthetic legend control,scale_*_sqrt/reverse/date. All tracked in the backlog. The axis types are:linear,:logand:categorical; a square-root axis is not among them. (pj/scale :size {:by :sqrt}is a different setting – it says how a size spreads across the radii, not how an axis is transformed.)