Requires vellum >= 0.6.6, which fixes the coordinate space
element_geometry() reports in — the exact hit-testing below is built on it.
-
The raster base image is now rendered at 2× for HiDPI screens. A widget is
always viewed at screen resolution, and a 1× image stretched by the browser is
visibly soft on a retina display.scene_png(scale = 2)multiplies dpi while
holding physical size, so the image carries two pixels per device pixel while
the SVG shell stays in device pixels — the space the element bounding boxes and
panel rectangles live in, so nothing about hit-testing or pan/zoom changes.No new argument: raster mode is chosen precisely when the SVG alternative would
be enormous, so the extra bytes are noise against what they replace (at 30,000
keyed points, 446 KB → 877 KB, versus a 14.7 MB per-element SVG). The dense end
is cheaper at 2×, not dearer — a saturated canvas compresses better.The shell's dimensions now come from the scene rather than from the image, via
vellum::vl_convert(). Halving a scaled PNG re-rounds and drifted by a pixel
on a fractional page size, which would have put a small but systematic offset
between the drawn frame and the bounding-box space picking uses. -
Exact hit-testing: picking now measures to the mark, not to its box. The
payload carries each keyed element's true vertices in device pixels
(vellum::element_geometry(), computed once at build time); the Flatbush
R-tree still shortlists candidates by bounding box — a box distance is never
greater than the true distance, so nothing in range is missed — and the
shortlist is then ranked by distance to the actual shape. What changes:- Graph edges are hoverable. An edge's box is the whole rectangle its
endpoints span, so a box-distance "nearest" matched it from anywhere inside
that rectangle; edges had to be excluded from the open-space snap entirely.
They are now measured to the line and compete on their merits. - A diagonal is no longer matched from the far corner of its box. On a
four-series line chart whose boxes each cover ~91% of the panel, a cursor on
a line picks that line — where previously two or more series were tied at
box-distance zero at 39 of 40 probe positions. - A click inside a filled region hits the region, at distance zero, rather
than snapping to whichever border happens to be nearest. - Round marks are measured to the disc, not to the square around it.
Only the kinds whose shape is not their box (segment, line, polygon, path) are
shipped; a point's disc and a label's box are reconstructed from the bounding
box the payload already carries, so the dense scatter — where payload size
actually bites — pays nothing for this. Above 500,000 vertices the block is
dropped with a message and picking falls back to boxes.Brush and lasso are unchanged: a box is the right answer for a rectangular
brush, which is what it was always for. - Graph edges are hoverable. An edge's box is the whole rectangle its
-
Raster-mode widgets no longer touch disk. The base image is now encoded
withvellum::scene_png(), which returns the PNG bytes, instead of rendering
to atempfile()and reading it back. Widget output is unchanged. -
Animated plots as widgets.
as_widget()now accepts a keyframe animation
fromvellumplot::animate()and embeds it as a self-contained animated SVG
— resolution-independent and honouringprefers-reduced-motion(both handled
inside the SVG by vellum). It plays on its own through the runtime's existing
inline-SVG mount; since the marks move every frame there is no per-element
interaction index (no hover/brush) and no raster canvas. -
Fix: click-to-source did nothing for single-row groups. A grob backed by
one data row had itsrowsauto-unboxed to a scalar in the payload, so the
runtime'srows.lengthcheck skipped it (no event, no popover) — e.g. every
single-county region of a choropleth. The payload now forces arrays, and the
runtime coerces a scalar defensively. -
Click-to-source. When a plot opts in with
vellumplot::inspect_source(),
clicking a mark surfaces the source data rows behind it: the widget reads the
compiled scene's provenance (keyed by each grob'sdata-vellum-id), fires a
bubblingvellum:sourceDOM event (detail=id,rows,fields, and
valueswheninspect_source(values = TRUE)), setsinput$<id>_sourceunder
Shiny, and shows a small values popover. Opt-in rides the spec, so it adds no
as_widget()argument and no payload weight to a plain widget.