All notable changes to this project will be documented in this file. This change log follows the conventions of keepachangelog.com.
A mapping may now be written out in full, saying which column or value it takes and whether to read it through a scale -- {:size {:column :weight :scale :log}}. A scale is written in the mapping it belongs to, and a scale set higher up carries down to the layers below it. Settings such as an axis title, a palette or a tick spacing can be written either in a scale or as a plot option of the same name, and the one written closest to the layer wins.
Three chapters are new to the book: Specifying Aesthetics, Column Types and Scales.
The names below no longer work, and every place one can be written reports it: pj/options, pj/scale, pj/with-config, pj/set-config!, plotje.edn and the :config option.
| Gone | Write instead |
|---|---|
:palette | :color-values, or :values in a :color scale |
:color-scale | :color-range, or :range in a :color or :fill scale |
:tick-spacing-x, :tick-spacing-y | :x-tick-spacing, :y-tick-spacing, or :tick-spacing in an axis scale |
:labels in a scale | :tick-labels |
:x-scale, :y-scale, :size-scale and the rest in a pose's :opts | pj/scale, which now writes the mapping |
pj/scale on :group | scale :color or :shape instead |
:size column. A value's place in its domain is square-rooted before it becomes a radius, matching ggplot2's scale_size. {:by :linear} gives the previous behaviour, ggplot2's scale_radius, and {:by :area} with :from-zero gives scale_size_area.:alpha column. The default range is 0.1 to 1.0, matching ggplot2. {:range [0.2 1.0]} restores the previous one.:color column with pj/scale. A scale spec changes only what it names and leaves the gradient alone. The gradient is set with :range; a map there has to name at least one of :low, :mid or :high.:size or :alpha column whose values are all equal. Every mark is drawn at the middle of the domain, read through the scale: radius 6.243 by default.:from-zero on a column holding zero or negative values. Distance from zero is what counts, so -5 and 5 draw the same size.:color or :fill given a :domain. The list sets the order of the categories, and the legend and the colours follow it.:label and the matching :x-label or :y-label. The :label in the scale wins.{:color {:column :variety :scale true}}, {:color {:column :hex :scale false}}, {:color {:value "blue" :scale false}}, {:color {:value "Model A" :scale true}}.{:size {:column :r :scale false}} uses the column's numbers as radii and {:color {:column :hex :scale false}} uses its strings as colours -- ggplot2's scale_size_identity() and scale_colour_identity(). Neither gets a legend. A single value can go the other way: {:color {:value "Model A" :scale true}} treats it as data, which is how a layer is labelled as a named series.:x and :y take the full form too: {:x {:column 0}} reads column 0, and {:x {:value 0}} places every mark at zero. :scale false on an axis places marks by drawing units from the top left of the panel background instead of by data value.{:color :red} reads a :red column if the data has one, and otherwise draws red.pj/scale and a mapping's :scale take the same forms: true, false, a scale type, or a whole spec map. A scale set higher up carries down, key by key, and the setting closest to the layer wins -- so a pose that sets a range and a layer that sets a type give a plot with both.pj/scale :size takes :range, :by and :from-zero; :alpha takes :range and :from-zero. :range is what the aesthetic spans: on :size that is a radius in drawing units, [2 8] by default.:domain now works on :size, :alpha and :color (issue #39). A value outside a numeric domain is drawn at the nearer end instead of being dropped. The column's type decides how the domain is read, on every aesthetic that takes one: against a continuous column it replaces the interval, and against a categorical one it supplies the order of the categories, however many are listed. A category left out of the domain is drawn last, with a warning. On :fill a :domain moves the marks and not the legend, so the two disagree -- issue #43.pj/arrange, where each cell has its own scales and legend.pj/scale and in a mapping, and so is giving one a value it cannot carry out -- a marker symbol that does not exist, :tick-labels without :breaks. :x-end, :x-min, :x-max, :y-min, :y-max and :text take no scale at all, like :group.pj/aesthetic-scales lists what each aesthetic's scale accepts, alongside pj/config-key-docs, pj/plot-option-docs and pj/layer-option-docs.Many scale settings can be written in two places: in the scale itself, or as a plot option that applies to the whole plot. The plot option is named after the aesthetic and the setting, joined by a hyphen -- :label on :x is :x-label, :values on :color is :color-values -- and the setting written closest to the layer wins. Not every setting has a plot option, but where one exists this is its name.
:tick-labels, paired with :breaks; :label sets the axis title. Using one where the other belongs is now an error.:n-ticks and :tick-spacing both control how many ticks a numeric axis draws, and it uses whichever you set. A categorical axis draws a tick per category, and :n-ticks thins them.:color and :fill take their colours from their own scale: :values is the palette for a categorical column, :range the gradient for a numeric one, and :midpoint the value drawn at the middle of that gradient. Each has a matching plot option.{:color {:column :party :scale {:values {"rep" "red" "dem" "blue"}}}}.:label sets the title of whatever explains a scale -- the axis for :x and :y, the legend for the rest -- and every aesthetic with a scale takes it.(layer-type/register! :bubble {:mark :bubble :varies {:size :radius} ...}). What it draws them as also decides how the area grows, so :by :area means the same thing on a mark drawing a radius and one drawing a width. Of the built-in layer types only :point varies an aesthetic, so this mainly matters when writing an extension.layer-type/channel-magnitude-fn gives a mark's renderer the function its legend is built from, and layer-type/quantities lists what a mark can draw an aesthetic as.:size or :alpha column gets no legend when no mark on the plot varies it, and Plotje says so.pj/scale writes the mapping rather than the pose's options. pj/coord and pj/facet still write :opts.:magnitude rather than :radius, and the legend carries :quantity and :swatch. The swatch matches what the mark draws: circles for a radius, squares for an opacity.<aesthetic>-scale.nil, or neither -- are reported at the pj/pose or lay-* call instead of later.:scale false and then setting an option for that scale is an error, since nothing would read it. :scale false on an axis is also an error on marks that cannot use it, and under :coord :flip and :coord :polar.:size, :alpha or :fill a categorical column reports which aesthetic and which column. :fill and :group report a value that names no column, and a hex colour missing its # is reported on annotations as well as layers.plotje.edn, pj/set-config!, pj/with-config and the :config option. pj/options already refused one, so a name this release retired went unnoticed only through the configuration path, and a project upgrading from 0.8.1 reverted to the default palette in silence. :strict turns the report into an error, as it does for options.(pj/options {:color-range :viridis}) beside (pj/scale pose :color :log).:size-label fit the canvas at any range.[0 1] domain the y axis has always used. An axis that no layer gives a data meaning draws no ticks and takes no label.(pj/lay-rule-h pose {:y-intercept 2 :color :red}) draws red.:domain-padding is read through the configuration chain, so (pj/options {:domain-padding 0.0}) and pj/with-config both reach it.pj/lay accepts the layer-type map from pj/layer-type-lookup as well as the keyword.pj/config-key-docs describes every form :color-range accepts.run_tests.sh, release.sh and snapshot.sh find bash through #!/usr/bin/env bash, so they run on distributions that do not keep it at /bin/bash, such as NixOS. (PR #35) - thanks, @otfromLocale/ROOT, so a plot draws the same numbers whatever machine renders it.:decimal-separator draws the decimal point as whatever string you name: (pj/options {:thousands-separator "." :decimal-separator ","}) writes 1.234,5. Off by default.123456, 0.1, 2.5.0.001, 0.01, 0.1.pj/save honors every plot option pj/plot honors, in SVG and PNG -- :title-font-size, :label-font-size, :thousands-separator, :grid-stroke-width and :annotation-stroke among them.:tooltip.:title-font-size, and its shared legend honors the configuration its cells honor, :thousands-separator among it.:strip-font-size wherever they are drawn, in a facet and in a grid composite alike. The default is 11.pj/to-data reads a categorical axis, answering with the category whose band holds the position, and nil outside every band.pj/to-drawing refuses a value a categorical axis has no position for -- a category it does not carry, or a fractional place such as 2.5 -- naming the value and the categories it could have been.:coord :flip, pj/to-data's dataset arity reads a categorical x back as its categories. Both functions take and answer in data order under a flip.(pj/lay-point data {:x :height :y :weight}) reads the mapping from its options map on a dataset of four or more columns. A mapping given only partly there, such as {:y :weight}, still takes its other axis from the inferred one.This release mostly follows the label-positioning topic thread. Many thanks to @behrica, @timothypratley, @phronmophobic, and @generateme for the fruitful work over that thread. See the new Placing Marks documentation chapter for the main fruits of this release.
pj/frames reports where a plot's panels sit on the canvas: per panel, the :panel-box and :drawing-area rectangles in drawing units, plus the :canvas once for the plot. A composite reports every cell in canvas coordinates. Plain data throughout. - thanks, @timothypratleypj/to-drawing and pj/to-data map between data space and drawing space for one panel of pj/frames. Both take a single x and y, or a dataset with :x and :y columns, and answer in kind. pj/to-data throws under :polar, which has no inverse; a panel's :invertible? says which case it is.:offset-x and :offset-y shift a layer by a number of drawing units, after the scales run -- the distance a label needs to clear its point. A nudge cannot: it shifts by a data amount, and throws on a categorical axis. Every layer type takes them, and a numeric domain widens to keep an offset label whole. - thanks, @behrica:x and :y may be given as a value, not only as a column. Both as values draws one mark: (pj/lay-text pose {:x 6.5 :y 3.5 :text "mean"}). One value beside a column repeats for every row, so {:x 33 :y :revenue} labels each row at one fixed x. The value counts toward the axis domains, as ggplot2's annotate() does. - thanks, @behrica:in names the space a layer's :x and :y are in: :data (default) or :drawing-area, which measures drawing units from the top left of the panel background. (pj/lay-text pose {:in :drawing-area :x 12 :y 12 :text "n = 150"}) puts a note in the corner without touching the domains.pj/lay-rule-* and pj/lay-band-* warn that they do not accept :in, instead of taking it and doing nothing. They are placed from data values only.:group no longer discards a fixed :color. (pj/lay-line data :x :y {:group :country :color "#d0d0d0"}) draws one line per country, all in that grey; before, each line took a different palette color.{:tooltip true} and {:brush true} work in the browser again. Since 0.7.0 the Scittle scripts reached the page with " where they wrote ", and a script element's content is raw text in HTML, so nothing decoded them back and neither interaction ran.:width and :height, which is a screen pixel only at the plot's natural size on a standard-resolution display.4-beta23 -> 4-beta25, brought in by Plotje: pj/plot asks for :kind/hiccup2, which 4-beta23 does not define.(pj/options {:legend-position :top}) draws its legend inside the image, instead of above the top edge where nothing is visible.:not-applicable labels a tick, legend entry or facet strip as not applicable. String categories are untouched. - thanks, @timothypratleyCost-Benefit Ratio, not Cost Benefit Ratio. Keyword and symbol names are unchanged. - thanks, @timothypratleypj/lay-* arity rejects a non-column position, as pj/pose already did. Breaking: (pj/lay-point data 0 1 {}) no longer plots integer column names -- rename them first with (tc/rename-columns ds [:x :y]).pj/scale is never widened. (Closes #18) - thanks, @behrica:fit-text-domain (default true) turns that widening off.:shape produces a legend. One column driving both :color and :shape gives one merged legend, as in ggplot2; two different columns give two legends. (Closes #4) - thanks, @timothypratleypj/shape-symbols, the marker symbols a categorical :shape mapping draws with, in assignment order.:circle, :square, :triangle, :diamond, :triangle-down, :plus, :cross. More categories than symbols repeats one, which now warns.pj/scale reads :shape: :domain sets the category and legend order, and new :values supplies the symbols -- (pj/scale pose :shape {:domain ["gold" "silver"] :values [:diamond :cross]}). An undrawable symbol is rejected, as is :values on any other channel.:shape-label, titling the shape legend as :color-label and :size-label do for theirs. Naming one half of a merged legend splits it in two.pj/svg-summary no longer counts legend symbols as plot data.{:position :dodge}. (Closes #13) - thanks, @behricapj/lay-text and pj/lay-label accept {:stat :count}, labelling each bar of a counting pj/lay-bar: (-> data (pj/lay-bar :species) (pj/lay-label {:stat :count})). pj/lay-point and pj/lay-line plot those counts too. (Closes #14) - thanks, @behrica:x-tick-angle rotates the tick labels in PNG output, not only in SVG.:y-label renders in full in PNG output, where it was cut off after about six characters -- the missing rotation above, not the upstream Membrane bug it was listed as.:label mark is gone; :text and :label are two layer types drawn by the same :text mark, so a label layer reports :mark :text in a plan. pj/lay-label and (pj/lay :label ...) are unchanged. Marks drop from 22 to 21; the 25 layer types are unchanged.:defaults map on the registry entry, which the call site overrides: :label is :text with {:box true}.:box true gives a white rounded panel with a thin border, and a map sets its properties -- {:box {:corner-radius 8}}, or 0 for square corners. (Closes #19, with :thousands-separator below; the arrows and out-of-panel text in that issue's image are #17 and #18) - thanks, @behricageom_label), where it was square.pj/svg-summary reports label background boxes as :label-boxes, one per label, instead of counting them as :tiles.pj/lay-text and pj/lay-label accept :font-weight (:normal or :bold) and :font-style (:normal or :italic), independent and combining, in SVG and PNG. A numeric CSS weight such as 700, or CSS :oblique, is rejected -- Java2D draws neither. (Closes #21) - thanks, @behricapj/svg-summary reports :bold-texts and :italic-texts.clipPath ids are derived from the clip region's geometry instead of a counter shared across the whole JVM, so rendering the same plot twice produces the same bytes.:thousands-separator too, and a whole-valued legend number drops its trailing .0 -- a size legend for a count reads 100,000, not 100,000.0.pj/layer-option-docs describes every layer option. Fourteen were missing, among them :x, :y, :data, :mark and :stat.pj/scale :values on a channel other than :shape now names :palette as the way to choose the colors a categorical :color mapping draws with.:thousands-separator groups the digits of large numbers in numeric tick labels and in text taken from a column, so (pj/options {:thousands-separator ","}) draws 462,389. Any string works. Off by default, and never applied to category names, legend entries or facet strips. Part of #19 - thanks, @behrica(pj/lay-bar :x :y {:x-label "sales"}) adds Plot options belong in pj/options: [:x-label]. The pointer works in every direction, and under :strict the same text appears in the thrown exception. - thanks, @timothypratleypj/lay-point docstring called its trailing map "aesthetic opts"; it holds layer options generally.R&D, or a title "Q1 <profit> & loss", was read as markup. pj/plot now asks for :kind/hiccup2. A saved SVG was never affected. (PR #33) - thanks, @timothypratleypj/lay-density on a column spanning 4.3 to 7.9 drew an axis from 2.1 to 10.1 and a pj/lay-rug beside it covered only the middle of the plot. The curve now starts and ends with the rug, and the axis matches what ggplot2's geom_density() produces for the same data. (Closes #23) - thanks, @behricapj/lay-violin and pj/lay-ridgeline estimate through the same kernel density, so they are bounded by their category's values too -- each body now ends where that category's data ends instead of tapering into a long needle past it. This matches ggplot2, which trims violins by default (geom_violin(trim = TRUE)).pj/lay-density, pj/lay-violin and pj/lay-ridgeline accept :trim, choosing which values each group's curve is estimated over, after ggplot2's argument of the same name and with its per-geom defaults. A density is untrimmed: every group is estimated across the whole layer, so grouped curves share one interval and each falls away to nothing rather than being cut off at its own group's extremes. {:trim true} estimates each group over its own values instead. A violin or ridgeline is trimmed, so each body ends at its category's values; {:trim false} extends it by three bandwidths on each side. This changes the appearance of a grouped pj/lay-density, which previously behaved as {:trim true}.nrd0, the bandwidth rule R's density() and ggplot2 use; the previous default smoothed about 18% wider, drawing every curve slightly flatter and broader than the same data in ggplot2. Affects pj/lay-density, pj/lay-violin and pj/lay-ridgeline. Passing an explicit :bandwidth is unchanged.pj/lay-violin and pj/lay-ridgeline keep 80, where the extra points make no visible difference.pj/plot, pj/save, pj/draft, pj/plan, and pj/membrane now accept raw data directly, giving it a default mapping first exactly as pj/pose does. Previously (pj/plot some-dataset) (data not wrapped in pj/pose) rendered a blank figure; it now renders the same inferred default as (pj/plot (pj/pose some-dataset)).pj/infer-mapping: given a pose that carries data but no mapping (the bare leaf pj/->pose produces), it attaches a default position/color mapping from the first 1-3 columns; it is a no-op on any pose that already has a mapping, has layers, is composite, or has 4+ columns. This is the step the terminal shortcuts apply after pj/->pose, exposed so pipeline-minded users can build their own chains ((-> data pj/->pose pj/infer-mapping pj/pose->draft pj/draft->plan)).[1 4 1 5 6] -- is now accepted as plot data and read as a single column named :value (previously only {:column [values]} maps or sequences of row-maps were accepted). Combined with the default-mapping change, (pj/plot [1 4 1 5 6]) renders a histogram.pj/save now returns the written file as a java.io.File carrying :kind/image metadata (instead of the path string), so evaluating a pj/save call in a notebook also displays the saved chart. The file prints as its path and compares equal to a plain java.io.File on the same path, so (str (pj/save ...)) still yields the path string. (PR #29) - thanks, @timothypratleyplotje-plot as a class on svg output (PR #28) - thanks, @timothypratleypj/svg-summary counts square markers. A :square marker draws as a rounded rectangle of radius 0, which fell between the summary's point test and its tile test, so (pj/lay-point :sepal-length :sepal-width {:shape :species}) on a 150-row dataset summarized as 100 marks. Squares now count in :points alongside circles, which also brings their colors and opacities into :colors and :alphas; :sizes still reports only positive radii.pj/lay-density and pj/lay-area accept an opt-in outline on the curve: :stroke (outline color) with optional :stroke-width. The fill still comes from :color, so (pj/lay-density :x {:color "lightblue" :stroke "black"}) draws a light-blue area with a black outline. The outline strokes only the top curve, not the baseline. Without :stroke the appearance is unchanged. (Closes #11) - thanks, @behricapj/lay-line, pj/lay-step, pj/lay-smooth, the reference lines pj/lay-rule-h / pj/lay-rule-v, and a density/area outline accept :stroke-dash, either a named preset (:dashed, :dotted, :solid) or a raw [dash gap ...] pixel pattern ({:stroke-dash [6 3]}). Renders through both the SVG and PNG (Java2D) paths. (Closes #12) - thanks, @behricapj/svg-summary reports :dashed-lines (count of polylines carrying a stroke-dasharray) and :dash-patterns (the distinct stroke-dasharray strings), for asserting that a dashed line, rule, or area outline rendered dashed and with which pattern.pj/lay-value-bar is removed. pj/lay-bar now covers both cases: with x only it counts each category (as before), and with a y column it uses the y value as the bar height (the former pj/lay-value-bar). The stat is inferred from whether a y column is present and is overridable with {:stat :count} or {:stat :identity}. To migrate, replace (pj/lay-value-bar data :x :y) with (pj/lay-bar data :x :y). This also lifts the previous "stacked bars reject pre-aggregated counts" limitation -- pj/lay-bar with {:position :stack} and a y column now stacks pre-computed values. - thanks, @timothypratleypj/lay-bar value bars now accept the categorical axis on either x or y: (pj/lay-bar :value :category) with a categorical y draws horizontal bars directly, no pj/coord :flip needed (matching how pj/lay-boxplot auto-orients). Plain and dodged horizontal bars are supported; stacked/filled horizontal bars still need (pj/coord :flip).pj/lay-bar with two numeric or temporal axes now draws a bar at each x position -- a numeric-position or time-series bar chart ((pj/lay-bar :month :revenue)), which previously errored. Bar width defaults to 0.9 of the smallest gap between adjacent x values; set it with {:bar-width n}. Grouped numeric bars currently overlap rather than dodge.pj/lay-bar's categorical-x error now points to the {:x-type :categorical} override and (pj/coord :flip), matching the guidance other categorical-axis marks already give.pj/valid-membrane? and pj/explain-membrane validate a membrane against its Malli schema, mirroring the existing pj/valid-plan? / pj/explain-plan pair for plans.pj/options -- notably :theme, but also :palette -- now flow through the explicit pj/draft->membrane and pj/draft->plot steps, not only through the pj/plot / pj/membrane shortcuts. These steps default their options to the draft's own options (any options passed explicitly override per key), so a theme set before drafting is no longer dropped at the membrane stage. (Closes #20) - thanks, @behricapj/lay-line reference line drawn beyond a narrowed pj/scale domain, say -- is masked at the panel edge instead of painting across neighbouring panels in a pj/arrange or facet layout. A narrowed :domain acts as a view window (like ggplot2's coord_cartesian): the data is kept, only the view is bounded. (Closes #16) - thanks, @behricapj/options accepts :x-tick-angle to rotate x-axis tick labels (in degrees; -45 is a common diagonal), so dense or long categorical labels stay readable instead of overlapping. :x-tick-label-pad overrides the vertical space reserved below the panel for the angled labels. The rotation flows through pj/save (SVG and PNG) as well as the notebook pj/plot path. Long labels can still run past the left plot edge (see Known Limitations). (PR #6) - thanks, @tombaryspj/scale accepts :n-ticks on a categorical axis to thin a crowded axis to about that many evenly-spaced tick labels, instead of labelling every category ((pj/scale :x {:n-ticks 8})). An alternative to rotating the labels for dense categorical axes. (PR #25) - thanks, @behricapj/scale :breaks and :labels now work on a categorical axis, not just numeric ones. On a discrete axis :breaks selects which categories get a tick (each matched to a category by its displayed label) and :labels relabels them; a break naming no category is dropped with a warning (an error under :strict). Previously the categorical branch ignored both. When both :breaks and :n-ticks are given, explicit :breaks win and no thinning is applied. (Closes #22) - thanks, @behricapj/lay-* call renders on top of the previous one -- instead of being reordered by position type. A pj/lay-text or pj/lay-label added after a bar now reads on top of it rather than being hidden underneath.pj/lay-text and pj/lay-label accept :align-x (:left/:center/:right) and :align-y (:top/:center/:bottom) to set which part of the label sits on the data point -- e.g. :align-x :right places a value label inside a bar's end. Defaults :left/:center preserve the previous placement.:nudge-x/:nudge-y on a categorical axis now raise a clear error pointing to :align-x/:align-y (and :jitter/:position :dodge). Nudge is a data-space shift and applies only to numeric or temporal axes.pj/lay-* with different x/y columns from the existing pose now produces a two-panel composite instead of throwing.pj/lay-* would create a new panel using columns that don't exist in the data, the error now fires at the lay call with a clear message, instead of later during pj/plan or pj/plot.:data but the pose's x/y columns are missing from it, the error now names where the missing column came from and suggests two fixes: rename the column to match, or set a different x/y on the layer.pj/scale :y :log now works on histograms and categorical bar charts. (Closes #5) - thanks, @harold.java.util.Locale/ROOT, so plots render correctly on JVMs whose default locale uses comma as the decimal separator (Czech, German, etc.). (PR #3) - thanks, @tombaryspj/scale accepts :labels paired with :breaks -- render numeric tick positions with custom text (e.g. days of the week 1-7 labelled "Mon"-"Sun" on a tile heatmap). Length must match :breaks; :labels without :breaks throws.PlotjeMembrane record implementing the Membrane UI protocols (IOrigin, IBounds, IChildren), so Plotje plots compose with hand-built Membrane elements. Width and height read via (membrane.ui/width m)/(height m); title rides as :plotje/title. Replaces the prior metadata-tagged-vector contract.pj/membrane? predicateCan you improve this documentation?Edit on GitHub
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 |