diff --git a/NEWS.md b/NEWS.md index c57d083a..9e6a43eb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -183,6 +183,9 @@ related to plot layering. See "Bug fixes" below. with a flat bar along the baseline) or left undrawn. The default `NULL` lets `FUN` decide; see the new "Implicit zeros and empty cells" section of `?type_barplot`. (#718 @grantmcdermott) + - `type_histogram()` now (correctly) supports mapping `by` grouping along the + `x` variable, e.g. `tinyplot(~mpg | mpg, data = mtcars, type = "hist")`. + Bar colours map to mean bin values. (#727 @grantmcdermott) - Themes: - `"heatmap"` provides a dedicated companion theme to the new `type_tile()` and `type_heatmap()` types (see above). The theme removes all axis padding, diff --git a/R/type_histogram.R b/R/type_histogram.R index ae32476f..67cc56f0 100644 --- a/R/type_histogram.R +++ b/R/type_histogram.R @@ -41,6 +41,14 @@ #' data = iris #' ) #' +#' # Special case: `by`==`x`. +#' # Here we colour each bar (bin) according to its position along the x-axis +#' tinyplot( +#' ~ Petal.Width | Petal.Width, +#' type = "histogram", +#' data = iris +#' ) +#' #' # Faceted version #' tinyplot( #' ~Petal.Width, @@ -101,21 +109,35 @@ data_histogram = function(breaks = "Sturges", hright = right fun = function(settings, .breaks = hbreaks, .freebreaks = hfree.breaks, .freq = hfreq, .right = hright, .drop.zeros = hdrop.zeros, ...) { - env2env(settings, environment(), c("palette", "bg", "col", "plot", "datapoints", "ymin", "ymax", "xmin", "xmax", "freq", "ylab", "xlab", "facet", "ribbon.alpha", "by", "null_by", "null_palette")) + env2env( + settings, + environment(), + c( + "bg", "by", "col", "datapoints", "facet", "freq", + "null_by", "null_palette", "palette", "plot", "ribbon.alpha", + "x_by", "xlab", "xmax", "xmin", "ylab", "ymax", "ymin" + ) + ) has_weights = !is.null(datapoints[["weights"]]) if (has_weights) settings$weights_used = TRUE + # `by == x`: colour each bin by its own position along the x-axis. + if (x_by) datapoints$by = "" + hbreaks = ifelse(!sapply(.breaks, is.null), .breaks, "Sturges") # Multi-group displays fill from the palette at `ribbon.alpha` - # transparency via the `ribbon.alpha` keyword. For single-group displays - # with a theme palette active we leave `bg = NULL` so the fill tracks the + # transparency via the `ribbon.alpha` keyword. The transparency is there + # so that overlapping groups show through one another, which `x_by` bins + # never do (one group per bin) -- it would only wash out the gradient, so + # those fill from the palette outright. For single-group displays with a + # theme palette active we leave `bg = NULL` so the fill tracks the # resolved border colour (see by_bg), which honours `col.default`. With # no theme palette, single-group uses the neutral "lightgray" shared by # all single-group area fills (matches base R hist()). if (is.null(bg) && !null_by) { - bg = ribbon.alpha + bg = if (x_by) "by" else ribbon.alpha } else if (is.null(bg) && null_by && null_palette && is.null(get_tpar("palette.qualitative", default = NULL))) { bg = "lightgray" } @@ -147,7 +169,9 @@ data_histogram = function(breaks = "Sturges", } freq = if (!is.null(.freq)) .freq else is.null(.freq) && h$equidist out = data.frame( - by = k$by[1], # already split + # `x_by`: one group per bin, valued at the bin's midpoint, so + # that the colours track the x-axis (see above) + by = if (x_by) h$mids else k$by[1], # already split facet = k$facet[1], # already split ymin = 0, ymax = if (freq) h$counts else h$density, @@ -169,7 +193,9 @@ data_histogram = function(breaks = "Sturges", ymax = datapoints$ymax xmin = datapoints$xmin xmax = datapoints$xmax - by = if (length(unique(datapoints$by)) == 1) by else datapoints$by + # `x_by` keeps the per-bin values even if a single bin survives, since + # the original `by` vector is no longer the right length (or scale). + by = if (!x_by && length(unique(datapoints$by)) == 1) by else datapoints$by facet = if (length(unique(datapoints$facet)) == 1) facet else datapoints$facet # legend customizations diff --git a/inst/tinytest/_tinysnapshot/hist_by_equals_x.svg b/inst/tinytest/_tinysnapshot/hist_by_equals_x.svg new file mode 100644 index 00000000..caea6b42 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/hist_by_equals_x.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + 15 + 20 + 25 + 30 + + + + + + + + +mpg + + + + + + + +mpg +Frequency + + + + + + + + + +10 +15 +20 +25 +30 +35 + + + + + + + + +0 +2 +4 +6 +8 +10 +12 + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/hist_by_equals_x_faceted.svg b/inst/tinytest/_tinysnapshot/hist_by_equals_x_faceted.svg new file mode 100644 index 00000000..af7ee211 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/hist_by_equals_x_faceted.svg @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + 0.5 + 1.0 + 1.5 + 2.0 + 2.5 + + + + + + + + + + +Petal.Width + + + + + + + +Petal.Width +Frequency + + + + + + + + + + + + + + + + +0.0 +0.5 +1.0 +1.5 +2.0 +2.5 + + + + + + + + + +0 +5 +10 +15 +20 +25 +30 +35 + +setosa + + + + + + + + + + + + + + + + + +0.0 +0.5 +1.0 +1.5 +2.0 +2.5 + + + + + + + + + +0 +5 +10 +15 +20 +25 +30 +35 + +versicolor + + + + + + + + + + + + + + + + + +0.0 +0.5 +1.0 +1.5 +2.0 +2.5 + + + + + + + + + +0 +5 +10 +15 +20 +25 +30 +35 + +virginica + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-type_histogram.R b/inst/tinytest/test-type_histogram.R index 7edb10fd..6dd2c347 100644 --- a/inst/tinytest/test-type_histogram.R +++ b/inst/tinytest/test-type_histogram.R @@ -100,3 +100,22 @@ f = function() { tinyplot(~Sepal.Length, data = iris, type = "histogram", weights = Petal.Width) } expect_snapshot_plot(f, label = "hist_weights") + + +# `by == x`: each bin is coloured by its own position along the x-axis, rather +# than every distinct x value being binned (and overplotted) as a group of its +# own. (#725) +f = function() { + tinyplot(~mpg | mpg, data = mtcars, type = "hist") +} +expect_snapshot_plot(f, label = "hist_by_equals_x") + +f = function() { + tinyplot( + ~Petal.Width | Petal.Width, data = iris, + facet = ~Species, facet.args = list(nrow = 1), + type = "hist" + ) +} +expect_snapshot_plot(f, label = "hist_by_equals_x_faceted") + diff --git a/man/type_histogram.Rd b/man/type_histogram.Rd index 02115fbf..aabe0113 100644 --- a/man/type_histogram.Rd +++ b/man/type_histogram.Rd @@ -78,6 +78,14 @@ tinyplot( data = iris ) +# Special case: `by`==`x`. +# Here we colour each bar (bin) according to its position along the x-axis +tinyplot( + ~ Petal.Width | Petal.Width, + type = "histogram", + data = iris +) + # Faceted version tinyplot( ~Petal.Width,