diff --git a/.gitignore b/.gitignore index a7a66486..4e9c0875 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ coverage/ /screenshots /screenshots-review review-home.png -/scripts +/scripts/* +!/scripts/screenshot-all.js /md diff --git a/NAMESPACE b/NAMESPACE index 2c5be048..c84429a1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -31,6 +31,7 @@ export(setExportOptions) export(setFacet) export(setKeyframe) export(setLayerOpacity) +export(setLegendTitle) export(setLinked) export(setLinkedCursor) export(setMargin) diff --git a/NEWS.md b/NEWS.md index 30ccb72c..5694f8b2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,345 @@ +# myIO (development version) + +## New features + +* `setLegendTitle()` puts a title on the legend naming the variable its entries + come from, so a grouped chart whose series are labelled `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. + +## Bug fixes + +* 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 travelling 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 centred 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 recognise, 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 colour, 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 colour meant which category. The legend now + lists one entry per category, in data order, with swatches that match the cell + colours. + +* Bump charts now draw each series in its own colour, 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 honoured + 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-levelled 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 honour `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 labelling. + +* 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-coloured 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. + +## User-visible changes + +* 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 + `"