Skip to content

vellumwidget 0.8.0

Latest

Choose a tag to compare

@schochastics schochastics released this 01 Aug 20:13
· 2 commits to main since this release
f72a09d

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.

  • Raster-mode widgets no longer touch disk. The base image is now encoded
    with vellum::scene_png(), which returns the PNG bytes, instead of rendering
    to a tempfile() and reading it back. Widget output is unchanged.

  • Animated plots as widgets. as_widget() now accepts a keyframe animation
    from vellumplot::animate() and embeds it as a self-contained animated SVG
    — resolution-independent and honouring prefers-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 its rows auto-unboxed to a scalar in the payload, so the
    runtime's rows.length check 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's data-vellum-id), fires a
    bubbling vellum:source DOM event (detail = id, rows, fields, and
    values when inspect_source(values = TRUE)), sets input$<id>_source under
    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.