Skip to content

Releases: r-vellum/vellumplot

vellumplot 0.9.0

Choose a tag to compare

@schochastics schochastics released this 01 Aug 17:24
bf70748

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 accessibilityplot_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 huepattern_*() hatch fills that survive greyscale print and colour-vision deficiency, on every backend.
  • Keyframe animationtransition_states() / animate() / anim_save() to GIF, APNG, or a resolution-independent animated SVG (honours prefers-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

Choose a tag to compare

@schochastics schochastics released this 27 Jul 07:46
  • Flow maps: mark_flow_map(). A one-to-many flow map on a vgraph() plot:
    a single root fans 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}). Edge weight drives the flow, which is
    mapped onto width_range. Needs the suggested \pkg{edgebundle} package.

  • Edge bundling: mark_edge_bundle(). A drop-in alternative to mark_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. type selects the
    algorithm ("force", "divided", "stub", "path", "hammer", "mingle")
    and params passes 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 (a from/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, so a -> b and b -> a are distinct ribbons and self-flows
    loop within a node's sector. direction shows a ribbon's direction by fading
    it from source to target ("gradient", default), stopping it short of the
    target ("gap"), or "both". sort, gap, and link_color echo
    vsankey(); mark_chord() is the exported layer.

  • Removed the unused max_overlaps argument from mark_text(),
    mark_label(), and mark_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 under coord_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 produces NaN
    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 integer k. A zero, negative,
    or fractional window size now errors clearly instead of silently producing a
    wrong (or empty) result.

  • A computed-constant if_false in condition() 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.
    Under coord_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 other auto marks (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 at node_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 is NA (an empty group, or a summary of data containing NA
    when the function does not remove it) is now dropped with a warning on both
    the grouped and ungrouped paths, which previously treated NA differently. A
    layer mapping both color and fill also keeps both aligned after
    aggregation (fill could 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(), and scale_edge_alpha() now reject a limits (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 validates max_size.

  • add_marginal() rejects a non-numeric mapping. A factor x/y previously
    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-logical toggle/empty (it
    was silently coerced to FALSE); vgraph() validates width/height like
    vsankey()/vhierarchy(); and area() rejects a non-numeric or non-integer
    cell index instead of aborting with an opaque "missing value where TRUE/FALSE
    needed".

  • mark_text() honours hjust/vjust passed 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/dendrogram and gains a height-aware "dendrogram" layout plus an
    "unrooted" layout (via graphlayouts::layout_as_tree_unrooted(), needs
    graphlayouts >= 1.2.5). mark_edges(routing = "elbow") gained elbow_at /
    elbow_axis so 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, and k to cut the
    tree and colour clusters. Also fixes mark_node_text(label = ) so an explicit
    label mapping overrides the default vertex name (it was previously ignored).

  • vhierarchy() respects scale_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, so scale_fill_manual() /
    scale_fill_discrete() recolour the branches and a lighten argument controls
    the depth fade (0 = flat colour per branch). Mapping fill to a node column
    instead colours every node by that variable (discrete or continuous, with the
    matching scale_fill_*()), with no depth fade.

vellumplot 0.7.0

Choose a tag to compare

@schochastics schochastics released this 21 Jul 21:08
8134b0d

Breaking

  • New vhierarchy() replaces vsunburst(). One constructor draws a parent-list tree four ways via type =: "sunburst" (default), "icicle", "treemap", "circlepack". vsunburst() / mark_sunburst() are removed; use vhierarchy(..., 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

Choose a tag to compare

@schochastics schochastics released this 20 Jul 08:43
  • Sankey styling options. vsankey() / mark_sankey() gain show_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_gap to 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 listid, parent (NA at the
    root), and value (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 batched sector_grob in an aspect-locked, axis-free square panel (mirroring
    vsankey()/vgraph()). inner_radius opens a central hole (a donut/ring
    sunburst); nodes are coloured by depth. mark_sunburst() is the exported layer.
    See vignette("flows-and-hierarchies").

  • Sankey / flow diagrams: vsankey(). A new plot type for layered flows,
    built from a flow list — one row per flow with from, to, and value (the
    ribbon width). Nodes are the union of from/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 (mirroring vgraph()); 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. See vignette("flows-and-hierarchies").

  • Uncertainty marks: mark_halfeye() and mark_interval(). ggdist-style
    slab + interval marks for sample/posterior input (many y rows per categorical
    x). mark_halfeye() draws a one-sided density slab with a point-interval at
    its base — the median (or point = "mean"), a thick inner and thin outer
    equal-tailed quantile interval at the .width probabilities (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() and scale_size_area(). coord_radial() is a fuller
    polar system (ggplot2 3.5's name): besides theta/start/direction it takes
    end to sweep only a partial arc (e.g. start = -pi/2, end = pi/2 for a
    semicircular gauge) and inner_radius for a donut hole; with the defaults
    it matches coord_polar(). scale_size_area() maps a value to the marker's
    area (value 0 → size 0), the perceptually honest default for bubble
    charts, with max_size the 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 leaf value with a clear message (instead of a cryptic
    downstream error), and mark_sunburst() validates inner_radius like
    vsunburst() and coord_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 a width = 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 spelled inner_radius everywhere it appears:
    coord_radial(), vsunburst()/mark_sunburst(), and mark_donut() (was
    hole). mark_ridgeline()'s overlap control is now height (was scale,
    which collided with mark_halfeye(scale=), a different quantity).

  • Consistent sketch / blend passthrough. mark_ecdf(), mark_contour(),
    mark_contour_filled(), mark_dotplot(), mark_qq(), and mark_qq_line() now
    accept a per-layer sketch = (their emitters already honoured it), and
    mark_pie() / mark_donut() accept blend =, 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's position tunable 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 a padding gap —
    so ragged groupings stay centred), and jitterdodge (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
    from vellum::scene_model(), relaxes the label boxes in pixel space, and maps
    the result back — so it needs no approximation and no vellum change, and is
    deterministic under seed. Tunable via box_padding, point_padding,
    min_segment_length, and seed. 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 its y per group (over rows ordered by x) before drawing —
    moving mean/sum/median/min/max over a window of k, running
    cumsum/cummean/cummax/cummin, lag/lead shifts, or rank. Pass an op
    name (window = "mean") or a list (window = list(op = "mean", k = 7, align = "right", partial = TRUE)); align is trailing/leading/centred and partial
    fills the edges from the shorter window so the line stays continuous. A plot
    without window is unchanged.

  • Diverging colour scales: scale_color_gradient2() / scale_fill_gradient2().
    A three-point ramp (low--mid--high) centred on midpoint (default 0),
    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 + diverging in its colorbar descriptor, so an interactive host can
    centre a value-range filter on the neutral value.

  • symlog position 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, which log10 cannot show — reads on one axis.
    Breaks sit at zero and signed powers of ten. The transform name flows into the
    panel scales descriptor like the other transforms.

  • New group-region marks: mark_ellipse() and mark_hull(). Both enclose a
    set of (x, y) points in a single region drawn over a scatter — one region per
    group when a color/fill is mapped. mark_ellipse() draws a covariance
    ellipse (type = "t" robust default via \pkg{MASS}, or "norm"/"euclid"),
    following ggplot2's stat_ellipse(); mark_hull() draws the convex hull. Both
    are unfilled boundaries by default (map/set a fill to 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 a family
    via method.args, e.g. logistic — the fit and its ribbon are back-transformed
    from the link scale), "gam" (a penalised smooth, default y ~ s(x); needs
    \pkg{mgcv}), and "rq" (quantile regression at a single method.args$tau;
    needs \pkg{quantreg}, line only, no ribbon). The default method = "auto"
    picks loess for small groups (< 1000 points) and gam for large ones, as in
    ggplot2. New formula, span, and method.args arguments; glm/gam bands
    use a normal interval, lm/loess a t-interval. Previously mark_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
    (including coord_flip and a linear coord_trans) now push a pannable vellum
    viewport, and gridlines carry role = "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....

Read more

vellumplot 0.5.0

Choose a tag to compare

@schochastics schochastics released this 16 Jul 20:44
  • 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.axis argument fed by
    the new sec_axis() / dup_axis(): a second set of ticks and labels on the
    opposite edge (top for x, right for y), 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, or dup_axis() to duplicate an axis on a wide plot. The
    transform is a formula, function, or scales::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 a sec.axis is byte-for-byte unchanged.

  • Datashading now covers dense lines and large-graph edges. mark_line(),
    mark_step(), mark_segment(), and mark_edges() gain an auto = TRUE
    switch (parallel to mark_point(auto = TRUE)): past a row threshold the layer
    rasterises into a line- / segment-density field via vellum::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) or scale_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 (the batlow
    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 declared data_id/tooltip/hover_group through to
    their drawn segments, so each bar is keyed to its datum — it appears in
    scene_model() and plot_provenance() and carries a data-key in 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(). The shape aesthetic's default palette
    now extends to "triangle_down" (a downward triangle) and "star" (a
    five-pointed star) after the original six, so a mapped shape covers up to eight
    levels without an explicit scale, and both are accepted as scale_shape(values=).
    Requires the accompanying vellum dev version. (A constant shape = "star" on a
    mark already worked once vellum gained the shape.)

vellumplot 0.4.0

Choose a tag to compare

@schochastics schochastics released this 15 Jul 06:55

New features

  • Categorical datashading in one call. mark_datashade() now accepts a mapped
    discrete color/fill aesthetic 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 with blend = "screen". New span/clip arguments clamp the
    density range (absolute limits or percentiles) so a few extreme cells don't
    flatten the rest. Requires the accompanying vellum dev 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. src is a column of local file paths or one constant
    path, and size sets the height in millimetres (the width follows each
    image's native aspect ratio). Requires the suggested magick package (#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 of x/y takes 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 under coord_trans().

  • mark_violin() and mark_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's x variable along the top edge and of its
    y variable along the right edge, each sharing the scatter's axis so they line
    up (the vellumplot analogue of ggExtra::ggMarginal()). Pick the distribution
    with type = "density" / "histogram", the edges with sides ("t", "r",
    "tr"), and the extent with size; group = TRUE splits each marginal by a
    discrete color/fill mapping in the matching palette. It reads x/y from
    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.
    Previously y = 0 was 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 channels x, 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 one evenodd path_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 declaring data_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 one evenodd rule, two
    same-fill features that overlap would cancel in the overlap region (the same
    property a self-overlapping MULTIPOLYGON already has); disjoint features —
    every real choropleth and coverage map — are unaffected.

Bug fixes

  • linetype now works on stroked marks. mark_segment(), mark_linerange(),
    mark_rule(), mark_errorbar(), mark_rug(), mark_edges(), and
    mark_contour() (and annotate("segment", ...)) previously ignored
    linetype and 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 = Inf for 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 read 2010 instead of 2 010 (#27). Pass
    explicit labels to restore grouping.

  • More inputs fail fast. A design layout area must be a solid rectangle (and
    area() requires t <= 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 by mark_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 checks panel.spacing,
    plot.margin, aspect.ratio, and axis.ticks.length (and rejects NA/Inf
    in any numeric setting); vplot() checks width/height the way it already
    checked dpi; a discrete shape/linetype scale 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 the NA key 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
    panels 1, 2, 10 rather than the lexicographic 1, 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 -Inf is clamped into the trained range). A descending
    limit such as ylim(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-NA input instead of a cryptic
    seq() error. mark_raster() and z-surface contours reject a grid with a
    duplicated cell (previously a silent transparent/NA hole). Composition
    auto-tags continue past 26 sub-plots (Z, AA, AB, …) instead of NA.

  • mark_area() now stacks. mark_area(position = ...) was silently ignored;
    it gains a position argument ("stack" default, plus "fill" and
    "identity"), so areas sharing an x with a mapped fill/color combine 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
    fill on mark_label() colours the label background (previously it always fell
    back to white); a mapped or constant alpha is honoured per category/tick by
    mark_violin(), mark_ridgeline(), mark_contour(), mark_contour_filled()
    and mark_rug() (previously collapsed to one valu...

Read more

vellumplot 0.3.0

Choose a tag to compare

@schochastics schochastics released this 10 Jul 07:12
  • 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 mapped size or shape aesthetic
    whose data contains missing values now shows an "NA" key (as the colour scales
    already did). Also fixes a crash: NA in a shape mapping 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 and mark_contour_filled() fills the bands between them
    (coloured by level automatically). By default the field is a kernel density
    estimate (via MASS::kde2d()); map a z aesthetic to contour a supplied
    surface over a regular x/y grid instead. Contour tracing uses the isoband
    package (both isoband and MASS are 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: map label = 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 new labs(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, so render_plot() output carries role="img" +
    <title>/<desc> in SVG and a tagged Figure with Alt in 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). Added scale_colour_*() aliases for the scale_color_*() family.

  • Faithful alt text. The auto-generated description now matches what the plot
    actually renders: it honours scale_*(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

Choose a tag to compare

@schochastics schochastics released this 08 Jul 07:53

vellumplot 0.2.1

Consumes vellum's new compound native + mm unit (requires vellum >= 0.1.1).

  • Label nudges: mark_text() / mark_label() gain nudge_x / nudge_y
    (in millimetres, +x right / +y up) 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()'s x / y offset 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

Choose a tag to compare

@schochastics schochastics released this 08 Jul 07:53

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() (and y twins) with date_breaks (interval strings like
    "3 months") and date_labels (strftime formats). Date/POSIXct columns
    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. alpha mapped to data now varies opacity (previously constant-only);
    linetype applies to mark_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(), and lims().
  • Legend control: guides() with guide = "none" (hide a legend),
    guide_legend(reverse = TRUE) (reverse key order), and guide_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), and mark_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

Choose a tag to compare

@schochastics schochastics released this 08 Jul 07:53

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 an id that matches the
    grob's data-vellum-id in SVG and the id column of vellum::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's rows resolve to the actual data rows an element draws
    rather than the whole layer. See the scene-contract vignette in vellum.