R/geom-table.R

Defines functions ttheme_set geom_table_npc geom_table

Documented in geom_table geom_table_npc ttheme_set

#' Inset tables
#'
#' \code{geom_table} and \code{geom_table_npc} add data frames as table insets
#' to the base ggplot, using syntax similar to that of
#' \code{\link[ggplot2]{geom_text}} and \code{\link{geom_text_s}}. In most
#' respects they behave as any other ggplot geometry: they add a layer
#' containing one or more grobs and grouping and faceting works as usual. The
#' most common use of \code{geom_table} is to add data labels that are whole
#' tables rather than text. \code{\link{geom_table_npc}} is used to add tables
#' as annotations to plots, but contrary to layer function \code{annotate},
#' \code{\link{geom_table_npc}} is data driven and respects grouping and facets,
#' thus plot insets can differ among panels.
#'
#' @details By default \code{geom_table()} uses \code{\link{position_nudge_center}} which is
#'   backwards compatible with \code{\link[ggplot2]{position_nudge}} but
#'   provides additional control on the direction of the nudging. In contrast to
#'   \code{\link[ggplot2]{position_nudge}}, \code{\link{position_nudge_center}}
#'   and all other position functions defined in packages 'ggpp' and 'ggrepel'
#'   keep the original coordinates thus allowing the plotting of connecting
#'   segments and arrows.
#'
#'   This geom works only with tibbles as \code{data}, as its expects a list of
#'   data frames (or tibbles) to be mapped to the \code{label} aesthetic. A
#'   table is built with function \code{gridExtra::gtable} for each data frame
#'   in the list, and formatted according to a table theme or \code{ttheme}. The
#'   character strings in the data frame can be parsed into R expressions so the
#'   inset tables can include maths.
#'
#'   If the argument passed to \code{table.theme} is a constructor function
#'   (passing its name without parenthesis), the values mapped to \code{size},
#'   \code{colour}, \code{fill}, \code{alpha}, and \code{family} aesthetics will
#'   the passed to this theme constructor for each individual table. In
#'   contrast, if a ready constructed \code{ttheme} stored as a list object is
#'   passed as argument (e.g., by calling the constructor, using constructor
#'   name followed by parenthesis), it will be used as is, i.e., mappings to
#'   aesthetics such as \code{colour} are ignored if present. By default the
#'   constructor \code{ttheme_gtdefault} is used and \code{colour} and
#'   \code{fill}, are mapped to \code{NA}. Mapping these aesthetics to \code{NA}
#'   triggers the use of the default \code{base_colour} of the \code{ttheme}. As
#'   the table is built with function \code{gridExtra::gtable()}, for formatting
#'   details, please, consult \code{\link[gridExtra]{tableGrob}}.
#'
#'   The \code{x} and \code{y} aesthetics determine the position of the whole
#'   inset table, similarly to that of a text label, justification is
#'   interpreted as indicating the position of the inset table with respect to
#'   its \emph{horizontal} and \emph{vertical} axes (rows and columns in the
#'   data frame), and \code{angle} is used to rotate the inset table as a whole.
#'
#'   Of these two geoms only \code{\link{geom_grob}} supports the plotting of
#'   segments, as \code{\link{geom_grob_npc}} uses a coordinate system that is
#'   unrelated to data units and data.In the case of \code{geom_table_npc},
#'   \code{npcx} and \code{npcy} aesthetics determine the position of the inset
#'   table. Justification as described above for .
#'
#' @inheritSection geom_text_s Alignment
#'
#' @inheritSection geom_text_s Position functions
#'
#' @inheritSection geom_grob Plot boundaries and clipping
#'
#' @note Complex tables with annotations or different colouring of rows or cells
#'   can be constructed with functions in package 'gridExtra' or in any other
#'   way as long as they can be saved as grid graphical objects and then added
#'   to a ggplot as a new layer with \code{\link{geom_grob}}.
#'
#' @inherit geom_grob note return seealso references
#'
#' @seealso Formatting of tables \code{stat_fmt_table},
#'   \code{\link{ttheme_gtdefault}}, \code{\link{ttheme_set}},
#'   \code{\link[gridExtra]{tableGrob}}.
#'
#' @param mapping The aesthetic mapping, usually constructed with
#'   \code{\link[ggplot2]{aes}}. Only needs to be set at the layer level if you
#'   are overriding the plot defaults.
#' @param data A layer specific data set - only needed if you want to override
#'   the plot defaults.
#' @param stat The statistical transformation to use on the data for this layer,
#'   as a string.
#' @param na.rm If \code{FALSE} (the default), removes missing values with a
#'   warning.  If \code{TRUE} silently removes missing values.
#' @param position Position adjustment, either as a string, or the result of a
#<'   call to a position adjustment function.
#' @param ... other arguments passed on to \code{\link[ggplot2]{layer}}. This
#'   can include aesthetics whose values you want to set, not map. See
#'   \code{\link[ggplot2]{layer}} for more details.
#' @param table.theme NULL, list or function A gridExtra ttheme defintion, or
#'   a constructor for a ttheme or NULL for default.
#' @param table.rownames,table.colnames logical flag to enable or disable
#'   printing of row names and column names.
#' @param table.hjust numeric Horizontal justification for the core and column
#'   headings of the table.
#' @param parse If TRUE, the labels will be parsed into expressions and
#'   displayed as described in \code{?plotmath}.
#' @param show.legend logical. Should this layer be included in the legends?
#'   \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE}
#'   never includes, and \code{TRUE} always includes.
#' @param inherit.aes If \code{FALSE}, overrides the default aesthetics, rather
#'   than combining with them. This is most useful for helper functions that
#'   define both data and aesthetics and shouldn't inherit behaviour from the
#'   default plot specification, e.g. \code{\link[ggplot2]{borders}}.
#' @param nudge_x,nudge_y Horizontal and vertical adjustments to nudge the
#'   starting position of each text label. The units for \code{nudge_x} and
#'   \code{nudge_y} are the same as for the data units on the x-axis and y-axis.
#' @param default.colour,default.color A colour definition to use for elements not targeted by
#'   the colour aesthetic.
#' @param colour.target,color.target A vector of character strings; \code{"all"},
#'   \code{"box"} and \code{"segment"} or \code{"none"}.
#' @param default.alpha numeric in [0..1] A transparency value to use for
#'   elements not targeted by the alpha aesthetic.
#' @param alpha.target A vector of character strings; \code{"all"},
#'   \code{"segment"}, \code{"box"}, \code{"box.line"}, and
#'   \code{"box.fill"} or \code{"none"}.
#' @param add.segments logical Display connecting segments or arrows between
#'   original positions and displaced ones if both are available.
#' @param box.padding,point.padding numeric By how much each end of the segments
#'   should shortened in mm.
#' @param segment.linewidth numeric Width of the segments or arrows in mm.
#' @param min.segment.length numeric Segments shorter that the minimum length
#'   are not rendered, in mm.
#' @param arrow specification for arrow heads, as created by
#'   \code{\link[grid]{arrow}}
#'
#' @references This geometry is inspired on answers to two questions in
#'   Stackoverflow. In contrast to these earlier examples, the current geom
#'   obeys the grammar of graphics, and attempts to be consistent with the
#'   behaviour of 'ggplot2' geometries.
#'   \url{https://stackoverflow.com/questions/12318120/adding-table-within-the-plotting-region-of-a-ggplot-in-r}
#'   \url{https://stackoverflow.com/questions/25554548/adding-sub-tables-on-each-panel-of-a-facet-ggplot-in-r?}
#'
#' @family geometries adding layers with insets
#'
#' @export
#'
#' @examples
#' library(dplyr)
#' library(tibble)
#'
#' mtcars %>%
#'   group_by(cyl) %>%
#'   summarize(wt = mean(wt), mpg = mean(mpg)) %>%
#'   ungroup() %>%
#'   mutate(wt = sprintf("%.2f", wt),
#'          mpg = sprintf("%.1f", mpg)) -> tb
#'
#' df <- tibble(x = 5.45, y = 34, tb = list(tb))
#'
#' # using defaults
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df,
#'              aes(x = x, y = y, label = tb))
#'
#' ggplot(mtcars,
#'        aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df,
#'              aes(x = x, y = y, label = tb),
#'              table.rownames = TRUE,
#'              table.theme = ttheme_gtstripes)
#'
#' # settings aesthetics to constants
#' ggplot(mtcars,
#'        aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df,
#'              aes(x = x, y = y, label = tb),
#'              color = "red", fill = "#FFCCCC",
#'              family = "serif", size = 5,
#'              angle = 90, vjust = 0)
#'
#' # passing a theme constructor as argument
#' ggplot(mtcars,
#'        aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df,
#'              aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtminimal) +
#'   theme_classic()
#'
#' df2 <- tibble(x = 5.45,
#'               y = c(34, 29, 24),
#'               x1 = c(2.29, 3.12, 4.00),
#'               y1 = c(26.6, 19.7, 15.1),
#'               cyl = c(4, 6, 8),
#'               tb = list(tb[1, 1:3], tb[2, 1:3], tb[3, 1:3]))
#'
#' # mapped aesthetics
#' ggplot(mtcars,
#'        aes(wt, mpg, color = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df2,
#'              inherit.aes = TRUE,
#'              mapping = aes(x = x, y = y, label = tb))
#'
#' # nudging and segments
#' ggplot(mtcars,
#'        aes(wt, mpg, color = factor(cyl))) +
#'   geom_point(show.legend = FALSE) +
#'   geom_table(data = df2,
#'              inherit.aes = TRUE,
#'              mapping = aes(x = x1, y = y1, label = tb),
#'              nudge_x = 0.7, nudge_y = 3,
#'              vjust = 0.5, hjust = 0.5,
#'              arrow = arrow(length = unit(0.5, "lines"))) +
#'   theme_classic()
#'
#' # Using native plot coordinates instead of data coordinates
#' dfnpc <- tibble(x = 0.95, y = 0.95, tb = list(tb))
#'
#' ggplot(mtcars,
#'        aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table_npc(data = dfnpc,
#'                  aes(npcx = x, npcy = y, label = tb))
#'
geom_table <- function(mapping = NULL, data = NULL,
                       stat = "identity", position = "identity",
                       ...,
                       nudge_x = 0,
                       nudge_y = 0,
                       default.colour = "black",
                       default.color = default.colour,
                       colour.target = "box",
                       color.target = colour.target,
                       default.alpha = 1,
                       alpha.target = "all",
                       add.segments = TRUE,
                       box.padding = 0.25,
                       point.padding = 1e-06,
                       segment.linewidth = 0.5,
                       min.segment.length = 0,
                       arrow = NULL,
                       table.theme = NULL,
                       table.rownames = FALSE,
                       table.colnames = TRUE,
                       table.hjust = 0.5,
                       parse = FALSE,
                       na.rm = FALSE,
                       show.legend = FALSE,
                       inherit.aes = FALSE) {

  colour.target <-
    rlang::arg_match(color.target,
                     values = c("segment", "all", "box", "none"),
                     multiple = TRUE)
  alpha.target <-
    rlang::arg_match(alpha.target,
                     values = c("segment", "all", "box",
                                "box.line", "box.fill", "none"),
                     multiple = TRUE)

  if (!missing(nudge_x) || !missing(nudge_y)) {
    if (!missing(position) && position != "identity") {
      rlang::abort("You must specify either `position` or `nudge_x`/`nudge_y`.")
    }
    # We do not keep the original positions if they will not be used
    position <-
      position_nudge_center(nudge_x, nudge_y,
                            kept.origin = ifelse(add.segments,
                                                 "original", "none"))
  }

  if (is.character(table.hjust)) {
    table.hjust <- switch(table.hjust,
                          left = 0,
                          middle = 0.5,
                          center = 0.5,
                          right = 1,
                          0.5)
  }

  ggplot2::layer(
    data = data,
    mapping = mapping,
    stat = stat,
    geom = GeomTable,
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(
      default.colour = default.color,
      colour.target = colour.target,
      default.alpha = default.alpha,
      alpha.target = alpha.target,
      add.segments = add.segments,
      box.padding = box.padding,
      point.padding = point.padding,
      segment.linewidth = segment.linewidth,
      min.segment.length = min.segment.length,
      arrow = arrow,
      table.theme = table.theme,
      table.rownames = table.rownames,
      table.colnames = table.colnames,
      table.hjust = table.hjust,
      parse = parse,
      na.rm = na.rm,
      ...
    )
  )
}

#' @rdname ggpp-ggproto
#' @format NULL
#' @usage NULL
#' @export
GeomTable <-
  ggplot2::ggproto("GeomTable", ggplot2::Geom,
          required_aes = c("x", "y", "label"),

          default_aes = ggplot2::aes(
            colour = NA,
            fill = NA,
            size = 3.2,
            angle = 0,
            hjust = "inward",
            vjust = "inward",
            alpha = 1,
            family = "",
            fontface = 1,
            lineheight = 1.2
          ),

          draw_panel = function(data,
                                panel_params,
                                coord,
                                add.segments = TRUE,
                                box.padding = 0.25,
                                point.padding = 1e-06,
                                segment.linewidth = 1,
                                min.segment.length = 0,
                                arrow = NULL,
                                table.theme = NULL,
                                table.rownames = FALSE,
                                table.colnames = TRUE,
                                table.hjust = 0.5,
                                parse = FALSE,
                                default.colour = "black",
                                colour.target = "all",
                                default.alpha = 1,
                                alpha.target = "all",
                                na.rm = FALSE) {

            if (nrow(data) == 0) {
              return(grid::nullGrob())
            }

            if (!is.data.frame(data$label[[1]])) {
              warning("Skipping as object mapped to 'label' is not a list of ",
                      "\"tibble\" or \"data.frame\" objects.")
              return(grid::nullGrob())
            }

            add.segments <- add.segments && all(c("x_orig", "y_orig") %in% colnames(data))

            # should be called only once!
            data <- coord$transform(data, panel_params)
            if (add.segments) {
              data_orig <- data.frame(x = data$x_orig, y = data$y_orig)
              data_orig <- coord$transform(data_orig, panel_params)
              data$x_orig <- data_orig$x
              data$y_orig <- data_orig$y
            }

            if (is.character(data$vjust)) {
              data$vjust <-
                compute_just2d(data = data,
                               coord = coord,
                               panel_params = panel_params,
                               just = data$vjust,
                               a = "y", b = "x")
            }
            if (is.character(data$hjust)) {
              data$hjust <-
                compute_just2d(data = data,
                               coord = coord,
                               panel_params = panel_params,
                               just = data$hjust,
                               a = "x", b = "y")
            }
            if (add.segments) {
              segments.data <-
                shrink_segments(data,
                                point.padding = point.padding,
                                box.padding = box.padding,
                                min.segment.length = min.segment.length)
            }

            # replace NULL with default
            if (is.null(table.theme)) {
              table.theme <-
                getOption("ggpmisc.ttheme.default", default = ttheme_gtdefault)
            }

            # loop needed as gpar is not vectorized
            all.grobs <- grid::gList()

            for (row.idx in 1:nrow(data)) {
              row <- data[row.idx, , drop = FALSE]
              table.alpha <-
                ifelse(any(alpha.target %in% c("all", "table")),
                       row$alpha, default.alpha)
              segment.alpha <-
                ifelse(any(alpha.target %in% c("all", "segment")),
                       row$alpha, default.alpha)

              # Build the table
              if (is.function(table.theme)) {
                # tableGrob puts all the padding on the same side unless just = 0.5
                # this makes it difficult to compute a suitable value for table.x
                # without knowing the width of the column. The code here at least
                # ensures that whatever its length the whole text is always displayed.
                table.x <- table.hjust
                if (is.na(row$fill)) {
                  core.params <-
                    list(fg_params = list(hjust = table.hjust, x = table.x))
                } else {
                  core.params <-
                    list(fg_params = list(hjust = table.hjust, x = table.x),
                         bg_params = list(fill = row$fill))
                }
                if (is.na(row$colour)) {
                  # use theme's default base_colour
                  this.table.theme <-
                    table.theme(base_size = row$size * .pt,
                                base_family = row$family,
                                parse = parse,
                                rowhead = list(fg_params = list(hjust = 1, x = 0.9)),
                                colhead = list(fg_params = list(hjust = table.hjust,
                                                                x = table.x)),
                                core = core.params)
                } else {
                  this.table.theme <-
                    # use colour from data$colour
                    table.theme(base_size = row$size * .pt,
                                base_colour = ggplot2::alpha(row$colour, row$alpha),
                                base_family = row$family,
                                parse = parse,
                                rowhead = list(fg_params = list(hjust = 1, x = 0.9)),
                                colhead = list(fg_params = list(hjust = table.hjust,
                                                                x = table.x)),
                                core = core.params)
                }
              } else if (is.list(table.theme)) {
                this.table.theme <- table.theme
              }
              table.tb <- data[["label"]][[row.idx]]
              user.grob <-
                gridExtra::tableGrob(
                  d = table.tb,
                  theme = this.table.theme,
                  rows = if (table.rownames) rownames(table.tb) else NULL,
                  cols = if (table.colnames) colnames(table.tb) else NULL
                )

              user.grob$vp <-
                grid::viewport(x = grid::unit(row$x, "native"),
                               y = grid::unit(row$y, "native"),
                               width = sum(user.grob$widths),
                               height = sum(user.grob$heights),
                               just = c(row$hjust, row$vjust),
                               angle = row$angle,
                               name = paste("inset.table.vp", row$PANEL,
                                            "row", row.idx, sep = "."))

              # give unique name to each grob
              user.grob$name <- paste("inset.table", row.idx, sep = ".")

              if (add.segments) {
                segment.row <- segments.data[row.idx, , drop = FALSE]
                if (segment.row$too.short) {
                  segment.grob <- grid::nullGrob()
                } else {
                  segment.grob <-
                    grid::segmentsGrob(x0 = segment.row$x,
                                       y0 = segment.row$y,
                                       x1 = segment.row$x_orig,
                                       y1 = segment.row$y_orig,
                                       arrow = arrow,
                                       gp = grid::gpar(
                                         col = if (segment.linewidth == 0) NA else # lwd = 0 is invalid in 'grid'
                                           ifelse(any(colour.target %in% c("all", "segment")),
                                                  ggplot2::alpha(row$colour, segment.alpha),
                                                  ggplot2::alpha(default.colour, segment.alpha)),
                                         lwd = (if (segment.linewidth == 0) 0.5 else segment.linewidth) * ggplot2::.stroke),
                                       name = paste("table.s.segment", row$group, row.idx, sep = "."))
                }
                all.grobs <- grid::gList(all.grobs, segment.grob, user.grob)
              } else {
                all.grobs <- grid::gList(all.grobs, user.grob)
              }
            }
            #    grid::grobTree(children = all.grobs, name = "geom.table.panel")
            grid::grobTree(children = all.grobs)

          },

          draw_key = function(...) {
            grid::nullGrob()
          }
  )

#' @rdname geom_table
#' @export
#'
geom_table_npc <- function(mapping = NULL, data = NULL,
                           stat = "identity", position = "identity",
                           ...,
                           table.theme = NULL,
                           table.rownames = FALSE,
                           table.colnames = TRUE,
                           table.hjust = 0.5,
                           parse = FALSE,
                           na.rm = FALSE,
                           show.legend = FALSE,
                           inherit.aes = FALSE) {

  if (is.character(table.hjust)) {
    table.hjust <- switch(table.hjust,
                          left = 0,
                          middle = 0.5,
                          center = 0.5,
                          right = 1,
                          0.5)
  }
  ggplot2::layer(
    data = data,
    mapping = mapping,
    stat = stat,
    geom = GeomTableNpc,
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(
      table.theme = table.theme,
      table.rownames = table.rownames,
      table.colnames = table.colnames,
      table.hjust = table.hjust,
      parse = parse,
      na.rm = na.rm,
      ...
    )
  )
}

#' @rdname ggpp-ggproto
#' @format NULL
#' @usage NULL
#' @export
GeomTableNpc <-
  ggplot2::ggproto("GeomTableNpc", ggplot2::Geom,
          required_aes = c("npcx", "npcy", "label"),

          default_aes = ggplot2::aes(
            colour = NA,
            fill = NA,
            size = 3.2,
            angle = 0,
            hjust = "inward",
            vjust = "inward",
            alpha = 1,
            family = "",
            fontface = 1,
            lineheight = 1.2
          ),

          draw_panel =
            function(data,
                     panel_params,
                     coord,
                     table.theme = NULL,
                     table.rownames = FALSE,
                     table.colnames = TRUE,
                     table.hjust = 0.5,
                     parse = FALSE,
                     na.rm = FALSE) {

              if (nrow(data) == 0) {
                return(grid::nullGrob())
              }

              if (!is.data.frame(data$label[[1]])) {
                warning("Skipping as object mapped to 'label' is not a list of ",
                        "\"tibble\" or \"data.frame\" objects.")
                return(grid::nullGrob())
              }

              data$npcx <- compute_npcx(data$npcx)
              data$npcy <- compute_npcy(data$npcy)

              if (is.character(data$vjust)) {
                data$vjust <- compute_just(data$vjust, data$npcy)
              }
              if (is.character(data$hjust)) {
                data$hjust <- compute_just(data$hjust, data$npcx)
              }

              # replace NULL with default
              if (is.null(table.theme)) {
                table.theme <-
                  getOption("ggpmisc.ttheme.default", default = ttheme_gtdefault)
              }

              tb.grobs <- grid::gList()

              for (row.idx in seq_len(nrow(data))) {
                # if needed, construct the table theme
                if (is.function(table.theme)) {
                  # text position in cell depends on hjust
                  table.x <- if(table.hjust == 0.5) 0.5 else table.hjust * 0.8 + 0.1
                  if (is.na(data$fill[row.idx])) {
                    core.params <-
                      list(fg_params = list(hjust = table.hjust, x = table.x))
                  } else {
                    core.params <-
                      list(fg_params = list(hjust = table.hjust, x = table.x),
                           bg_params = list(fill = data$fill[row.idx]))
                  }
                  if (is.na(data$colour[row.idx])) {
                    # use theme's default base_colour
                    this.table.theme <-
                      table.theme(base_size = data$size[row.idx] * .pt,
                                  base_family = data$family[[row.idx]],
                                  parse = parse,
                                  rowhead = list(fg_params = list(hjust = 1, x = 0.9)),
                                  colhead = list(fg_params = list(hjust = table.hjust,
                                                                  x = table.x)),
                                  core = core.params)
                  } else {
                    # use colour from data$colour
                    this.table.theme <-
                      table.theme(base_size = data$size[row.idx] * .pt,
                                  base_colour = ggplot2::alpha(data$colour[row.idx],
                                                               data$alpha[row.idx]),
                                  base_family = data$family[[row.idx]],
                                  parse = parse,
                                  rowhead = list(fg_params = list(hjust = 1, x = 0.9)),
                                  colhead = list(fg_params = list(hjust = table.hjust,
                                                                  x = table.x)),
                                  core = core.params)
                  }
                } else if (is.list(table.theme)) {
                  this.table.theme <- table.theme
                }
                table.tb <- data[["label"]][[row.idx]]
                gtb <-
                  gridExtra::tableGrob(
                    d = table.tb,
                    theme = this.table.theme,
                    rows = if (table.rownames) rownames(table.tb) else NULL,
                    cols = if (table.colnames) colnames(table.tb) else NULL
                  )

                gtb$vp <-
                  grid::viewport(x = grid::unit(data$npcx[row.idx], "native"),
                                 y = grid::unit(data$npcy[row.idx], "native"),
                                 width = sum(gtb$widths),
                                 height = sum(gtb$heights),
                                 just = c(data$hjust[row.idx], data$vjust[row.idx]),
                                 angle = data$angle[row.idx],
                                 name = paste("geom_table.panel", data$PANEL[row.idx],
                                              "row", row.idx, sep = "."))

                # give unique name to each table
                gtb$name <- paste("table", row.idx, sep = ".")

                tb.grobs[[row.idx]] <- gtb
              }

              grid::grobTree(children = tb.grobs)
            },

          draw_key = function(...) {
            grid::nullGrob()
          }
  )

#' Table themes
#'
#' Additional theme constructors for use with \code{\link{geom_table}}.
#'
#' @details Depending on the theme, the base_colour, which is
#'   mapped to the \code{colour} aesthetic if present, is applied to only the
#'   text elements, or to the text elements and rules. The difference is
#'   exemplified below.
#'
#' @param base_size numeric, default font size.
#' @param base_colour	default font colour.
#' @param base_family	default font family.
#' @param parse	logical, default behaviour for parsing text as plotmath.
#' @param padding length-2 unit vector specifying the horizontal and vertical
#'   padding of text within each cell.
#' @param ... further arguments to control the gtable.
#'
#' @note These theme constructors are wrappers on
#'   \code{gridExtra::ttheme_default()} and \code{gridExtra::ttheme_minimal()}.
#'   They can also be used with \code{\link[gridExtra]{grid.table}} if desired.
#'
#' @return A \code{list} object that can be used as \code{ttheme} in the
#'   construction of tables with functions from package 'gridExtra'.
#'
#' @export
#'
#' @family geometries for adding insets to ggplots
#'
#' @examples
#' library(dplyr)
#' library(tibble)
#'
#' mtcars %>%
#'   group_by(cyl) %>%
#'   summarize(wt = mean(wt), mpg = mean(mpg)) %>%
#'   ungroup() %>%
#'   mutate(wt = sprintf("%.2f", wt),
#'          mpg = sprintf("%.1f", mpg)) -> tb
#'
#' df <- tibble(x = 5.45, y = 34, tb = list(tb))
#'
#' # Same as the default theme constructor
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtdefault) +
#'   theme_classic()
#'
#' # Minimal theme constructor
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtminimal) +
#'   theme_classic()
#'
#' # A theme with white background
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtbw) +
#'   theme_bw()
#'
#' # Default colour of theme superceded by aesthetic constant
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtbw, colour = "darkblue") +
#'   theme_bw()
#'
#' # A theme with dark background
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtdark) +
#'   theme_dark()
#'
#' # Default colour of theme superceded by aesthetic constant
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtdark, colour = "yellow") +
#'   theme_dark()
#'
#' # A theme with light background
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtlight)
#'
#' # Default colour of theme superceded by aesthetic constant
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtlight, colour = "darkred")
#'
#' # Default colour of theme superceded by aesthetic constant
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtsimple)
#'
#' # Default colour of theme superceded by aesthetic constant
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb),
#'              table.theme = ttheme_gtstripes) +
#'   theme_dark()
#'
ttheme_gtdefault <- function (base_size = 10,
                              base_colour = "black",
                              base_family = "",
                              parse = FALSE,
                              padding = unit(c(0.8, 0.6), "char"),
                              ...)
{
  gridExtra::ttheme_default(base_size = base_size,
                            base_colour = base_colour,
                            base_family = base_family,
                            parse = parse,
                            padding = padding,
                            ...)
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtminimal <- function (base_size = 10,
                              base_colour = "black",
                              base_family = "",
                              parse = FALSE,
                              padding = unit(c(0.5, 0.4), "char"),
                              ...)
{
  gridExtra::ttheme_minimal(base_size = base_size,
                            base_colour = base_colour,
                            base_family = base_family,
                            parse = parse,
                            padding = padding,
                            ...)
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtbw <- function (base_size = 10,
                         base_colour = "black",
                         base_family = "",
                         parse = FALSE,
                         padding = unit(c(1, 0.6), "char"),
                         ...)
{
  core <-
    list(bg_params = list(fill = "white", lwd = 1.5, col = "grey90"))
  colhead <-
    list(bg_params = list(fill = "grey80", lwd = 1.5, col = "grey90"))
  rowhead <-
    list(bg_params = list(fill = "grey80", lwd = 1.5, col = "grey90"))

  default <-
    gridExtra::ttheme_default(base_size = base_size,
                              base_colour = base_colour,
                              base_family = base_family,
                              parse = parse,
                              padding = padding,
                              core = core,
                              colhead = colhead,
                              rowhead = rowhead)

  utils::modifyList(default, list(...))
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtplain <- function (base_size = 10,
                            base_colour = "black",
                            base_family = "",
                            parse = FALSE,
                            padding = unit(c(0.8, 0.6), "char"),
                            ...)
{
  core <-
    list(bg_params = list(fill = "white"))
  colhead <-
    list(bg_params = list(fill = "grey90"))
  rowhead <-
    list(bg_params = list(fill = "grey90"))

  default <-
    gridExtra::ttheme_default(base_size = base_size,
                              base_colour = base_colour,
                              base_family = base_family,
                              parse = parse,
                              padding = padding,
                              core = core,
                              colhead = colhead,
                              rowhead = rowhead)

  utils::modifyList(default, list(...))
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtdark <- function (base_size = 10,
                           base_colour = "grey90",
                           base_family = "",
                           parse = FALSE,
                           padding = unit(c(0.8, 0.6), "char"),
                           ...)
{
  core <-
    list(bg_params = list(fill = "grey30", lwd = 1.5, col = base_colour))
  colhead <-
    list(bg_params = list(fill = "black", lwd = 1.5, col = base_colour))
  rowhead <-
    list(bg_params = list(fill = "black", lwd = 1.5, col = base_colour))

  default <-
    gridExtra::ttheme_default(base_size = base_size,
                              base_colour = base_colour,
                              base_family = base_family,
                              parse = parse,
                              padding = padding,
                              core = core,
                              colhead = colhead,
                              rowhead = rowhead)

  utils::modifyList(default, list(...))
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtlight <- function (base_size = 10,
                            base_colour = "grey10",
                            base_family = "",
                            parse = FALSE,
                            padding = unit(c(0.8, 0.6), "char"),
                            ...)
{
  core <-
    list(bg_params = list(fill = "white", lwd = 1.5, col = base_colour))
  colhead <-
    list(bg_params = list(fill = "grey80", lwd = 1.5, col = base_colour))
  rowhead <-
    list(bg_params = list(fill = "grey80", lwd = 1.5, col = base_colour))

  default <-
    gridExtra::ttheme_default(base_size = base_size,
                              base_colour = base_colour,
                              base_family = base_family,
                              parse = parse,
                              padding = padding,
                              core = core,
                              colhead = colhead,
                              rowhead = rowhead)

  utils::modifyList(default, list(...))
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtsimple <- function (base_size = 10,
                            base_colour = "grey10",
                            base_family = "",
                            parse = FALSE,
                            padding = unit(c(0.5, 0.4), "char"),
                            ...)
{
  core <-
    list(bg_params = list(fill = "white", lwd = 0, col = NA))
  colhead <-
    list(bg_params = list(fill = "grey80", lwd = 0, col = NA))
  rowhead <-
    list(bg_params = list(fill = "grey80", lwd = 0, col = NA))

  default <-
    gridExtra::ttheme_default(base_size = base_size,
                              base_colour = base_colour,
                              base_family = base_family,
                              parse = parse,
                              padding = padding,
                              core = core,
                              colhead = colhead,
                              rowhead = rowhead)

  utils::modifyList(default, list(...))
}

#' @rdname ttheme_gtdefault
#'
#' @export
#'
ttheme_gtstripes <- function (base_size = 10,
                              base_colour = "grey10",
                              base_family = "",
                              parse = FALSE,
                              padding = unit(c(0.8, 0.6), "char"),
                              ...)
{
  core <-
    list(bg_params = list(fill = c("white", "grey90"), lwd = 0, col = NA))
  colhead <-
    list(bg_params = list(fill = "grey75", lwd = 0, col = NA))
  rowhead <-
    list(bg_params = list(fill = "grey75", lwd = 0, col = NA))

  default <-
    gridExtra::ttheme_default(base_size = base_size,
                              base_colour = base_colour,
                              base_family = base_family,
                              parse = parse,
                              padding = padding,
                              core = core,
                              colhead = colhead,
                              rowhead = rowhead)

  utils::modifyList(default, list(...))
}

#' Set default table theme
#'
#' Set R option to the theme to use as current default. This function is
#' implemented differently but is used in the same way as
#' \code{ggplot2::theme_set()} but affects the default table-theme instead
#' of the plot theme.
#'
#' @note The ttheme is set when a plot object is constructed, and consequently
#' the option setting does not affect rendering of ready built plot objects.
#'
#' @param table.theme NULL, list or function A gridExtra ttheme defintion, or
#'   a constructor for a ttheme or NULL for default.
#'
#' @return A named list with the previous value of the option.
#'
#' @export
#'
#' @examples
#' library(dplyr)
#' library(tibble)
#'
#' mtcars %>%
#'   group_by(cyl) %>%
#'   summarize(wt = mean(wt), mpg = mean(mpg)) %>%
#'   ungroup() %>%
#'   mutate(wt = sprintf("%.2f", wt),
#'          mpg = sprintf("%.1f", mpg)) -> tb
#'
#' df <- tibble(x = 5.45, y = 34, tb = list(tb))
#'
#' # Same as the default theme constructor
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb))
#'
#' # set a new default
#' old_ttheme <- ttheme_set(ttheme_gtstripes)
#'
#' ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
#'   geom_point() +
#'   geom_table(data = df, aes(x = x, y = y, label = tb))
#'
#' # restore previous setting
#' ttheme_set(old_ttheme)
#'
ttheme_set <- function(table.theme = NULL) {
  stopifnot("Bad argument passed to 'table.theme'" =
              is.null(table.theme) ||
              is.function(table.theme) ||
              is.list(table.theme))
  invisible(options(ggpmisc.ttheme.default = table.theme)[[1]])
}

Try the ggpp package in your browser

Any scripts or data that you put into this service are public.

ggpp documentation built on Nov. 8, 2023, 1:10 a.m.