setLinkedCursor(axis = ),
linkCharts(cursorAxis = ) and
setLinked(cursorAxis = ) now render. The argument was
validated, stored and published to the MCP schema since 1.2.0, but the
engine only ever drew a vertical rule, so "y" and
"xy" were accepted and then silently ignored.
"y" draws a horizontal rule at the hovered y value and
"xy" draws both, which is what makes cursor sync useful for
horizontally-oriented charts and for scatter matrices. Each rule is
drawn only when the receiving chart can map the incoming value through
its own scale, so a value outside a sibling’s domain draws nothing on
that axis rather than clamping to an edge. The default is still
"x" and its output is unchanged.
transform_ci() accepts
method = "polynomial". It previously stopped with
"Unknown ci method", so composite_regression()
with a polynomial fit and a confidence band never worked. The fit uses
options$degree (default 2) and loess now
honors that same option instead of always fitting locally quadratic.
Polynomial fits require degree + 2 points rather than 3,
since a higher-degree fit through too few points has no residual degrees
of freedom to build an interval from.
setTransition(easing = ) now reaches every chart
type. It was documented as universal but only the enter and update joins
of about half the renderers actually applied it, so calling it on a
radar, funnel, waffle, dumbbell, bracket, beeswarm, bump, calendar
heatmap, lollipop, parallel-coordinates or quantile-dots chart changed
nothing and produced no warning. All 144 transitions across the renderer
set now resolve their easing through the public setting, falling back to
each renderer’s previous curve when no easing is set, so charts that
never called setTransition() animate exactly as before.
setTransition(stagger = ) reaches the eleven renderers that
had no transition delays at all; transitions that use a delay to
sequence one animation behind another keep that sequencing.
Bump charts now draw rank 1 at the top of the plot with whole-number axis ticks, instead of rank 1 at the bottom with half-rank ticks such as 1.5. This flips the vertical orientation of every existing bump chart.
Confidence and prediction intervals are no longer the same width.
transform_ci() read interval but computed both
from the standard error of the fit alone, so a prediction interval was
drawn as a confidence interval. Prediction intervals now add the
residual scale, and the t critical value uses the degrees of freedom the
prediction reports rather than a value derived from the effective
parameter count. Every prediction band drawn before this was too
narrow.
Grouped charts no longer emit phantom rows for missing group
values. Boxplot, violin, ridgeline, quantile-dots and the other grouped
transforms matched groups with ==, which returns
NA rather than FALSE for an NA
group value, so subsetting introduced all-NA rows into the
result. Groups are matched by position now, and a genuinely missing
group is labeled "NA (missing)" — chosen to avoid colliding
with an existing label after the engine strips punctuation from tag
names.
transform_lm() returns a typed empty result instead
of failing when fewer than two finite points remain after dropping
NA and infinite values. It also drops those rows before
fitting rather than passing them to lm().
Survival curves extend through the end of follow-up rather than
stopping at the last event time, and lines and bands honor
curveType = "stepAfter" instead of drawing a smooth
monotone curve through step data.
myio_validate_spec() rejects mappings that are
non-character, empty, NA, or whitespace only. It previously
accepted them and failed later in the engine. For parallel
charts it validates every column in the dimensions list
rather than only single-string mappings.
Layer opacity set through setLayerOpacity() is no
longer overwritten by enter transitions, and layer matching uses exact
mark tokens — a label that was a prefix of a neighboring label could
hide the wrong marks.
setLayerOpacity() reaches donut, gauge and
grouped-bar charts, which ignored it without a warning. Together with
the matching fix above, opacity now applies on every chart type,
including layers whose labels contain punctuation (#122).
Beeswarm charts center points on their category bands instead of
starting at the band edge, which placed points above their axis labels,
and category tooltips no longer read NaN.
Calendar heatmaps size cells from the chart’s actual clip dimensions and account for the gaps between cells, so the last weekday row is no longer clipped in narrow containers.
Axis ticks render correctly for R Date values before
1970.
Charts clean up on destruction: delayed callbacks no longer fire against a removed chart, and binding subscriptions are released. Registration is retained while a chart is merely hidden, so charts in inactive tabs still render when their tab is shown.
setSlider() documentation describes what the
function does. It does not re-render on its own — the value has to be
read from input$`myIO-{outputId}-slider-{param}` in the
render expression and passed to the transform option. The previous
example passed a fixed option value and a param name that
was documented as a transform option, so following it produced a slider
that changed nothing.
NEWS entries use the US spellings the package declares in
Language: en-US.
The large-data vignette no longer advertises server-engine
timings or recommends the "server" engine for hosted Shiny.
It now says that the server engine does not run brush queries in this
release (#123).
Radar charts follow the theme. Their grid rings, spokes and
labels read --chart-grid and --chart-fg, two
variables nothing defines, so
setTheme(textColor = , gridColor = ),
mode = "dark" and every preset were ignored and the light
fallbacks always rendered. Axis labels were close to invisible in dark
themes, at about 1.1:1 contrast. They now read
--chart-grid-color and --chart-text-color like
every other renderer, so the default light radar grid and labels also
change to the theme’s gray (#140).
setToolTipOptions(suppressY = TRUE) works again. The
option was validated, stored and documented, but nothing in the v1.0
engine read it, so the y value appeared in every tooltip. Tooltips on
point, bar, line, area, grouped-bar and donut charts now show only the x
title when it is set. Charts that never call it are unchanged
(#121).
Reference lines from setReferenceLines() follow the
theme. They were drawn with an inline gray stroke 3px wide,
which overrode the --chart-ref-line-color and
--chart-ref-line-width values that every preset and
mode = "dark" set, so they could not be themed at all. They
now read those tokens, which also changes the default line to
#9ca3af at 2px, matching the tooltip crosshair
(#141).
Hiding every layer from the legend no longer strands the chart. The empty state used to hide the legend-and-actions button and close the panel, so once the last visible series was switched off there was no way to bring any of them back without reloading. The button and panel now stay, with the entries shown as off and a “Show All” button.
The legend-and-actions button opens and closes the panel, and it keeps its menu icon. It used to turn into an X while the panel was open, and clicking that X did nothing.
Charts with a single series no longer show a one-entry legend in the panel. The entry repeated the layer name, sat above a divider with nothing to divide, and could only be used to hide the chart’s only data. Legends with two or more entries and gradient legends are unchanged.
Funnel charts no longer show a legend. Their stages are already labeled on the bars, and clicking a legend entry did nothing. Waffle, radar, parallel-coordinates and treemap legends show a plain color key instead of entries that looked clickable but did nothing. Donut legend entries still hide and show segments.
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).
setLegendTitle() puts a title on the legend naming
the variable its entries come from, so a grouped chart whose series are
labeled 5 6 7 8 9 can say those are months. The title
renders on whichever legend surface is active – the compact in-plot
strip or the panel behind the legend button – and on exported SVG, PNG
and PDF. Pass a string, or TRUE to derive the title from
the grouping column supplied to
addIoLayer(mapping = list(group = ...)). Charts render
exactly as before until the function is called.
A chart title too long for its container is now trimmed with an ellipsis instead of running underneath the legend button in the top-right corner. The untrimmed title is kept as the element’s accessible name.
Toggling a grouped bar chart to its stacked layout no longer leaves the rotated y-axis title behind when the wider stacked tick labels grow the left margin. The title moved with the plot on a normal redraw but not on the layout toggle, so the labels still ran through it.
The legend button no longer sits on top of plotted data. Charts
already reserved the button’s corner band on narrow layouts; wide
layouts now do the same, so a mark that lands in the top-right corner is
never hidden underneath it. An explicit setMargin() still
wins, and sparklines and non-axis charts are unaffected.
Waterfall charts no longer log a browser console warning about
null values, and the total bar’s tooltip no longer reads
Delta: null. A total row is declared by putting
NA in the value column, and that NA was
traveling all the way to the browser even though the transform had
already worked out what the bar’s height should be. The total row now
carries its own magnitude – the running total the bar spans – so the
chart reports a real number everywhere. Blast radius: the value column
in exported CSV changes for waterfall charts. A total row’s cell now
holds the total instead of being blank, and a non-total row written as
NA now reads 0, which is the height that row
has always been drawn at. Nothing about how the chart renders
changes.
Faceted charts now size each panel’s left margin to fit its y
tick labels. Panels never ran the margin fit that ordinary charts run,
so a facet grid with a wide y format – currency, or any large number –
drew its tick labels through the rotated axis title and off the left
edge of the panel, where the same chart unfaceted laid out correctly.
Blast radius: a faceted, axes-based chart whose y labels are wider than
its left margin now gains left margin and loses the same amount of plot
width; with facet(scales = "fixed") all panels share one
domain and so shift together, and panels that call
setMargin() are unchanged.
Grouped bar charts no longer let their y tick labels run through
the y-axis title after switching to the stacked layout. Stacking sums
the series, so the tick labels gain digits the chart’s left margin was
never sized for – a "$,.0f" format on data in the hundreds
of thousands pushed the labels several pixels past the rotated title.
The left margin is now re-measured after the bars are drawn, both when
the layout is toggled from the chart’s action button and on every redraw
of a chart already in stacked mode. Charts that call
setMargin() keep the margin they were given, and every
other chart type is unchanged.
Linked charts no longer disagree when a brush covers empty space.
Dragging a brush over a region containing no points left the brushed
chart with all its marks dimmed while every linked chart snapped back to
full opacity, so the two showed opposite states for the same selection.
An empty brush is now treated as what it is – a selection of nothing –
and dims the marks on the linked charts too. Removing the brush entirely
still restores everything everywhere, unchanged. Blast radius: any
existing setLinked() or linkCharts() page will
now dim its linked charts while a brush sits over empty space, where
before they stayed bright. The myIO-{id}-brushed Shiny
input gains an active field carrying the distinction
(TRUE while a brush rectangle is on screen,
FALSE once it is removed); existing code reading
keys, extent or data is
unaffected.
setLinked() charts now cross-select against non-myIO
widgets. A myIO chart in a Crosstalk group used to broadcast and match
on its own internal row numbers rather than the keys the
SharedData object was built with, so brushing it selected
nothing in a linked DT table, plotly figure or
leaflet map, and a selection made in any of those lit
nothing in the myIO chart. Selections now travel on the Crosstalk keys,
so all four coordinate. Two myIO charts linked to each other behaved
consistently before and still do, and widgets saved by earlier versions
of myIO keep working. Blast radius: the keys that reach the Crosstalk
group change from myIO’s private row_1, row_2,
… to the real keys, so any custom JavaScript listening on the group now
sees the same keys every other widget uses. One limit is unchanged: keys
are paired with rows by position, so a chart built from a frame that was
re-sorted or re-filtered after shared$data() cannot be
matched. Where the row count no longer matches, the chart now falls back
to matching within itself rather than pairing keys with the wrong rows.
See ?setLinked.
type = "boxplot" with
options = list(whiskerType = "minmax") no longer fails with
$ operator is invalid for atomic vectors. The documented
"minmax" whisker style errored on every call; whiskers now
render at each group’s own minimum and maximum as documented. The
default "tukey" whiskers are unaffected.
The compact in-plot legend no longer runs underneath the x-axis title on narrow charts. Its first row shares a baseline band with the centered axis title, so that row now stops short of it and wraps instead; the second row sits below the title and keeps the full width.
Treemap cells no longer render underneath the floating action button in the chart’s top-right corner. The tiling now reserves the same button-width band that funnel and Sankey charts already reserve, so the corner cell and its label stay fully visible and clickable.
linkCharts() now actually links charts. Brushing one
chart in a linkCharts() group dims the non-matching marks
on every other chart in the group; previously nothing happened at all,
in any environment. Three things were wrong: the link mode written by
linkCharts() was a value the browser-side code did not
recognize, so neither the outgoing nor the incoming handler was ever
connected; the cross-selection machinery required Crosstalk to be loaded
even though linkCharts() is documented as not needing it,
so it bailed out on a plain R Markdown or Quarto page; and rows were
matched between charts by position rather than by the on
column, which is meaningless for charts built from different data
frames. Charts are now matched on the on column’s values as
documented, and coordination works with or without Crosstalk on the
page. Charts linked with setLinked() are unaffected – they
continue to use Crosstalk and to match on the shared row key. Widgets
saved by earlier versions of myIO keep working against the new
code.
Clearing a brush – by clicking outside it, pressing Escape, or using the status bar’s “Clear” button – no longer overflows the call stack. The overflow left the chart’s stored selection in place and skipped the cleared event, so linked target charts stayed dimmed until the page was reloaded.
Charts no longer pad the y axis down into negative territory for
data that never goes negative. Y-axis padding is now 5% of the data
range per side, matching the x axis, and the padded lower bound stops at
zero when the data minimum is at or above zero – a yield series
bottoming out near 0 no longer renders a -5 tick. Limits
set explicitly with setAxisLimits() are
unaffected.
type = "regression" now draws the raw scatter, the
fitted line and the confidence band in visually separable treatments.
Previously all three shared a single hue, so the fit and its uncertainty
band were hard to distinguish from the data. The scatter keeps the layer
color, the fit line and band take a second contrasting hue from the
Okabe-Ito palette, and the band renders at a lighter fill opacity. Pass
a two-element color vector to set both explicitly
(color = c("#333", "#333") restores the previous single-hue
look), or set options$areaOpacity to control the band
opacity.
Linked brushing set up with setLinked() now actually
propagates: brushing a chart configured as a Crosstalk source dims the
non-selected points on every target chart in the same group, and
clearing the brush restores them. Target charts previously stayed inert
because the inbound Crosstalk subscription was registered under an event
name Crosstalk never emits. The filter = TRUE variant,
which hides rather than dims non-selected marks, is fixed
likewise.
The demo screenshot script
(scripts/screenshot-all.js) now enumerates the navbar and
any nested tabsets from the live DOM instead of a hard-coded tab list,
so it covers all 41 demo charts and no longer fails on tabs that are
plain top-level entries rather than dropdown menus. It also records
console errors and warnings per chart in
report.json.
Waffle charts now show a legend. Previously no legend appeared at all – neither the in-plot strip nor the bottom-sheet panel listed a single category, so there was no way to tell which color meant which category. The legend now lists one entry per category, in data order, with swatches that match the cell colors.
Bump charts now draw each series in its own color, matching the legend swatches. Every series previously rendered in the same blue, making the legend useless and the ranking lines impossible to tell apart.
Bump chart points and lines now line up with their x-axis ticks. On a categorical x axis the marks were drawn half a category width to the left of the tick they belonged to.
Small multiples created with setFacet() now show the
chart title once, above the grid, instead of repeating it inside every
panel, and every panel draws its own x and y axis with identical plot
geometry so the panels are readable and directly comparable. Previously
only the first panel had a y axis, only the bottom row had an x axis,
and the panels that lost an axis were drawn wider than the rest. The
facet grid is also styled as a real CSS grid, so ncol and
minWidth take effect and panels sit side by side rather
than stacked at full width. An explicit suppressAxis()
setting is now honored inside facet panels.
Radar charts now draw concentric grid rings with radial value
labels, so the magnitude of each polygon vertex can actually be read.
Previously the chart showed only the radial spokes and their category
labels, leaving the plot without any value reference. The rings can be
turned off or re-leveled per layer with
options = list(grid = FALSE) and
options = list(gridLevels = 6).
Bar, grouped bar and lollipop charts now start their value axis
at zero by default, so bar lengths are proportional to the values they
encode. The axis previously began at a buffered data minimum (a grouped
bar demo spanning 56-97 started at 50) while the bars themselves were
still drawn from zero and clipped, so the axis and the geometry
disagreed. An explicit setAxisLimits() still wins on either
bound. Relatedly, a limit of exactly 0 passed to
setAxisLimits() is no longer silently discarded – it was
treated as “not set” on every chart type, so
ylim = list(min = 0) had no effect at all.
Violin charts now draw the median marker inside the interquartile box. The median sub-layer rendered nothing at all, so the box showed the IQR span with no indication of where the median sat within it. It now draws the same white rule that boxplot charts use, matched to the box width.
Ridgeline charts now label the y axis with the group each ridge
belongs to instead of printing the raw stacking offsets (0.8, 1.0, 1.2,
…), and default the y axis title to the grouping variable. The axis
previously read as a density scale even though the tick positions were
group baselines, so the numbers were meaningless. An explicit
setAxisFormat(yLabel = ) still wins.
Funnel and Sankey charts now show their numbers in the plot.
Funnel stages print the stage value and its conversion rate against the
first stage; Sankey nodes print their total and each flow prints its
magnitude. Previously the only text was the stage or node name and the
values were reachable only by hovering. Labels honor
setAxisFormat(yAxis = ), are placed so they stay readable
against the mark they sit on, and are suppressed rather than overlapped
where the shape is too small. Pass showValues = FALSE to
addIoLayer() to restore the previous names-only
labeling.
Sankey charts no longer push their terminal nodes against the right edge of the plot. The layout now reserves room for the right-hand node labels, so the last column and its labels sit inside the plot area instead of being clipped and drawn back on top of the flow ribbons. Node labels are also drawn in a high-contrast ink with a background-colored halo, so they stay legible where they cross a ribbon; on a dark chart background the ink flips to white.
Rotated y-axis titles set with
setAxisLabels(yAxis = ) are no longer shaved off at the
left edge of the chart. The title was anchored 6px from the SVG border,
but a -90 degree rotation grows the glyphs leftward from
that anchor, so the tops of every letter were clipped on every chart
with a y-axis title. The anchor now sits far enough inside the border
for the full glyph height to fit.
Funnel and Sankey charts no longer draw under the floating action
button in the corner of the widget. Both filled their whole plot
rectangle, so the widest funnel stage and the right-hand Sankey column
and its labels were partly hidden behind the button. Both layouts now
reserve that band. Charts that already set
setMargin(right = 56) or larger are unchanged.
An open legend/actions panel now closes when its chart’s tab is switched away from. Tab frameworks hide the pane rather than destroying the widget, so the panel stayed open – it was still there on return, its button still reported itself expanded, and two charts could hold competing keyboard focus traps at once.
Sankey flow values no longer stack on top of each other. Two links that cross inside the same column gap can compute the same label position, and each label’s visibility was decided without reference to any other, so both were painted on the same pixel. A label that would land on one already drawn is now dropped, deterministically and in the links’ own data order, so the same label wins on every re-render. The suppressed value is still on the link’s tooltip and in the chart’s data table.
Short funnels no longer drop their values with no trace. Value
placement now degrades in three steps instead of two: a stage band at
least 34px tall keeps the value on a second line under the stage name, a
band between 18px and 34px moves it onto the name’s own line just
outside the trapezoid, and only below 18px is the name drawn alone with
the value left to the tooltip. Previously anything under 34px hid every
value outright, including values that would have fitted perfectly well
beside the stage. options = list(showValues = FALSE)
behaves exactly as before.
A funnel value label placed outside its trapezoid no longer runs under the legend/actions button. The funnel’s stages were already kept clear of the button, but a label that no longer fits inside a stage is placed against the full plot width, so on a funnel with a small top margin the first stage’s value could be painted beneath it. Only a label whose text actually falls in the button’s band is affected – labels lower down the funnel still use the full width, so nothing that was legible before is suppressed now.
The legend/actions button now sits in the top-right corner at
every container width. On containers narrower than 600px it dropped to
the bottom-right, where it covered the right-most x-axis tick label –
the bottom band there is fully occupied by rotated tick labels, the
x-axis title and the inline legend, so there was no free space for it.
Narrow axes charts that have not called setMargin() now
reserve a 48px top margin for the button so it never lands on the plot.
Blast radius: an axes chart in a container 600px wide or less that has
not called setMargin() gains 18px of top margin (30 becomes
48) and loses the same amount of plot height. Charts wider than 600px,
charts that call setMargin(), sparklines, and non-axes
charts such as funnel, sankey and treemap are unchanged.
Sparklines no longer paint a floating action button. The button covered about 8% of a 60px inline chart and sat on top of its last data points, and it intercepted the pointer over that corner so those points had no tooltip. Sparkline mode already strips axes, legend, reference lines and interactions, and now strips the button too. Blast radius: the export menu (CSV, PNG, SVG, PDF, clipboard) is no longer reachable from a sparkline; export the same data from a full-size chart. The panel’s legend was already suppressed in sparkline mode, so no legend is lost.
Legend entries for grouped layers now show the group value on its
own instead of concatenating the layer label with the group value –
"Core" rather than "Rankings — Core". The
chart title and axis labels already carry that context. This affects
addIoLayer(mapping = list(group = ...)), grouped data
frames from dplyr::group_by(), and the
regression, qq and survfit
composites. Labels set explicitly on ungrouped layers are unchanged.
When a bare group value would collide with a layer already on the chart,
the previous "<label> — <group>" form is used
automatically, so multi-layer charts keep unique labels. Code that
targets grouped layers by label – updateMyIOData(),
addKeyframe() – and any CSS or test selector pinned to a
grouped layer’s generated class name must use the new labels.
type = "ridgeline" now stacks its groups in a
deterministic order instead of the order the group values happen to
appear in the data. If the group column is a factor, its level order is
used; otherwise groups sort ascending (character columns sort in the C
locale so the result is identical on every machine). Ascending order
reads bottom-to-top, matching a discrete y axis in ggplot2. This
changes the rendering of any existing ridgeline whose group column was
not already sorted: the ridges move, the y tick labels move
with them, and because the color vector is applied by group
position, the colors re-map too. For example, a ridgeline over
mtcars with cyl as a character column
previously stacked 6, 4, 8 from the bottom and now stacks 4, 6, 8. To
keep a specific non-alphabetical order, make the group column a factor
with the levels in the order you want.
type = "boxplot", type = "violin" and
type = "comparison" now place their groups along the x axis
by the same deterministic rule ridgeline already uses, instead of by the
order the group values happen to appear in the data: a factor’s level
order wins, anything else sorts ascending, and character columns sort in
the C locale so the result is identical on every machine. This
changes the rendering of any existing boxplot, violin or comparison
chart whose group column was not already sorted – the boxes
move, the x tick labels move with them, and on a violin passed a vector
of colors the hues re-map, because color is applied by
group position. Every statistic stays with its own group: each box,
whisker, median and outlier is now looked up by group name rather than
by position, and the significance brackets on a comparison chart are
positioned by the same rule, so they keep spanning the pairs they are
labeled with. Charts whose groups already appear in sorted order –
including every factor whose rows follow its levels – render exactly as
before. To keep a specific non-alphabetical order, make the group column
a factor with the levels in the order you want.
Charts left on the default left margin now widen it automatically
when the y tick labels would run into the rotated y-axis title.
Previously a currency or large-number y format –
setAxisFormat(yAxis = "$,.0f") on three-digit data, for
example – produced labels wide enough to overlap the title. The engine
now measures the rendered tick labels and grows the left margin just
enough to clear the title band; it only ever grows, never shrinks below
the configured value, and it settles on the first render. A chart that
calls setMargin() keeps exactly the margins it was given
and is never adjusted. Charts whose labels already fit are unchanged.
Note that a widget saved as HTML by an earlier version of myIO has no
record of whether setMargin() was called, so re-rendering
it against this version may widen a too-narrow left margin.
@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.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.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.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.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().