Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export(type_ellipse)
export(type_errorbar)
export(type_function)
export(type_glm)
export(type_heatmap)
export(type_hexbin)
export(type_hist)
export(type_histogram)
Expand All @@ -51,6 +52,7 @@ export(type_spineplot)
export(type_spline)
export(type_summary)
export(type_text)
export(type_tile)
export(type_violin)
export(type_vline)
importFrom(grDevices,adjustcolor)
Expand Down Expand Up @@ -135,6 +137,7 @@ importFrom(stats,qnorm)
importFrom(stats,qt)
importFrom(stats,quantile)
importFrom(stats,reformulate)
importFrom(stats,sd)
importFrom(stats,setNames)
importFrom(stats,spline)
importFrom(stats,terms)
Expand Down
16 changes: 14 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ where the formatting is also better._

#### New plot types

- `type_hexbin()` (equivalently, `type = "hexbin"`) for hexagonal bin plots, a
2D analogue of a histogram. (#667 @grantmcdermott)
- `type_hexbin()` / `"hexbin"` for hexagonal bin plots, a 2D analogue of a
histogram. (#667 @grantmcdermott)
- `type_tile()` / `"tile"` for tile plots, i.e. a grid of rectangles whose fill
encodes a third variable. (#677 @grantmcdermott)
- `type_heatmap()` / `"heatmap"` builds on `type_tile()`, adding a `scale`
argument that scales the fill values *within* each category of one axis. This
is analogous to base R's `heatmap()` function, and like the latter it z-scores
along the chosen margin by default. (#677 @grantmcdermott)

#### Other new features

Expand Down Expand Up @@ -44,6 +50,12 @@ where the formatting is also better._
`"cat"` (console), in any combination; a destination the user has already
labelled is left alone. Shared bandwidths are reported once and named as
joint, individual bandwidths per group. (#287 @haomeng797-ship-it)
- Themes:
- `"heatmap"` provides a dedicated companion theme to the new `type_tile()`
and `type_heatmap()` types (see above). The theme removes all axis padding,
so that tiles meet the panel edge, and also rotates the tick labels against
their respective axes. Colour fills default to the "tealgrn" sequential
palette. (#677 @grantmcdermott)

### Bug fixes

Expand Down
9 changes: 6 additions & 3 deletions R/facet.R
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,13 @@ draw_facet_window = function(
)
if (!is.null(xaxb)) args_x$at = xaxb
if (!is.null(yaxb)) args_y$at = yaxb
# `xlabs` is only non-NULL when a type has placed categorical data on the
# x-axis, so its presence is the signal to draw labelled ticks.
# `xlabs`/`ylabs` are only non-NULL when a type has placed categorical data
# on that axis, so their presence is the signal to draw labelled ticks.
# The y-side previously listed the eligible types by name, but every type
# that populates `ylabs` does so precisely because it has categories to
# label, making the extra condition redundant (#665).
type_range_x = !is.null(xlabs)
type_range_y = !is.null(ylabs) && (type == "p" || (isTRUE(flip) && type %in% c("barplot", "pointrange", "errorbar", "ribbon", "boxplot", "violin")))
type_range_y = !is.null(ylabs)
if (type_range_x) {
args_x = modifyList(args_x, list(at = xlabs, labels = names(xlabs)))
}
Expand Down
3 changes: 3 additions & 0 deletions R/sanitize_type.R
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ sanitize_type = function(settings) {
"spline",
"summary",
"text",
"tile", "heatmap",
"violin",
"vline"
)
Expand Down Expand Up @@ -114,6 +115,8 @@ sanitize_type = function(settings) {
"spline" = type_spline,
"summary" = type_summary,
"text" = type_text,
"tile" = type_tile,
"heatmap" = type_heatmap,
"violin" = type_violin,
"vline" = type_vline,
type # default case (incl. line-family chars, handled below)
Expand Down
8 changes: 6 additions & 2 deletions R/tinyplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,27 @@
#' - Shapes:
#' - `"area"` / [`type_area()`]: Plots the area under the curve from `y` = 0 to `y` = f(`x`).
#' - `"errorbar"` / [`type_errorbar()`]: Adds error bars to points; requires `ymin` and `ymax`.
#' - `"jitter"` / [`type_jitter()`]: Jittered points.
#' - `"pointrange"` / [`type_pointrange()`]: Combines points with error bars.
#' - `"polygon"` / [`type_polygon()`]: Draws polygons.
#' - `"polypath"` / [`type_polypath()`]: Draws a path whose vertices are given in `x` and `y`.
#' - `"rect"` / [`type_rect()`]: Draws rectangles; requires `xmin`, `xmax`, `ymin`, and `ymax`.
#' - `"ribbon"` / [`type_ribbon()`]: Creates a filled area between `ymin` and `ymax`.
#' - `"rug"` / [`type_rug()`]: Adds a rug to an existing plot.
#' - `"segments"` / [`type_segments()`]: Draws line segments between pairs of points.
#' - `"text"` / [`type_text()`]: Add text annotations.
#' - `"tile"` / [`type_tile()`]: Draws a grid of tiles, with the fill given by `by`.
#' - Visualizations:
#' - `"barplot"` / [`type_barplot()`]: Creates a bar plot.
#' - `"boxplot"` / [`type_boxplot()`]: Creates a box-and-whisker plot.
#' - `"chull"` / [`type_chull()`]: Draws convex hull(s) around grouped points.
#' - `"density"` / [`type_density()`]: Plots the density estimate of a variable.
#' - `"ellipse"` / [`type_ellipse()`]: Draws confidence ellipse(s) around grouped points.
#' - `"heatmap"` / [`type_heatmap()`]: Draws a grid of tiles, optionally rescaling the fill along one axis.
#' - `"hexbin"` / [`type_hexbin()`]: Creates a hexagonal bin plot, a 2D analogue of a histogram.
#' - `"histogram"` / [`type_histogram()`]: Creates a histogram of a single variable.
#' - `"jitter"` / [`type_jitter()`]: Jittered points.
#' - `"qq"` / [`type_qq()`]: Creates a quantile-quantile plot.
#' - `"ridge"` / [`type_ridge()`]: Creates a ridgeline (aka joy) plot.
#' - `"rug"` / [`type_rug()`]: Adds a rug to an existing plot.
#' - `"spineplot"` / [`type_spineplot()`]: Creates a spineplot or spinogram.
#' - `"violin"` / [`type_violin()`]: Creates a violin plot.
#' - Models:
Expand Down
64 changes: 61 additions & 3 deletions R/tinyplot.matrix.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,30 @@
#' used as the group (and legend) labels. Single-column matrices are drawn as
#' a simple index plot with no grouping or legend.
#'
#' The `"tile"` and `"heatmap"` types are an exception, since the matplot
#' convention makes little sense for them. Instead the matrix is laid out as a
#' grid---columns along the x-axis, rows along the y-axis---with the matrix
#' *values* supplied as the fill. Row order is reversed so that the first row
#' sits at the top, matching how one reads a matrix (cf.
#' \code{\link[stats]{heatmap}} and \code{\link[graphics]{image}}). Both axis
#' titles are suppressed, since the dimnames already label the ticks, and so
#' is the legend, since the fill merely re-encodes the matrix's own values.
#' Pass an explicit `legend` (or `xlab`/`ylab`) to override either. See
#' Examples.
#'
#' @param x an object of class `"matrix"`.
#' @param type plot type passed on to `tinyplot`. Defaults to `"p"` (points).
#' @param legend specification passed on to `tinyplot`. The default is to draw a
#' legend when the matrix has named columns, and to suppress it otherwise.
#' legend when the matrix has named columns, and to suppress it otherwise. For
#' `"tile"` and `"heatmap"` types it is suppressed by default.
#' @param facet specification of `facet` passed on to `tinyplot`. The only
#' accepted non-`NULL` value is the `"by"` convenience string, which facets
#' the plot by matrix column.
#' @param xlab,ylab axis labels passed on to `tinyplot`. `ylab` defaults to the
#' deparsed matrix name. `xlab` defaults to `"Index"` when the matrix has no
#' row names; when it does, the row names already label the ticks so the
#' x-axis title is suppressed.
#' x-axis title is suppressed. For `"tile"` and `"heatmap"` types both
#' titles default to `NA`, since the dimnames label both axes.
#' @param ... further arguments passed to `tinyplot`.
#'
#' @returns No return value, called for the side effect of producing a plot.
Expand All @@ -36,10 +49,13 @@
#' tinyplot(VADeaths, type = "b")
#' tinyplot(VADeaths, type = "b", legend = "direct", theme = "socviz")
#' tinyplot(VADeaths, type = "b", legend = FALSE, facet = "by", theme = "socviz")
#'
#'
#' # equivalent plot to an example in `?matplot`
#' sines = outer(1:20, 1:4, function(x, y) sin(x / 20 * pi * y))
#' tinyplot(sines, type = "o", pch = "by", lty = "by", col = rainbow(ncol(sines)))
#'
#' # `"tile"` + `"heatmap"` types lay the matrix out as a grid instead
#' tinyplot(VADeaths, type = "heatmap", theme = "heatmap", col = "white")
#'
#' @export
tinyplot.matrix = function(x, type = NULL, legend = NULL, facet = NULL, xlab = NULL, ylab = NULL, ...) {
Expand All @@ -50,6 +66,48 @@ tinyplot.matrix = function(x, type = NULL, legend = NULL, facet = NULL, xlab = N
if (is.null(type)) type = "p"
dep_x = deparse1(substitute(x))
dims = dim(x)

## Tile and heatmap types need a different mapping to the matplot convention
## below: they want the matrix laid out as a grid (columns on x, rows on y)
## with the *values* supplied as the fill, rather than a series per column
## with the values on y. Detect via the resolved type name, so that both the
## convenience strings and the type_*() constructors are covered.
tname = if (inherits(type, "tinyplot_type")) type[["name"]] else type
if (is.character(tname) && length(tname) == 1L &&
tname %in% c("tile", "heatmap")) {
rnms = rownames(x)
cnms = colnames(x)
xx = if (is.null(cnms)) {
factor(rep(seq_len(dims[2]), each = dims[1]))
} else {
factor(rep(cnms, each = dims[1]), levels = cnms)
}
## Reverse the row levels so that row 1 sits at the *top* of the plot,
## matching how one reads a matrix (cf. `heatmap()`, `image()`).
yy = if (is.null(rnms)) {
factor(rep(seq_len(dims[1]), times = dims[2]),
levels = rev(seq_len(dims[1])))
} else {
factor(rep(rnms, times = dims[2]), levels = rev(rnms))
}
## Both axes are labelled by the matrix dimnames, so axis titles would be
## redundant. Ditto the legend: the fill encodes the matrix's own values, so
## a colourbar adds little for a bare `tinyplot(m, type = "heatmap")` call.
## Users who want one can still ask for it explicitly.
if (is.null(xlab)) xlab = NA
if (is.null(ylab)) ylab = NA
if (is.null(legend)) legend = FALSE
return(tinyplot.default(
x = xx, y = yy,
type = type,
by = as.vector(x),
facet = facet,
legend = legend,
xlab = xlab,
ylab = ylab,
...
))
}
if (dims[2] == 1L) {
bby = NULL
legend = FALSE
Expand Down
27 changes: 25 additions & 2 deletions R/tinytheme.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#' - `"tufte"` (*): floating axes and minimalist plot artifacts in the style of Edward Tufte.
#' - `"float"` (*): builds on `"tufte"` with outward ticks, fewer tick marks, and a "dark" qualitative palette.
#' - `"void"` (*): switches off all axes, titles, legends, etc.
#' - `"heatmap"` (*): a specialized theme for tile plots and heatmaps (see [`type_tile()`]). Builds off of `"clean2"`, but removes the axis padding so that the tiles meet the panel edge, drops the (redundant) grid lines, rotates the tick labels and removes their tick marks, and defaults to the "tealgrn" sequential palette. Not recommended for non-tile plots.
#' - `"ridge"` (*): a specialized theme for ridge plots (see [`type_ridge()`]). Builds off of `"clean"`, but adds ridge-specific tweaks (e.g. default "Zissou 1" palette for discrete colors, solid horizontal grid lines, and minor adjustments to y-axis labels). Not recommended for non-ridge plots.
#' - `"ridge2"` (*): removes the plot frame (box) from `"ridge"`, but retains the x-axis line. Again, not recommended for non-ridge plots.
#' @param ... Named arguments to override specific theme settings. These
Expand Down Expand Up @@ -194,7 +195,7 @@ tinytheme = function(
"clean", "clean2", "bw", "linedraw", "classic",
"minimal", "ipsum", "ipsum2", "dark",
"socviz", "broadsheet", "nber", "web",
"ridge", "ridge2",
"heatmap", "ridge", "ridge2",
"tufte", "float", "void"
),
...,
Expand Down Expand Up @@ -225,6 +226,7 @@ tinytheme = function(
"ipsum2" = theme_ipsum2,
"minimal" = theme_minimal,
"nber" = theme_nber,
"heatmap" = theme_heatmap,
"ridge" = theme_ridge,
"ridge2" = theme_ridge2,
"socviz" = theme_socviz,
Expand Down Expand Up @@ -300,7 +302,7 @@ builtin_themes = c(
"clean", "clean2", "bw", "linedraw", "classic",
"minimal", "ipsum", "ipsum2", "dark",
"socviz", "broadsheet", "nber", "web",
"ridge", "ridge2",
"heatmap", "ridge", "ridge2",
"tufte", "float", "void"
)

Expand Down Expand Up @@ -359,6 +361,12 @@ theme_default = list(
side.sub = 1,
tck = NA,
tcl = par("tcl"), # -0.5
# `theme_default` doubles as the reset baseline for tinytheme(), so every
# parameter that *any* theme sets has to appear here -- otherwise nothing
# restores it and the setting leaks into subsequent (incl. base) plots. The
# axis styles below are only touched by the "heatmap" theme so far.
xaxs = par("xaxs"), # "r"
yaxs = par("yaxs"), # "r"
xaxt = "standard",
yaxt = "standard"
)
Expand Down Expand Up @@ -534,6 +542,21 @@ theme_dark = modifyList(theme_minimal, list(

# derivatives of clean/clean2

# Companion theme for type_tile() / type_heatmap(). Tiles are opaque and drawn
# edge-to-edge, so the usual axis padding leaves them floating inside the panel
# and the grid is hidden behind them regardless. Long categorical labels are the
# norm for correlation matrices, hence the rotated, tick-less axes.
theme_heatmap = modifyList(theme_clean2, list(
tinytheme = "heatmap",
gap.axis = 0,
grid = FALSE,
las = 2,
palette.sequential = "tealgrn",
tcl = 0,
xaxs = "i",
yaxs = "i"
))

theme_ridge = modifyList(theme_clean, list(
tinytheme = "ridge",
col.default = "black", # keep black ridgelines; Zissou is for gradient fills
Expand Down
11 changes: 5 additions & 6 deletions R/type_hexbin.R
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,11 @@
#' # 2) Continuous grouping variable: each cell is coloured by its mean.
#' # Example: Create a long version of the `volcano` dataset, and plot its
#' # elevations onto a gridded terrain map.
#' volc = local({
#' v = setNames(stack(as.data.frame(volcano)), c("elevation", "y"))
#' v$y = as.numeric(gsub("^V", "", v$y))
#' v$x = seq_len(nrow(volcano))
#' v
#' })
#' volc = data.frame(
#' x = as.vector(row(volcano)),
#' y = as.vector(col(volcano)),
#' elevation = as.vector(volcano)
#' )
#' tinyplot(
#' y ~ x | elevation, data = volc,
#' type = "hexbin", xbins = 50,
Expand Down
Loading