Keyframe storytelling adds addKeyframe() for named,
transformed data snapshots and accessible previous/play-pause/next
controls. Single-layer charts accept a data frame; multi-layer charts
accept a named list keyed by layer label, with omitted layers retaining
their prior state. Playback runs once and stops at the final frame,
while reduced-motion and zero-duration transitions remain fully step-
and play-capable. Shiny applications can use setKeyframe()
and stepKeyframe() through the existing instance
registry.
WebR 0.6.0 compatibility is now a blocking CI contract: the package and its dependencies are compiled with the official r-wasm action, loaded in WebR, used to create and serialize a real widget, and rendered with the production bundle in Chromium. The verified path does not claim DuckDB-WASM support or universal compatibility across browser hosts.
Legend/button UI streamlining (#84): charts now show exactly one
legend surface at a time. When a discrete chart’s compact in-plot legend
is showing, the chart-controls panel no longer repeats the same legend
and becomes actions-only (this supersedes the 1.2.0 note that kept the
action-sheet legend alongside the new inline one). The in-plot legend is
now interactive — click or keyboard (Enter/Space) toggles a series
on/off, with role="switch" semantics matching the panel
legend. Charts with more than 10 series, or containers too narrow to fit
the strip legibly, move the full legend to the panel instead of
truncating (previously the strip silently cut off at 10 items). Inline
legend rows now wrap by measured width rather than item count. No R API
changes; suppressLegend() behaves exactly as
before.
Responsive chart-controls behavior now keys off the widget’s own container width everywhere, instead of a mix of container width (panel docking) and browser viewport width (button position, sheet drag handle). A narrow widget embedded in a wide page — dashboard grids, side-by-side layouts — gets a coherent narrow-tier UI. The panel legend’s two-column grid is now driven by item count on all sizes, not just narrow containers.
New setTransition(duration, easing, stagger)
configures chart animations: duration in milliseconds,
easing (one of "linear", "quad",
"cubic", "sin", "exp",
"circle", "back", "bounce",
"elastic", mapped to the corresponding d3 easing), and
stagger (per-element cascade delay in ms). All arguments
are optional and additive; unset values keep each renderer’s existing
defaults, so the change is fully backward compatible.
setTransitionSpeed() is now a thin wrapper over
setTransition(duration = ). Animation stays fully
opt-out-able: duration = 0 disables it, and easing and
stagger automatically no-op when the effective duration is 0, including
under the viewer’s prefers-reduced-motion: reduce system
setting. A Playwright e2e spec verifies animate-when-on, still-when-off,
and still-under-reduced-motion.
New opt-in "lttb" transform for line
layers downsamples a large series with Largest-Triangle-Three-Buckets,
shipping at most options$threshold points (default 2000)
while preserving the visual shape:
addIoLayer(type = "line", transform = "lttb", options = list(threshold = 1000)).
Off by default (identity); runs on the in-memory/SVG path
and is independent of the DuckDB-WASM engine’s own SQL-side LTTB, so it
never double-downsamples.
New myIOProxy() + updateMyIOData()
update a rendered chart’s layer data in place from the Shiny server
without re-running renderMyIO(). Layers are matched by
label and swapped through the existing data-join path, so only the
changed marks transition and brush/zoom/toggle state is preserved (the
full re-render destroyed and recreated the chart, flickering and
dropping state):
myIOProxy("chart") |> updateMyIOData(series = new_df).
@hono/node-server
2.0.12 and declares Node.js 20 or newer as its runtime floor; its
conformance, stdio smoke, and audit gates pass with zero known npm
vulnerabilities.inst/htmlwidgets/myIO/myIOapi.js drops from 2.32 MB to 1.20
MB raw (398,650 to 298,757 bytes gzipped, -25%) with no behavior change;
the development watch build stays unminified for
debugging.playwright.config.ts and a new e2e CI
workflow. The suite builds and loads the minified
myIOapi.js, guarding the production bundle that
source-importing unit tests cannot catch.touchstart on a bar surfaces the tooltip with the datum’s
content and touchend dismisses it, guarding the mobile
hover path against the production bundle.file:// deployment e2e (self-contained widget
opened directly from disk) is now exercised for real: its fixture loads
the IIFE bundle via a classic <script src> rather
than an ES module, so the file-protocol → SVG-engine fallback is
verified under file:// instead of skipped.Argument names are now consistently camelCase across setters.
setBrush(onSelect),
setFacet(minWidth, labelPosition),
setTheme(textColor, gridColor), and
setBigData(rowkeyCol) are the canonical forms (matching
colorScheme, xAxis, etc.). The previous
snake_case names (on_select, min_width,
label_position, text_color,
grid_color, rowkey_col) keep working as
deprecated aliases that emit a one-line warning; existing code is
unaffected aside from the warning. When both forms are supplied the
camelCase value wins.
setFacet(), setLayerOpacity(), and
setTheme(mode = ) now report invalid arguments with
consistent, actionable messages (e.g. setFacet(): \scales`
must be “fixed”, “free_x”, “free_y”, “free”, not
“x”.) instead of barestopifnot()failures.setColorScheme()`
errors are likewise function-prefixed. No change to which inputs are
accepted.
setTheme() now warns when passed an unknown argument
that lacks the required -- prefix (e.g. a misspelled
text_colour) and suggests the intended argument, instead of
silently dropping it. Valid ---prefixed CSS overrides are
unaffected.
setTheme() documents the named preset
values ("midnight", "ocean",
"forest", "sunset", "monochrome",
"neon", "corporate", "academic",
"nature", "minimal", "retro",
"warm", plus "light"/"dark"); the
preset argument was already functional.
setLinked() and linkCharts() now
cross-reference each other in their documentation to clarify when to use
the Crosstalk path versus the group-identifier path. ##
Documentation
New “Theme Gallery” article renders the same chart under all
named presets (midnight, ocean,
forest, sunset, monochrome,
neon, corporate, academic,
nature, minimal, retro,
warm, plus light/dark) as live,
side-by-side previews, and shows how to layer custom CSS overrides on
top of a preset. ## Performance and reliability
Inline Arrow IPC payloads now decode via the native
Uint8Array.fromBase64 when the browser provides it (falling
back to the previous atob loop), avoiding a per-character
JavaScript callback over large payloads in the in-memory and DuckDB-WASM
engines.
Added a regression test confirming charts that already render an inline legend are not given a duplicated legend on image/SVG export (GH #64).
Layer-data serialization (addIoLayer()) is faster
for large data: the row-rectangling step now extracts columns once and
indexes per row instead of subsetting the data frame on every row,
roughly 5x faster at 100k rows. The emitted JSON is byte-identical to
before (pinned by tests across numeric, integer, character, logical,
factor, Date, and POSIXct columns), so every chart type renders exactly
as it did.
inst/myio-schema.json) describing all chart types,
required mappings, valid transforms, and function signatures, generated
from the package’s own contracts.myio_list_chart_types(),
myio_chart_schema(), myio_validate_spec(),
myio_list_functions(),
myio_function_signature(), and
myio_validate_call(). A Model Context Protocol server in
the package’s source repository exposes the same six tools to
MCP-compatible clients.llm-tool-calling demonstrates a
generate-validate-repair loop.quantile_dots chart type and
quantile_dots transform render a Wilkinson dot plot of
predictive quantiles for communicating uncertainty.fan composite renders a fan chart of nested
prediction intervals around a central estimate.myIO(title = ...) and setTitle() add a
backward-compatible chart title surface rendered inside the SVG.
Existing charts that omit a title are unchanged.setAxisFormat(xLabel = ..., yLabel = ...) now render in the
chart SVG, and multi-series charts get a compact visible legend while
preserving the existing action-sheet legend.setLinked() now links waffle,
beeswarm, lollipop, and dumbbell
layers in addition to the previously supported point,
bar, groupedBar, histogram, and
hexbin. Selection dims non-matching elements; filter hides
them. Aggregate chart types (boxplot, violin,
qq, regression, density,
ridgeline, survfit, comparison)
remain outside crosstalk in v1.2.crosstalk-linking demonstrates linking
myIO with DT::datatable() and
reactable::reactable(), with code listings for plotly and
leaflet.myIO() widgets now declare a sizing policy with
browser.fill = TRUE and are tagged as
html-fill-item via htmltools::bindFillRole(),
so they fill container height inside bslib::card(), Quarto
Dashboard {.fill} cards, and flexdashboard layouts.
Rendering in plain RMarkdown and Shiny at the default 400px height is
unchanged.calendarHeatmap chart type —
GitHub-contributions-style grid of daily cells over a single calendar
year. Usage:
addIoLayer(type = "calendarHeatmap", data, mapping = list(date = "day", value = "x")).
Supports Sunday or Monday week starts via
options$weekStart, continuous color legend, and
linked-cursor sync across two linked calendars. Multi-year data is
rejected in v1.2; multi-year layouts are planned for v1.3. New CSS
variables: --chart-calendar-cell-gap,
--chart-calendar-cell-stroke,
--chart-calendar-empty-fill.setTheme(preset = "midnight").
Available presets: midnight, ocean, forest, sunset, monochrome, neon,
corporate, academic, nature, minimal, retro, warm. Plus light/dark via
mode parameter.linkCharts(chart1, chart2, on = "column") enables
cross-chart selection. Brush in one chart highlights matching rows in
linked charts. Works in Shiny and static HTML. Aggregation-to-source-row
key resolution for summary views.linkCharts() and setLinked() gain a
cursor = TRUE argument that draws a synchronized vertical
crosshair on every linked chart when the user hovers any chart in the
group. Supports point, bar, line, area, groupedBar, hexbin, and
histogram layers (donut and treemap are excluded — no x-axis to sync
on). Off by default; enable per-link or retrofit with
setLinkedCursor() on a pre-linked widget. New
--chart-cursor-rule-color/-width/-dasharray
CSS variables theme the crosshair.dplyr::group_by() tibble to
addIoLayer() and it auto-creates one layer per group with
colors from the Okabe-Ito palette. Soft dplyr dependency.addIoLayer(type = "survfit", mapping = list(time = "time", status = "status"))
computes Kaplan-Meier estimator with Greenwood CI using base R only (no
survival package dependency). Composite expands to
step-curve line, CI band, and censored-observation markers. Group
stratification supported.addIoLayer(type = "histogram_fit", mapping = list(value = "x"), options = list(family = "normal"))
fits normal, lognormal, or exponential distributions via MLE. Composite
renders histogram + fitted density curve + optional parameter
annotation.setLayerOpacity(label, opacity) sets per-layer opacity
(0-1). Critical for CI bands overlaying scatter data.graphics-document,
graphics-object, graphics-symbol) applied to
SVG chart structure for screen reader navigation.lollipop — vertical stem with circle head, supports
mean and summary transforms. Compatible with
categorical x-axis charts.dumbbell — connected dots showing a range between
low_y and high_y.waffle — 10x10 grid of colored squares representing
proportions. Standalone.beeswarm — dodge-positioned points to avoid overlap.
Inline dodge algorithm.bump — smooth S-curves showing rank/value changes over
time with grouped lines.radar — spider/radar chart with radial axes and polygon
data fill.funnel — narrowing horizontal bars for conversion
pipeline data.parallel — parallel coordinates for multivariate
exploration. All new types support themed colors and standard tooltip
formatting.setFacet(var, ncol, scales) splits charts into a CSS
grid of panels, one per unique value of the faceting variable. Supports
fixed and free scale modes, auto-layout with configurable minimum panel
width, and responsive breakpoints.setExportOptions() controls which export buttons appear
in the toolbar (PNG, SVG, clipboard, CSV). New SVG download button and
clipboard copy (SVG + PNG) via the Clipboard API.setTheme(mode = "dark") applies a WCAG AA-verified dark
palette across all chart elements. Also supports "light"
and "auto" (detects OS preference and Quarto/Bootstrap
data-bs-theme).setTheme(bg = "#fff")
calls still work.overrides parameter for fine-grained CSS custom
property control.myIO(sparkline = TRUE) renders a compact, axes-free
chart suitable for embedding in table cells (reactable, DT, gt). Strips
legend, axes, reference lines, and all interactions. Default height
20px. Supports line, bar, and area layer types.myio_validate_spec() and the chart specification schema
no longer reject valid specs for chart types with a single required
mapping (for example histogram, gauge, and
qq). The schema now always represents list-typed fields as
arrays.myIO now supports bidirectional data flow — user interactions return structured data, not just visual feedback.
setBrush(): rectangle brush selection on point, bar,
histogram, hexbin, and grouped bar charts. Selected points available as
Shiny reactive input or scoped CSV export in static HTML.setAnnotation(): click any data point to attach a
label. Annotations stored as structured data (source key, coordinates,
label, category color, timestamp) and exportable as CSV. Supports preset
label dropdowns and color category pickers.setLinked(): Crosstalk SharedData
integration for cross-widget linked brushing. Selection dims
non-matching points; optional filter mode hides them. Supports source,
target, or bidirectional modes.setSlider(): parameter sliders below the chart that
trigger Shiny re-rendering with configurable debounce. Renders disabled
with tooltip in static HTML.dragPoints() now correctly emits the
dragEnd event to Shiny
(input$myIO-{id}-dragEnd), which was previously registered
but never fired.type + transform = layer. Transforms compute statistics in
R; renderers display them in D3.js.lm, loess,
polynomial, ci, smooth,
mean, mean_ci, residuals,
summary, pairwise_test, qq.line, point, bar,
area, groupedBar.histogram, hexbin,
regression, qq.boxplot, violin,
ridgeline, comparison.candlestick, waterfall,
heatmap.donut, gauge,
treemap, sankey.boxplot: expands into IQR box + whisker caps + median +
outliers.violin: expands into density area + IQR box + median
point.ridgeline: density curves stacked vertically by
group.regression: scatter + trend line + CI band + R-squared
annotation.qq: Q-Q scatter + reference line + confidence
envelope.comparison: boxplots + pairwise significance
brackets.setToolTipOptions().dragPoints().setReferenceLines().setToggle().myIOOutput() and
renderMyIO().|>.setTheme().