1#' Add new rows in specified position.
2#'
3#' Insert new rows in a gtable and adjust the grob placement accordingly. If
4#' rows are added in the middle of a grob spanning multiple rows, the grob will
5#' continue to span them all. If a row is added above or below a grob, the grob
6#' will not span the new row(s).
7#'
8#' @param x a [gtable()] object
9#' @param heights a unit vector giving the heights of the new rows
10#' @param pos new row will be added below this position. Defaults to
11#'   adding row on bottom. `0` adds on the top.
12#'
13#' @return A gtable with the new rows added.
14#'
15#' @family gtable manipulation
16#'
17#' @export
18#'
19#' @examples
20#' library(grid)
21#' rect <- rectGrob(gp = gpar(fill = "#00000080"))
22#' tab <- gtable(unit(rep(1, 3), "null"), unit(rep(1, 3), "null"))
23#' tab <- gtable_add_grob(tab, rect, t = 1, l = 1, r = 3)
24#' tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 1)
25#' tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 3)
26#' dim(tab)
27#' plot(tab)
28#'
29#' # Grobs will continue to span over new rows if added in the middle
30#' tab2 <- gtable_add_rows(tab, unit(1, "null"), 1)
31#' dim(tab2)
32#' plot(tab2)
33#'
34#' # But not when added to top (0) or bottom (-1, the default)
35#' tab3 <- gtable_add_rows(tab, unit(1, "null"))
36#' tab3 <- gtable_add_rows(tab3, unit(1, "null"), 0)
37#' dim(tab3)
38#' plot(tab3)
39#'
40gtable_add_rows <- function(x, heights, pos = -1) {
41  if (!is.gtable(x)) stop("x must be a gtable", call. = FALSE)
42  if (length(pos) != 1) stop("pos must be a scalar unit", call. = FALSE)
43  n <- length(heights)
44
45  pos <- neg_to_pos(pos, length(x$heights))
46
47  # Shift existing rows down
48  x$heights <- insert.unit(x$heights, heights, pos)
49  layout <- unclass(x$layout)
50  layout$t <- ifelse(layout$t > pos, layout$t + n, layout$t)
51  layout$b <- ifelse(layout$b > pos, layout$b + n, layout$b)
52  x$layout <- new_data_frame(layout)
53
54  x
55}
56
57#' Add new columns in specified position.
58#'
59#' Insert new columns in a gtable and adjust the grob placement accordingly. If
60#' columns are added in the middle of a grob spanning multiple columns, the grob
61#' will continue to span them all. If a column is added to the left or right of
62#' a grob, the grob will not span the new column(s).
63#'
64#' @param x a [gtable()] object
65#' @param widths a unit vector giving the widths of the new columns
66#' @param pos new columns will be added to the right of this position. Defaults
67#'   to adding col on right. `0` adds on the left.
68#'
69#' @return A gtable with the new columns added.
70#'
71#' @family gtable manipulation
72#'
73#' @export
74#'
75#' @examples
76#' library(grid)
77#' rect <- rectGrob(gp = gpar(fill = "#00000080"))
78#' tab <- gtable(unit(rep(1, 3), "null"), unit(rep(1, 3), "null"))
79#' tab <- gtable_add_grob(tab, rect, t = 1, l = 1, r = 3)
80#' tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 1)
81#' tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 3)
82#' dim(tab)
83#' plot(tab)
84#'
85#' # Grobs will continue to span over new rows if added in the middle
86#' tab2 <- gtable_add_cols(tab, unit(1, "null"), 1)
87#' dim(tab2)
88#' plot(tab2)
89#'
90#' # But not when added to left (0) or right (-1, the default)
91#' tab3 <- gtable_add_cols(tab, unit(1, "null"))
92#' tab3 <- gtable_add_cols(tab3, unit(1, "null"), 0)
93#' dim(tab3)
94#' plot(tab3)
95#'
96gtable_add_cols <- function(x, widths, pos = -1) {
97  if (!is.gtable(x)) stop("x must be a gtable", call. = FALSE)
98  if (length(pos) != 1) stop("pos must be a scalar unit", call. = FALSE)
99  n <- length(widths)
100
101  pos <- neg_to_pos(pos, length(x$widths))
102
103  # Shift existing columns right
104  x$widths <- insert.unit(x$widths, widths, pos)
105  layout <- unclass(x$layout)
106  layout$l <- ifelse(layout$l > pos, layout$l + n, layout$l)
107  layout$r <- ifelse(layout$r > pos, layout$r + n, layout$r)
108  x$layout <- new_data_frame(layout)
109  x
110}
111