diff --git a/DESCRIPTION b/DESCRIPTION index f12e63e..69fedea 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: statgl Title: Statistics Greenland R Package -Version: 0.5.2.9000 +Version: 0.5.2.9001 Authors@R: c( person("Emil", "Malta", , "emim@stat.gl", role = c("aut", "cre")), person("Alexander", "Krabbe", , "alkr@stat.gl", role = "aut") diff --git a/NEWS.md b/NEWS.md index 8968650..4fd1056 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,14 @@ # statgl 0.5.2.9000 +* `statgl_plot()`'s `highlight` argument no longer collapses non-matched + series to neutral grey on grouped line/area charts. Instead, the + resolved `palette` is preserved and non-matched series are dimmed via + alpha (`0.2`), while matched series keep full opacity and get a + thicker stroke (`lineWidth = 4`) plus foreground `zIndex`. Reads + much better in dark mode and pairs naturally with palettes like + `"dawn"`. Ungrouped bar/column highlight is unchanged + (orange-on-grey). * `statgl_plot()` gains a `position` argument for bar/column charts: `"stack"`, `"percent"`, or `"dodge"` (bars side-by-side within each category). Supersedes `stacking` when both are set. diff --git a/R/statgl_plot.R b/R/statgl_plot.R index 16d28fd..900f1f6 100644 --- a/R/statgl_plot.R +++ b/R/statgl_plot.R @@ -108,16 +108,19 @@ #' and `height` is not passed explicitly, height is scaled up to as tall as #' is allowed. #' @param highlight Optional character vector of labels to visually -#' emphasise. Matching elements are drawn in the Statgl accent orange -#' (`#faa41a`); everything else is drawn in neutral grey (`#d3d3d3`). -#' Overrides `palette` when set. Dispatch depends on chart shape: +#' emphasise. Dispatch depends on chart shape: #' * **Grouped chart** (`group =` supplied): `highlight` matches against -#' series names (the `group` values). Line/area types additionally get -#' a thicker stroke and a higher `zIndex` so the highlighted series -#' sits in the foreground. +#' series names (the `group` values). Matching series keep their +#' palette colour at full opacity; non-matching series are drawn in +#' the same palette colour at reduced alpha (`0.2`) so the chart +#' keeps its palette identity rather than collapsing to grey. +#' Line/area types additionally get a thicker stroke (`lineWidth = 4`) +#' and a higher `zIndex` so the highlighted series sits in the +#' foreground. #' * **Ungrouped bar / column chart**: `highlight` matches against the -#' `x` values, re-colouring individual bars. Useful for emphasising -#' one district, commodity, etc. on a per-bar chart. +#' `x` values, drawing matched bars in Statgl accent orange +#' (`#faa41a`) and the rest in neutral grey (`#d3d3d3`). Useful for +#' emphasising one district, commodity, etc. on a per-bar chart. #' * **Anything else ungrouped** (line, scatter, ...): no-op with a #' warning, since there's nothing series- or bar-shaped to single out. #' @@ -709,10 +712,14 @@ statgl_plot <- function( } # --- palette --------------------------------------------------- - # When `highlight` is set we colour the chart ourselves below, so skip - # the palette pass entirely. That way users can pass the default - # `palette = "main"` together with `highlight` without conflict. - if (!is.null(palette) && is.null(highlight)) { + # Palette runs even when `highlight` is set for grouped charts; the + # highlight pass below then dims non-matched series via alpha rather + # than recolouring everything to grey, so the chart keeps its palette + # identity. Ungrouped bar/column highlight still uses orange/grey, so + # skip palette in that specific case to avoid wasted work. + skip_palette_for_highlight <- !is.null(highlight) && + !has_group && type %in% c("bar", "column") + if (!is.null(palette) && !skip_palette_for_highlight) { series_list <- chart$x$hc_opts$series if (is.null(series_list)) series_list <- list() @@ -828,10 +835,10 @@ statgl_plot <- function( } # --- highlight ------------------------------------------------- - # Re-colour the chart so values in `highlight` are the Statgl accent - # orange and everything else is neutral grey. Dispatch by shape: - # * grouped chart -> match against series names (per-series colour) - # * ungrouped bar/column -> match against x values (per-point colour) + # Emphasise values in `highlight`. Dispatch by shape: + # * grouped chart -> keep palette colour, dim non-matched via + # alpha, thicken stroke on matched + # * ungrouped bar/column -> orange for matched, grey for rest # * anything else -> warn (nothing meaningful to single out) if (!is.null(highlight) && has_fill_group) { warning( @@ -842,9 +849,14 @@ statgl_plot <- function( highlight_set <- as.character(highlight) highlight_color <- "#faa41a" neutral_color <- "#d3d3d3" + dim_alpha <- 0.2 + highlight_lw <- 4 if (has_group) { # --- per-series highlight ---------------------------------- + # Keep palette colours; dim non-matched series via alpha so the + # chart retains its colour identity. Highlighted series get full + # opacity plus a thicker stroke (lines/areas) and foreground zIndex. series_list <- chart$x$hc_opts$series if (is.null(series_list)) { series_list <- list() @@ -869,21 +881,43 @@ statgl_plot <- function( ) } - cols <- ifelse(matched, highlight_color, neutral_color) - chart <- highcharter::hc_colors(chart, cols) + # Pull the resolved palette colours the palette pass put in place. + # Falls back to Highcharts defaults if no palette resolved (e.g. + # palette = NULL or an unknown name). + chart_colors <- chart$x$hc_opts$colors + if (is.null(chart_colors) || length(chart_colors) == 0L) { + chart_colors <- c( + "#2caffe", "#544fc5", "#00e272", "#fe6a35", "#6b8abc", + "#d568fb", "#2ee0ca", "#fa4b42", "#feb56a", "#91e8e1" + ) + } + chart_colors <- rep_len(unlist(chart_colors), max(length(series_list), 1L)) + + is_line_like <- type %in% c("line", "spline", "area", "areaspline") + + for (i in seq_along(series_list)) { + base_col <- if (!is.null(series_list[[i]]$color)) { + series_list[[i]]$color + } else { + chart_colors[[i]] + } - # Emphasise highlighted lines / areas: thicker stroke + foreground. - if (type %in% c("line", "spline", "area", "areaspline")) { - for (i in seq_along(series_list)) { - if (isTRUE(matched[i])) { - series_list[[i]]$lineWidth <- 3 + if (isTRUE(matched[i])) { + series_list[[i]]$color <- base_col + if (is_line_like) { + series_list[[i]]$lineWidth <- highlight_lw series_list[[i]]$zIndex <- 5 - } else { - series_list[[i]]$zIndex <- 1 + } + } else { + series_list[[i]]$color <- grDevices::adjustcolor( + base_col, alpha.f = dim_alpha + ) + if (is_line_like) { + series_list[[i]]$zIndex <- 1 } } - chart$x$hc_opts$series <- series_list } + chart$x$hc_opts$series <- series_list } else if (type %in% c("bar", "column")) { # --- per-point highlight on an ungrouped bar/column chart --- diff --git a/man/statgl_crosstable.Rd b/man/statgl_crosstable.Rd index 7c362cb..20ba4d3 100644 --- a/man/statgl_crosstable.Rd +++ b/man/statgl_crosstable.Rd @@ -24,6 +24,7 @@ statgl_crosstable( .replace_nas = NULL, .first_col_width = NULL, .bottom_rule = TRUE, + .footnote = NULL, .as_html = FALSE ) } @@ -117,6 +118,16 @@ bottom border on the last data row. Matches the Statistics Greenland design convention. Same semantics as \code{\link[=statgl_table]{statgl_table()}}'s \code{.bottom_rule}.} +\item{.footnote}{Optional footnote(s) appended below the table. \code{NULL} +(default) adds nothing. Supply a character vector; \strong{names} become +bold titles and values become the note text: + +\if{html}{\out{
}}\preformatted{.footnote = c(`Note` = "Values are preliminary.", + `Source` = "Statistics Greenland.") +}\if{html}{\out{
}} + +An unnamed scalar (\code{"Text only"}) is also accepted.} + \item{.as_html}{Logical; if \code{FALSE} (default), returns an \code{htmlwidget} that renders correctly in every context — the RStudio Viewer when called interactively, knitr / Quarto chunks, diff --git a/man/statgl_plot.Rd b/man/statgl_plot.Rd index 0c11c23..4187024 100644 --- a/man/statgl_plot.Rd +++ b/man/statgl_plot.Rd @@ -24,12 +24,14 @@ statgl_plot( decimal.mark = ",", locale = NULL, stacking = NULL, + position = NULL, palette = "main", palette_reverse = FALSE, pyramid = NULL, highlight = NULL, height = 300, legend_position = "bottom", + download_data = TRUE, ... ) } @@ -50,8 +52,12 @@ values \item{name}{Optional series name passed to \code{\link[highcharter:hchart]{highcharter::hchart()}}.} -\item{group}{Optional bare column name used to split data into series -(\code{highcharter::hcaes(group = ...)}).} +\item{group}{Optional bare column name, or a two-variable expression +\code{c(g1, g2)}, used to split data into series. When a single name is +supplied the behaviour is unchanged. When \code{c(g1, g2)} is supplied, \code{g1} +is the pyramid-split variable (determines left/right side) and \code{g2} is +the fill dimension: each \code{g2} value becomes a sub-bar with a consistent +colour across both pyramid sides. See \code{position} for layout control.} \item{title, subtitle, caption}{Optional text annotations added via \code{\link[highcharter:hc_title]{highcharter::hc_title()}}, \code{\link[highcharter:hc_subtitle]{highcharter::hc_subtitle()}} and @@ -89,7 +95,15 @@ they are derived from \code{locale} (\code{"da"}/\code{"kl"} -> decimal \code{", \code{"."}; other values -> decimal \code{"."}, big mark \code{","}).} \item{stacking}{Optional stacking mode for \code{"area"}, \code{"column"} and \code{"bar"} -charts. One of \code{"normal"} or \code{"percent"}, or \code{NULL} (no stacking).} +charts. One of \code{"normal"} or \code{"percent"}, or \code{NULL} (no stacking). +Superseded by \code{position} when both are set.} + +\item{position}{Sub-bar positioning for \code{"bar"} / \code{"column"} charts. +One of \code{"stack"} (\code{stacking = "normal"}), \code{"percent"}, or \code{"dodge"} +(bars placed side-by-side within each category). \code{NULL} (default) defers +to \code{stacking}. When both are set, \code{position} wins. In pyramid mode with +\code{group = c(g1, g2)}, \code{"dodge"} reverses the \code{g2} order on the left side +so sub-bars mirror symmetrically around zero.} \item{palette}{Optional palette specification for the series colours. Either: \itemize{ @@ -147,17 +161,20 @@ and \code{height} is not passed explicitly, height is scaled up to as tall as is allowed.} \item{highlight}{Optional character vector of labels to visually -emphasise. Matching elements are drawn in the Statgl accent orange -(\verb{#faa41a}); everything else is drawn in neutral grey (\verb{#d3d3d3}). -Overrides \code{palette} when set. Dispatch depends on chart shape: +emphasise. Dispatch depends on chart shape: \itemize{ \item \strong{Grouped chart} (\verb{group =} supplied): \code{highlight} matches against -series names (the \code{group} values). Line/area types additionally get -a thicker stroke and a higher \code{zIndex} so the highlighted series -sits in the foreground. +series names (the \code{group} values). Matching series keep their +palette colour at full opacity; non-matching series are drawn in +the same palette colour at reduced alpha (\code{0.2}) so the chart +keeps its palette identity rather than collapsing to grey. +Line/area types additionally get a thicker stroke (\code{lineWidth = 4}) +and a higher \code{zIndex} so the highlighted series sits in the +foreground. \item \strong{Ungrouped bar / column chart}: \code{highlight} matches against the -\code{x} values, re-colouring individual bars. Useful for emphasising -one district, commodity, etc. on a per-bar chart. +\code{x} values, drawing matched bars in Statgl accent orange +(\verb{#faa41a}) and the rest in neutral grey (\verb{#d3d3d3}). Useful for +emphasising one district, commodity, etc. on a per-bar chart. \item \strong{Anything else ungrouped} (line, scatter, ...): no-op with a warning, since there's nothing series- or bar-shaped to single out. } @@ -172,6 +189,11 @@ Nuuk bar; \code{statgl_plot(df, time, value, group = commodity, highlight = "I a \code{"bottom"} (default), \code{"left"}, \code{"right"}. Any other value (e.g. \code{"none"}, \code{NULL}, \code{FALSE}) hides the legend.} +\item{download_data}{Logical; defaults \code{TRUE}. When \code{TRUE}, loads +Highcharts' \code{export-data.js} module and adds a context-menu button with +options to download the chart data as CSV, XLS, or view the data table +inline. Set to \code{FALSE} to suppress the export menu entirely.} + \item{...}{Additional arguments forwarded to \code{\link[highcharter:hchart]{highcharter::hchart()}}. Names that collide with arguments \code{statgl_plot()} already sets (\code{object}, \code{type}, \code{mapping}, \code{name}) are silently ignored so the diff --git a/man/statgl_table.Rd b/man/statgl_table.Rd index f7ed072..0858058 100644 --- a/man/statgl_table.Rd +++ b/man/statgl_table.Rd @@ -21,6 +21,7 @@ statgl_table( .bottom_rule = TRUE, .caption = NULL, .bold_rows = NULL, + .footnote = NULL, .as_html = FALSE ) } @@ -82,6 +83,16 @@ printed table (after \code{.row_group} is stripped out), or formatted data frame; it must return a logical vector. }} +\item{.footnote}{Optional footnote(s) appended below the table. \code{NULL} +(default) adds nothing. Supply a character vector; \strong{names} become +bold titles and values become the note text: + +\if{html}{\out{
}}\preformatted{.footnote = c(`Note` = "Values are preliminary.", + `Source` = "Statistics Greenland.") +}\if{html}{\out{
}} + +An unnamed scalar (\code{"Text only"}) is also accepted.} + \item{.as_html}{Logical; if \code{FALSE} (default), returns an \code{htmlwidget} that renders correctly in every context — the RStudio Viewer when called interactively, knitr / Quarto chunks,