Releases: r-vellum/vellumplot
Release list
vellumplot 0.9.0
First tagged release of vellumplot — a declarative, pipe-first grammar of graphics that compiles an inspectable, serializable spec into a vellum vector scene and renders it. It runs without a graphics device (vellum measures text itself), and the compiled plot is the scene, so the static figure and the interactive widget cannot drift.
Things that are a little unusual to find together in one R grammar:
- One spec → PNG / SVG / PDF from one solved layout, and the PDF is tagged (a real structure tree + alt text a screen reader can navigate).
- Checkable accessibility —
plot_lint()flags tiny text, low contrast, and single-level legends; render through a colour-vision-deficiency simulation (render(cvd = )) to preview a colour-blind reader's view. - Interactive HTML widgets (vellumwidget) that reuse the marks you declared — no
*_interactive()twins, no Vega/plotly re-draw. - Vector-preserving effects and regions — real Gaussian-blur
glow()/shadow()(including on text), and Venn/Euler diagrams and merged choropleths computed as boolean geometry so they stay crisp in PDF. - Texture as well as hue —
pattern_*()hatch fills that survive greyscale print and colour-vision deficiency, on every backend. - Keyframe animation —
transition_states()/animate()/anim_save()to GIF, APNG, or a resolution-independent animated SVG (honoursprefers-reduced-motion). - A native hand-drawn mode (
theme_sketch()), exact and identical across PNG/SVG/PDF.
Also included: the full mark/scale/facet/coord grammar, statistical marks, spatial (sf / coord_sf) and network (igraph, sankey, hierarchies, chord) plots, multi-page PDF (pdf_pages()) and parallel batch (render_all()) output, repelled labels, SVG icon markers, and multi-plot composition.
Install:
pak::pak("r-vellum/vellumplot")Needs the vellum backend (a Rust toolchain; pak pulls it in).
vellumplot 0.8.0
-
Flow maps:
mark_flow_map(). A one-to-many flow map on avgraph()plot:
a singlerootfans out to every destination along smooth, merging branches
whose width tracks the flow volume (the Minard / migration-map idiom).
type = "spiral"(default) builds an angle-restricted spiral tree from the
\pkg{edgebundle} package;type = "steiner"builds an approximate Steiner tree
(additionally needs \pkg{interp}). Edgeweightdrives the flow, which is
mapped ontowidth_range. Needs the suggested \pkg{edgebundle} package. -
Edge bundling:
mark_edge_bundle(). A drop-in alternative tomark_edges()
that routes a graph's edges as bundled curves instead of straight lines, so a
dense node-link hairball collapses into a few legible trunks.typeselects the
algorithm ("force","divided","stub","path","hammer","mingle")
andparamspasses tuning through to the \pkg{edgebundle} package, which
computes the geometry; vellumplot draws the returned paths with the usual edge
aesthetics (faint by default so overlapping trunks read as density). Needs the
suggested \pkg{edgebundle} package (>= 1.0.0). -
Chord diagrams:
vchord(). Wraps a weighted flow (afrom/to/value
list or a square flow matrix) onto a circle -- one arc per node, one ribbon per
flow through the centre. Directed: each node's arc splits into an outgoing then
an incoming block, soa -> bandb -> aare distinct ribbons and self-flows
loop within a node's sector.directionshows a ribbon's direction by fading
it from source to target ("gradient", default), stopping it short of the
target ("gap"), or"both".sort,gap, andlink_colorecho
vsankey();mark_chord()is the exported layer. -
Removed the unused
max_overlapsargument frommark_text(),
mark_label(), andmark_node_text(). It was reserved for a never-implemented
overlap cap and did nothing; drop it from any call. -
More robust handling of degenerate and extreme inputs. A single distinct
value on an axis undercoord_fixed()/coord_sf()no longer collapses the
panel to nothing; a map centred on a pole no longer gets a runaway aspect
ratio; a circle-pack of collinear equal-value leaves no longer producesNaN
coordinates; and a pathologically deep hierarchy or dendrogram aborts with a
clear "too deep" message instead of overflowing the call stack. -
mark_line(window = )requires a positive integerk. A zero, negative,
or fractional window size now errors clearly instead of silently producing a
wrong (or empty) result. -
A computed-constant
if_falseincondition()is carried into the
interaction model. A negative literal (e.g.-0.5) or a computed constant
(e.g.grey(0.5)) was previously dropped; only genuine per-row column
references are now excluded (and carried per element instead). -
A faceted layer whose own data lacks the facet variable still draws on every
panel, but a genuine evaluation error there is no longer silently swallowed
into that fallback. -
mark_point(auto = TRUE)no longer datashades under a warped coordinate
system. Undercoord_polar()/coord_trans()the >50000-row datashade
fallback — which bins in linear data space — would rasterise into the wrong
place; it is now skipped and the vector path draws, matching the documented
behaviour and the otherautomarks (line/step/segment/edges). -
A sankey column with too many nodes errors clearly. When a column has more
nodes than its inter-node gaps can fit, the node/ribbon heights previously went
negative and spilled outside the panel;vsankey()/mark_sankey()now abort
with a message pointing atnode_gap. -
Aggregating stats drop missing summaries consistently, with a warning. For
a value-summarising bar (mark_bar(y = , fun = )and friends), a category
whose summary isNA(an empty group, or a summary of data containingNA
when the function does not remove it) is now dropped with a warning on both
the grouped and ungrouped paths, which previously treatedNAdifferently. A
layer mapping bothcolorandfillalso keeps both aligned after
aggregation (fillcould previously be left at the wrong length). -
Clearer errors for malformed scale bounds.
scale_x_continuous(),
scale_y_continuous(),scale_size(),scale_size_area(),scale_alpha(),
scale_edge_width(), andscale_edge_alpha()now reject alimits(or output
range) that is not a length-2 vector — and, for the size/alpha/edge scales, a
non-numeric one — with a clear message, instead of failing later with a cryptic
low-level error (e.g.'length = 2' in coercion to 'logical(1)').scale_size_area()
also validatesmax_size. -
add_marginal()rejects a non-numeric mapping. A factorx/ypreviously
slipped through and the marginal was computed over the integer level codes
rather than the data; it now errors clearly, as a character column already did. -
Stricter validation of interactivity and layout arguments.
select_point()/select_interval()reject a non-logicaltoggle/empty(it
was silently coerced toFALSE);vgraph()validateswidth/heightlike
vsankey()/vhierarchy(); andarea()rejects a non-numeric or non-integer
cell index instead of aborting with an opaque "missing value where TRUE/FALSE
needed". -
mark_text()honourshjust/vjustpassed as a variable. They were only
read as constant params, so a value routed through a variable (as
vdendrogram()does per direction) silently fell back to centred, letting leaf
labels run over the edges..emit_text()now reads them value-first, like
angle.vdendrogram()leaf labels also get a larger default gap. -
Dendrograms and unrooted trees.
vgraph()now accepts a base
hclust/dendrogramand gains a height-aware"dendrogram"layout plus an
"unrooted"layout (viagraphlayouts::layout_as_tree_unrooted(), needs
graphlayouts >= 1.2.5).mark_edges(routing = "elbow")gainedelbow_at/
elbow_axisso the elbow can be the dendrogram bracket (corner at the
parent's level) rather than only the midpoint S-bend.vdendrogram()is a
one-line preset: bracket edges, leaf labels,direction, andkto cut the
tree and colour clusters. Also fixesmark_node_text(label = )so an explicit
labelmapping overrides the default vertex name (it was previously ignored). -
vhierarchy()respectsscale_fill_*(). By default nodes are still
coloured by their depth-1 branch and lightened with depth, but the branch is
now an ordinary discrete fill scale, soscale_fill_manual()/
scale_fill_discrete()recolour the branches and alightenargument controls
the depth fade (0= flat colour per branch). Mappingfillto a node column
instead colours every node by that variable (discrete or continuous, with the
matchingscale_fill_*()), with no depth fade.
vellumplot 0.7.0
Breaking
- New
vhierarchy()replacesvsunburst(). One constructor draws a parent-list tree four ways viatype =:"sunburst"(default),"icicle","treemap","circlepack".vsunburst()/mark_sunburst()are removed; usevhierarchy(..., type = "sunburst")/mark_hierarchy().
Also in this release
- Sunbursts, polar pies, and self-loops render un-mirrored (requires vellum >= 0.5.1).
select_point(group_by=)links whole groups.- Declarative interactivity:
select_point()/select_interval()as part of the plot spec.
See NEWS.md for the full list.
vellumplot 0.6.0
-
Sankey styling options.
vsankey()/mark_sankey()gainshow_values
(append each node's value to its label),flow_color("source","target",
or"gradient"— a source-to-target colour fade per ribbon), and
node_width/node_gapto tune the node rectangles. -
Sankey crossing minimisation.
vsankey()now orders the nodes within each
column with the Sugiyama barycenter heuristic to minimise ribbon crossings
(previously first-appearance order, which left many avoidable crossings). The
reordering is deterministic and pure R. A related fix stacks each node's ribbon
slices to meet the node in the same vertical order as the nodes they connect to,
removing the remaining crossings within a fan of ribbons. -
Sunburst rendering fixes.
vsunburst()now colours wedges by their
top-level branch (each branch a distinct hue, lightened with depth) instead of
a single colour per ring — sibling branches were previously indistinguishable.
It also starts the first wedge at twelve o'clock and winds clockwise, matching
the package's pies/roses (coord_polar) rather than starting at three o'clock
counter-clockwise. -
Sunburst / radial hierarchies:
vsunburst(). A new plot type for
part-of-whole hierarchies, from a parent list —id,parent(NAat the
root), andvalue(leaf values; internal nodes sum their children). Depth maps
to a ring and each node's angular span is its share of its parent's, drawn as
one batchedsector_grobin an aspect-locked, axis-free square panel (mirroring
vsankey()/vgraph()).inner_radiusopens a central hole (a donut/ring
sunburst); nodes are coloured by depth.mark_sunburst()is the exported layer.
Seevignette("flows-and-hierarchies"). -
Sankey / flow diagrams:
vsankey(). A new plot type for layered flows,
built from a flow list — one row per flow withfrom,to, andvalue(the
ribbon width). Nodes are the union offrom/to; a node that is both a source
and a target makes the diagram multi-stage.vsankey(data, from, to, value)
returns a ready, axis-free plot (mirroringvgraph());mark_sankey()is the
exported layer it adds. The layout is computed R-side (longest-path layering,
value-proportional node heights and ribbon widths, filled Bézier ribbons) and is
deterministic. Flows must form a DAG; nodes are coloured from the qualitative
palette. Seevignette("flows-and-hierarchies"). -
Uncertainty marks:
mark_halfeye()andmark_interval(). ggdist-style
slab + interval marks for sample/posterior input (manyyrows per categorical
x).mark_halfeye()draws a one-sided density slab with a point-interval at
its base — the median (orpoint = "mean"), a thick inner and thin outer
equal-tailed quantile interval at the.widthprobabilities (default
c(0.66, 0.95));mark_interval()is the point-interval alone. A natural fit
for visualising posterior draws (e.g. from \pkg{brms}). -
coord_radial()andscale_size_area().coord_radial()is a fuller
polar system (ggplot2 3.5's name): besidestheta/start/directionit takes
endto sweep only a partial arc (e.g.start = -pi/2, end = pi/2for a
semicircular gauge) andinner_radiusfor a donut hole; with the defaults
it matchescoord_polar().scale_size_area()maps a value to the marker's
area (value0→ size0), the perceptually honest default for bubble
charts, withmax_sizethe size of the largest value. -
Sankey labels stay on-panel.
vsankey()now reserves horizontal margin for
node labels, so the source column's labels (drawn to their left) and the
terminal column's (drawn to their right) no longer clip at the panel edge. -
Robustness of the new marks.
vsunburst()/mark_sunburst()now reject a
missing or negative leafvaluewith a clear message (instead of a cryptic
downstream error), andmark_sunburst()validatesinner_radiuslike
vsunburst()andcoord_radial()do.mark_halfeye()/mark_interval()skip a
category with fewer than two finite observations (with a warning) rather than
drawing an empty interval, and reject awidth =argument (a likely typo for
.width) that was previously ignored silently. -
Consistent argument names for the hole radius / ridge height. The
central-hole fraction is now spelledinner_radiuseverywhere it appears:
coord_radial(),vsunburst()/mark_sunburst(), andmark_donut()(was
hole).mark_ridgeline()'s overlap control is nowheight(wasscale,
which collided withmark_halfeye(scale=), a different quantity). -
Consistent
sketch/blendpassthrough.mark_ecdf(),mark_contour(),
mark_contour_filled(),mark_dotplot(),mark_qq(), andmark_qq_line()now
accept a per-layersketch =(their emitters already honoured it), and
mark_pie()/mark_donut()acceptblend =, matching the rest of the mark
surface. -
Parameterised position adjustments. New
position_nudge(),
position_jitter(),position_dodge(),position_dodge2(), and
position_jitterdodge()give a mark'spositiontunable parameters (a bare
string like"dodge"still works with the defaults). Adds three adjustments:
nudge(shift every element by a constant in data units),dodge2(dodge by
the groups actually present at each x, filling the band with apaddinggap —
so ragged groupings stay centred), andjitterdodge(jitter points within
their dodged slot).position_jitter(width=, height=, seed=)and
position_dodge(width=)expose the previously-fixed jitter/dodge extents. A
plot using the old string positions is unchanged. -
Label repulsion:
mark_text(repel = TRUE)/mark_label(repel = TRUE).
Overlapping text labels are moved apart with a force-directed layout
(ggrepel-style), each keeping a thin leader line back to its point. Because the
plot size is fixed on the spec, the repulsion is resolved exactly against the
true rendered panel — a two-pass compile that reads the panel's device geometry
fromvellum::scene_model(), relaxes the label boxes in pixel space, and maps
the result back — so it needs no approximation and novellumchange, and is
deterministic underseed. Tunable viabox_padding,point_padding,
min_segment_length, andseed. Limited to a single cartesian panel for now
(facets / composition / polar error clearly). -
mark_line(window = ): rolling / cumulative / offset transforms. A line can
now transform itsyper group (over rows ordered byx) before drawing —
movingmean/sum/median/min/maxover a window ofk, running
cumsum/cummean/cummax/cummin,lag/leadshifts, orrank. Pass an op
name (window = "mean") or a list (window = list(op = "mean", k = 7, align = "right", partial = TRUE));alignis trailing/leading/centred andpartial
fills the edges from the shorter window so the line stays continuous. A plot
withoutwindowis unchanged. -
Diverging colour scales:
scale_color_gradient2()/scale_fill_gradient2().
A three-point ramp (low--mid--high) centred onmidpoint(default0),
rescaled about the midpoint (scales::rescale_mid) so the neutral colour sits
on the chosen value and each side spans as far as the data reaches — the correct
scale for signed / anomaly data. A diverging continuous colorbar also reports
midpoint+divergingin itscolorbardescriptor, so an interactive host can
centre a value-range filter on the neutral value. -
symlogposition transform.scale_x_continuous(trans = "symlog")(and
y) adds a symmetric-log axis: linear through zero, logarithmic in the tails
(sign(x) · log10(1 + |x|)), so signed data spanning several orders of magnitude
— including zero and negatives, whichlog10cannot show — reads on one axis.
Breaks sit at zero and signed powers of ten. The transform name flows into the
panelscalesdescriptor like the other transforms. -
New group-region marks:
mark_ellipse()andmark_hull(). Both enclose a
set of(x, y)points in a single region drawn over a scatter — one region per
group when acolor/fillis mapped.mark_ellipse()draws a covariance
ellipse (type = "t"robust default via \pkg{MASS}, or"norm"/"euclid"),
following ggplot2'sstat_ellipse();mark_hull()draws the convex hull. Both
are unfilled boundaries by default (map/set afillto shade them) and need at
least 3 points per group. The region's boundary trains the position scales, so
an ellipse that bulges past the data is not clipped. -
mark_smooth()gains real smoothing methods. Beyond"lm", the smooth
mark now fits"loess"(local regression,span =),"glm"(with afamily
viamethod.args, e.g. logistic — the fit and its ribbon are back-transformed
from the link scale),"gam"(a penalised smooth, defaulty ~ s(x); needs
\pkg{mgcv}), and"rq"(quantile regression at a singlemethod.args$tau;
needs \pkg{quantreg}, line only, no ribbon). The defaultmethod = "auto"
picksloessfor small groups (< 1000 points) andgamfor large ones, as in
ggplot2. Newformula,span, andmethod.argsarguments;glm/gambands
use a normal interval,lm/loessa t-interval. Previouslymark_smooth()
errored on any method other than"lm". \pkg{mgcv} and \pkg{quantreg} are
Suggests — a gated method errors clearly if its package is absent. -
Data panels are emitted as pannable, gridlines tagged. Cartesian data panels
(includingcoord_flipand a linearcoord_trans) now push apannablevellum
viewport, and gridlines carryrole = "grid". This is inert for static rendering
but lets an interactive host (vellumwidget) pan/zoom a panel's marks while its
clip + axes stay fixed and hide/redraw gridlines — the groundwork for axis-aware
zoom....
vellumplot 0.5.0
-
Rich
md()legend titles no longer clip. A legend built from a rich title
(scale_color_continuous(name = md("Power (hp m^2^)")),labs(color = md(...)))
measured the title as zero width, so the legend reserved no room for it and the
drawn title spilled off the page. Titles are now measured through vellum's rich
text path (vl_strwidth()), reserving the space they actually occupy. -
Secondary axes. Continuous position scales gain a
sec.axisargument fed by
the newsec_axis()/dup_axis(): a second set of ticks and labels on the
opposite edge (top forx, right fory), computed as a 1:1 monotonic
transform of the primary axis — e.g.
scale_x_continuous(sec.axis = sec_axis(~ . * 1.8 + 32, name = "°F"))for a
unit conversion, ordup_axis()to duplicate an axis on a wide plot. The
transform is a formula, function, orscales::transform_*()object. Scoped to
the default Cartesian system with shared facet scales; combining it with
coord_flip()/coord_polar()/coord_trans(), free facet scales, or
add_marginal()raises a clear error, and it is not drawn inside a plot
composition. A plot without asec.axisis byte-for-byte unchanged. -
Datashading now covers dense lines and large-graph edges.
mark_line(),
mark_step(),mark_segment(), andmark_edges()gain anauto = TRUE
switch (parallel tomark_point(auto = TRUE)): past a row threshold the layer
rasterises into a line- / segment-density field viavellum::datashade_lines()
/vellum::datashade_segments()instead of emitting one vector per element, so
overplotted timeseries stacks and graph "hairballs" render fast and honestly.
The fallback is skipped under a warped coordinate system (coord_polar()/
coord_trans()), which keeps the vector path. (Area / ribbon datashading is not
yet available, pending area-fill support in vellum.) -
mark_datashade(spread = )exposes vellum's post-aggregation spreading:
NULL(default, raw output), a positive integer for a fixed pixel radius
(vellum::spread()), or"auto"for density-adaptive dilation
(vellum::dynspread()). Datashaded lines and segments default to"auto"so
single-pixel marks stay visible. -
Continuous and binned colour scales now interpolate perceptually (Oklab) by
default. A colour ramp built from a plain colour vector (e.g.
scale_color_gradient(low, high)orscale_color_continuous(palette = c(...)))
is blended in the perceptually-uniform Oklab space instead of sRGB, so it no
longer passes through muddy, over-dark midtones or drifts in hue — the ramp and
its legend colourbar read evenly. Designed perceptual palettes (thebatlow
default,hcl.colors()names) are already uniform and are unchanged. Set
options(vellumplot.color.interpolation = "srgb")(or"lab") to restore the
old behaviour. Gradient fills opt in per gradient with
linear_gradient(..., interpolation = "oklab")(passed through to vellum). -
Error bars and line ranges are now interactive.
mark_errorbar()and
mark_linerange()thread a declareddata_id/tooltip/hover_groupthrough to
their drawn segments, so each bar is keyed to its datum — it appears in
scene_model()andplot_provenance()and carries adata-keyin the SVG,
ready for hover/click/select in a widget (a bar's cap segments share the bar's
key).mark_boxplot()already keyed each box by its category; this completes the
statistical marks. A mark with no interactivity declared is unchanged. -
Two more shapes in
scale_shape(). Theshapeaesthetic's default palette
now extends to"triangle_down"(a downward triangle) and"star"(a
five-pointed star) after the original six, so a mappedshapecovers up to eight
levels without an explicit scale, and both are accepted asscale_shape(values=).
Requires the accompanyingvellumdev version. (A constantshape = "star"on a
mark already worked once vellum gained the shape.)
vellumplot 0.4.0
New features
-
Categorical datashading in one call.
mark_datashade()now accepts a mapped
discretecolor/fillaesthetic and shades categorically (datashader's
count_cat): each category is aggregated separately and every cell is coloured by
the count-weighted average of the category hues it holds, opacity by density — so
you see which category dominates where, and where they mix, with a colour
legend. Category hues come from the usual discrete colour scale
(scale_color_*()apply). This replaces the old idiom of stacking one datashade
layer per category withblend = "screen". Newspan/cliparguments clamp the
density range (absolute limits or percentiles) so a few extreme cells don't
flatten the rest. Requires the accompanyingvellumdev version. -
mark_image()draws images at data points. A new mark places a bitmap
image (e.g. a country flag or company logo) at each(x, y), replacing the
usual point marker.srcis a column of local file paths or one constant
path, andsizesets the height in millimetres (the width follows each
image's native aspect ratio). Requires the suggestedmagickpackage (#33). -
coord_trans()— nonlinear display transforms. Warps the display of one
or both position axes after the scale has trained, so gridlines bunch up and
straight lines curve while the axis keeps its original data-value labels (unlike
scale_*(trans=), which rescales the data and relabels in transformed space).
Each ofx/ytakes a transform name ("log10","sqrt","identity") or a
scales::transform_*()object, e.g.coord_trans(y = "log10"). Supports the
common marks (points, lines, areas/ribbons, bars, tiles, smooths, text, …);
segment/interval, boxplot, edges, and raster/datashade marks are not warped yet
and raise a clear error undercoord_trans(). -
mark_violin()andmark_ridgeline()no longer clip at the edges. The
position scales are now trained to include each mark's drawn footprint -- the
full kernel-density support for a violin, and the ridge height (scale) above
the top category for a ridgeline -- so the tallest ridge and the density tails
are no longer cropped by the panel. Explicit axis limits still take precedence. -
Marginal distributions with
add_marginal(). A new plot modifier draws a
density or histogram of the panel'sxvariable along the top edge and of its
yvariable along the right edge, each sharing the scatter's axis so they line
up (the vellumplot analogue ofggExtra::ggMarginal()). Pick the distribution
withtype = "density"/"histogram", the edges withsides("t","r",
"tr"), and the extent withsize;group = TRUEsplits each marginal by a
discretecolor/fillmapping in the matching palette. It readsx/yfrom
the first plain (identity-stat) layer and reuses the existing density/bin
stats, so no axes or legends are duplicated. This version supports a single
panel only (an error is raised with facets, a non-Cartesian coordinate system,
or a locked aspect ratio). -
Positional constants now render. A bare literal on a coordinate channel
(e.g.mark_segment(x = i, y = 0, xend = i, yend = value)for a lollipop
baseline) is now treated as a constant-valued coordinate rather than a style
param, so it populates the layer's values and trains the position scale.
Previouslyy = 0was dropped from scale training, collapsing each segment's
start onto its end (zero-length, invisible), and a segment-only plot errored
with "Every layer needs an x and y encoding". Segment/edge endpoints (xend/
yend) now also extend the axis domain, so a segment-only plot derives its
range from both ends. Affects the positional channelsx,y,xend,
yend,xmin,xmax,ymin,ymax. -
Faster
mark_sf()on large maps. Non-interactive sf layers now batch all
features that share a resolved fill/colour into a single grob per style group
(polygons into oneevenoddpath_grob, lines into one NA-separated
lines_grob), instead of one grob per feature, and the layer's bounding box is
computed in a single linear pass rather than by growing coordinate vectors. On
a 40k-polygon grid this cut scene compilation from ~24s to ~0.8s. Interactive
layers (those declaringdata_id/tooltip/hover_group) are unchanged —
they still emit one keyed grob per feature so every feature stays addressable.
Note: because a batched polygon group is filled with oneevenoddrule, two
same-fill features that overlap would cancel in the overlap region (the same
property a self-overlappingMULTIPOLYGONalready has); disjoint features —
every real choropleth and coverage map — are unaffected.
Bug fixes
-
linetypenow works on stroked marks.mark_segment(),mark_linerange(),
mark_rule(),mark_errorbar(),mark_rug(),mark_edges(), and
mark_contour()(andannotate("segment", ...)) previously ignored
linetypeand always drew solid lines; they now honour it, consistent with
mark_line()(#30). -
annotate("rect", ...)honours infinite bounds. A bound of-Inf/Inf
(e.g.xmin = -Inf, xmax = Inffor a full-width band) now extends the
rectangle to the panel edge, matching ggplot2, instead of silently rendering
nothing (#29). -
Continuous labels no longer group thousands by default. A default
continuous axis or legend now renders 4-digit-plus values without a grouping
separator, so years and IDs read2010instead of2 010(#27). Pass
explicitlabelsto restore grouping. -
More inputs fail fast. A
designlayout area must be a solid rectangle (and
area()requirest <= b,l <= r); a per-row interactivity value
(tooltip/data_id/…) must be length 1 or the data's row count; and
add_marginal()requires a cleanly numeric column. Each now errors clearly
instead of silently mis-rendering. -
Clearer alt text for grid facets. A
facet_grid()plot's automatic alt text
now distinguishes the row and column variables (e.g. "Faceted by rows cyl,
columns am") instead of running them together. -
Compositions can be themed.
theme()now accepts a composition (from
[concat()] / [hconcat()] / [vconcat()]) and styles its figure-level chrome —
the title/subtitle/caption bands, the collected legend, panel spacing, and
tags. Previously a composition's figure chrome always used the default theme.
Each sub-plot still carries its own theme. -
Gradient fills fail clearly on marks that don't support them. A
fill = linear_gradient(...)is painted bymark_area(),mark_ribbon(), and
mark_bar(); on any other mark it now raises a clear error instead of leaking
an undefined paint into the renderer. -
Inputs are validated up front.
theme()now checkspanel.spacing,
plot.margin,aspect.ratio, andaxis.ticks.length(and rejectsNA/Inf
in any numeric setting);vplot()checkswidth/heightthe way it already
checkeddpi; a discreteshape/linetypescale with more categories than
its palette errors instead of silently reusing a glyph; and a binned colour
scale given fewer than two breaks errors instead of crashing in
colorRampPalette(). Valid input is unaffected. -
Legends read consistently. A horizontal (top/bottom) continuous colour
legend now draws theNAkey that the vertical one always did.
guide_legend(reverse = TRUE)now actually flips a continuous colourbar
(previously a silent no-op) and no longer desyncs a binned colour scale's
boundaries from its swatches. A merged colour+shape legend's swatches are now
tagged for interactive highlight/select. Minor gridlines past the outermost
breaks use the local break spacing at each end, so they sit correctly when
breaks are unevenly spaced. -
Numeric facets order numerically. Faceting on a numeric variable now orders
panels1, 2, 10rather than the lexicographic1, 10, 2; multi-variable
facets order by each variable's own type. -
Log/reversed axes. Bars and areas on a
scale_*(trans = "log10")(or
"sqrt") axis draw from the axis floor instead of a degenerate shape (the zero
baseline that maps to-Infis clamped into the trained range). A descending
limit such asylim(hi, lo)on a bar/area is no longer silently un-reversed by
the zero baseline. -
Grouped histogram density.
after_stat(density)/after_stat(prop)on a
grouped histogram or bar now normalizes per group (each group integrates/sums
to 1), matching ggplot2, rather than dividing by the grand total. -
Clearer errors instead of silent surprises or crashes.
coord_trans()
rejects a"reverse"transform (a no-op there — use
scale_*(trans = "reverse")) and rejects bar/area marks on a nonlinearly
transformed axis (a zero baseline has no place on a log display). Histogram /
2-D bin / hexbin stats abort cleanly on all-NAinput instead of a cryptic
seq()error.mark_raster()and z-surface contours reject a grid with a
duplicated cell (previously a silent transparent/NAhole). Composition
auto-tags continue past 26 sub-plots (Z,AA,AB, …) instead ofNA. -
mark_area()now stacks.mark_area(position = ...)was silently ignored;
it gains apositionargument ("stack"default, plus"fill"and
"identity"), so areas sharing anxwith a mappedfill/colorcombine into
a band instead of overlapping from the zero baseline. An area with no fill
mapping is unchanged. -
Mapped aesthetics that were silently dropped now take effect. A mapped
fillonmark_label()colours the label background (previously it always fell
back to white); a mapped or constantalphais honoured per category/tick by
mark_violin(),mark_ridgeline(),mark_contour(),mark_contour_filled()
andmark_rug()(previously collapsed to one valu...
vellumplot 0.3.0
-
Adopted vellum's renamed
vl_*graphics primitives (vl_gpar(),vl_unit(),
vl_viewport()), which no longer mask grid. -
NA legend keys for
size/shape. A mappedsizeorshapeaesthetic
whose data contains missing values now shows an "NA" key (as the colour scales
already did). Also fixes a crash: NA in ashapemapping previously errored
(Unknown point shape: NA) — it now draws as a neutral circle. -
2-D density contours.
mark_contour()draws iso-density contour lines of a
2-D point cloud andmark_contour_filled()fills the bands between them
(coloured by level automatically). By default the field is a kernel density
estimate (viaMASS::kde2d()); map azaesthetic to contour a supplied
surface over a regularx/ygrid instead. Contour tracing uses theisoband
package (bothisobandandMASSare Suggests). -
Binned position scales.
scale_x_binned()/scale_y_binned()cut a
continuous axis into bins — ticks at the bin boundaries, each datum drawn at its
bin centre — reusing the binned-colour classification (style/n, or explicit
breaks).mark_bar()sizes to the bin width. -
Rich and multi-line text labels.
mark_text()now carries rich labels
through to the renderer instead of flattening them: maplabel = md(<expr>)for
a per-datum styled label (bold/italic/super-/subscript/colour), and plain labels
may contain newlines (\n) to wrap onto stacked lines. Requires vellum's
development version. (mark_label()'s background box does not yet support rich
labels.) -
Accessibility (alt text). Every compiled plot is now an accessible image
by default.plot_alt()returns a plot's text alternative — an author-written
string from the newlabs(alt = ), or a prose summary vellumplot generates from the
spec (chart type, x/y/colour/size mappings, observation count, faceting). At the
compile seam the plot title becomes the scene's accessible name and this alt
text becomes its description, sorender_plot()output carriesrole="img"+
<title>/<desc>in SVG and a taggedFigurewithAltin PDF (via vellum).
See the new Accessibility article. Requires vellum's development version
(>= 0.1.1.9000) for the accessible SVG/PDF backend. -
British spelling.
mark_*(colour = )is now honoured as an alias for
color(previously the mapping was silently dropped and no colour scale was
trained). Addedscale_colour_*()aliases for thescale_color_*()family. -
Faithful alt text. The auto-generated description now matches what the plot
actually renders: it honoursscale_*(name = )axis/legend titles, names the
implicit "count" axis of a count bar, and describes a network graph by its node
and edge counts instead of its (meaningless) layout x/y axes.
vellumplot 0.2.1
vellumplot 0.2.1
Consumes vellum's new compound native + mm unit (requires vellum >= 0.1.1).
- Label nudges:
mark_text()/mark_label()gainnudge_x/nudge_y
(in millimetres,+xright /+yup) that shift a label by an exact absolute
distance, device-resolved so the nudge is constant regardless of scale or panel
aspect. - Device-exact drop shadows:
shadow()'sx/yoffset is now an absolute
distance in millimetres (was a panel-relative npc fraction), so a drop
shadow stays the same physical distance and is isotropic on non-square panels.
Defaults changed accordingly (a small down-right drop). Replaces the previous
npc-fraction workaround.
Device-space dodge (mark_bar) and jitter (mark_point) remain data-space for
now; converting them to the compound unit is deferred (it changes existing
rendered output and needs snapshot review).
vellumplot 0.2.0
vellumplot 0.2.0
Grammar-breadth release: new scales, mapped aesthetics, legend control, and
distribution marks, closing the most conspicuous gaps versus ggplot2.
Scales & axes
- Date/time scales:
scale_x_date()/scale_x_datetime()/
scale_x_time()(andytwins) withdate_breaks(interval strings like
"3 months") anddate_labels(strftime formats).Date/POSIXctcolumns
still get a sensible date axis automatically. - New mapped aesthetics:
scale_alpha()(continuous opacity) and
scale_linetype()(discrete line types), each drawn per element with its own
legend.alphamapped to data now varies opacity (previously constant-only);
linetypeapplies tomark_line()/mark_step(). - Identity scales:
scale_color_identity(),scale_fill_identity(),
scale_size_identity(),scale_shape_identity(),scale_alpha_identity(),
scale_linetype_identity()— use data values verbatim, no legend. - Limit shortcuts:
xlim(),ylim(), andlims(). - Legend control:
guides()withguide = "none"(hide a legend),
guide_legend(reverse = TRUE)(reverse key order), andguide_legend(title=).
Marks & stats
- Distribution marks:
mark_ecdf()(empirical CDF step),mark_rug()
(marginal ticks),mark_qq()+mark_qq_line()(quantile-quantile plot). - Density-shape marks:
mark_violin()(mirrored density per category),
mark_ridgeline()(overlapping per-category densities), andmark_dotplot()
(binned, stacked dots). Violin and ridgeline carry per-category provenance.
Not yet implemented (still planned)
Secondary axes (sec_axis/dup_axis); 2-D density / contour
(mark_contour/stat_density_2d); position-binned scales (scale_x_binned);
coord_trans() and free non-position scales across facets; triple-merge legends
(colour+shape+size) and NA keys for size/shape.
vellumplot 0.1.1
vellumplot 0.1.1
- New exported
plot_provenance(): returns the compiled-scene provenance table
— one record per emitted mark grob, tying each low-level primitive back to the
data rows and trained scales that produced it, with anidthat matches the
grob'sdata-vellum-idin SVG and theidcolumn ofvellum::scene_model().
This is the first public consumer of the provenance metadata (previously
internal), the substrate for interactivity, linked views, and accessibility. - Row-key provenance is now refined per element for the line, area, ribbon,
step, text, and boxplot marks (previously only point/bar/tile/segment/sf/
edges), so a record'srowsresolve to the actual data rows an element draws
rather than the whole layer. See the scene-contract vignette invellum.