Skip to content

Commit 3dbd7cd

Browse files
committed
Even better support for tables
1 parent a2a1b81 commit 3dbd7cd

File tree

7 files changed

+252
-18
lines changed

7 files changed

+252
-18
lines changed

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ S3method(patchGrob,guide_area)
4747
S3method(patchGrob,patch)
4848
S3method(patchGrob,table_patch)
4949
S3method(patchGrob,wrapped_patch)
50+
S3method(patchGrob,wrapped_table)
5051
S3method(plot,inset_patch)
5152
S3method(plot,patch)
5253
S3method(plot,patch_area)
@@ -86,6 +87,7 @@ export(set_dim)
8687
export(wrap_elements)
8788
export(wrap_ggplot_grob)
8889
export(wrap_plots)
90+
export(wrap_table)
8991
import(cli)
9092
import(rlang)
9193
importFrom(farver,get_channel)
@@ -142,6 +144,7 @@ importFrom(grid,seekViewport)
142144
importFrom(grid,unit)
143145
importFrom(grid,unit.c)
144146
importFrom(grid,unit.pmax)
147+
importFrom(grid,unitType)
145148
importFrom(grid,upViewport)
146149
importFrom(grid,valid.just)
147150
importFrom(grid,viewport)

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
* Plot backgrounds are now always placed beneath all other elements in the
55
patchwork (#370)
66
* Axis titles can now reliably be collected even with faceted plots (#367)
7-
* Native support for gt objects
7+
* Native support for gt objects, either adding them directly or controlling
8+
their layout with `wrap_table()`
89
* Empty patches no longer breaks up axis title collection (#375)
910
* `wrap_ggplot_grob()` now respects auto-tagging (#363)
1011
* Fix a bug where guide collecting would prevent proper axes collecting (#359)

R/add_plot.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ggplot_add.ggplot <- function(object, plot, object_name) {
77
#' @importFrom ggplot2 ggplot_add
88
#' @export
99
ggplot_add.grob <- function(object, plot, object_name) {
10+
table <- as_patch(object)
1011
plot + wrap_elements(full = object)
1112
}
1213
#' @importFrom ggplot2 ggplot_add
@@ -20,12 +21,14 @@ ggplot_add.raster <- ggplot_add.grob
2021
ggplot_add.nativeRaster <- ggplot_add.grob
2122
#' @importFrom ggplot2 ggplot_add
2223
#' @export
23-
ggplot_add.gt_tbl <- ggplot_add.grob
24+
ggplot_add.gt_tbl <- function(object, plot, object_name) {
25+
plot + wrap_table(object)
26+
}
2427

2528
#' @importFrom grid is.grob
2629
#' @importFrom grDevices is.raster
2730
should_autowrap <- function(x) {
28-
is.grob(x) || inherits(x, 'formula') || is.raster(x) || inherits(x, 'nativeRaster') || inherits(x, 'gt_tbl')
31+
is.grob(x) || inherits(x, 'formula') || is.raster(x) || inherits(x, 'nativeRaster')
2932
}
3033

3134
# Convert a plot with a (possible) list of patches into a self-contained

R/plot_patchwork.R

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,7 @@ find_strip_pos <- function(gt) {
10091009
}
10101010
'inside'
10111011
}
1012+
#' @importFrom grid unitType
10121013
set_panel_dimensions <- function(gt, panels, widths, heights, fixed_asp, design) {
10131014
width_ind <- seq(PANEL_COL, by = TABLE_COLS, length.out = length(widths))
10141015
height_ind <- seq(PANEL_ROW, by = TABLE_ROWS, length.out = length(heights))
@@ -1022,6 +1023,24 @@ set_panel_dimensions <- function(gt, panels, widths, heights, fixed_asp, design)
10221023
heights <- unit(heights, 'null')
10231024
}
10241025
height_strings <- as.character(heights)
1026+
1027+
panel_widths <- do.call(unit.c, lapply(panels, function(x) x$widths[PANEL_COL]))
1028+
absolute_col <- unitType(panel_widths) == "points"
1029+
if (any(absolute_col)) {
1030+
pos <- ifelse(absolute_col & design$l == design$r & width_strings[design$l] == "-1null", design$l, NA)
1031+
fixed_widths <- lapply(split(panel_widths, pos), "sum")
1032+
widths[as.numeric(names(fixed_widths))] <- do.call(unit.c, fixed_widths)
1033+
width_strings <- as.character(widths)
1034+
}
1035+
panel_heights <- do.call(unit.c, lapply(panels, function(x) x$heights[PANEL_ROW]))
1036+
absolute_row <- unitType(panel_heights) == "points"
1037+
if (any(absolute_row)) {
1038+
pos <- ifelse(absolute_row & design$t == design$b & height_strings[design$t] == "-1null", design$t, NA)
1039+
fixed_heights <- lapply(split(panel_heights, pos), "sum")
1040+
heights[as.numeric(names(fixed_heights))] <- do.call(unit.c, fixed_heights)
1041+
height_strings <- as.character(heights)
1042+
}
1043+
10251044
if (any(width_strings == '-1null') && any(height_strings == '-1null')) {
10261045
respect <- matrix(0, nrow = length(gt$heights), ncol = length(gt$widths))
10271046
fixed_areas <- lapply(which(fixed_asp), function(i) {

R/wrap_elements.R

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,6 @@ as_patch.raster <- function(x, ...) {
168168
}
169169
#' @export
170170
as_patch.nativeRaster <- as_patch.raster
171-
#' @export
172-
#' @importFrom grid viewport grobWidth grobHeight grobTree
173-
as_patch.gt_tbl <- function(x, ...) {
174-
check_installed("gt", version = "0.11.0")
175-
grob <- gt::as_gtable(x)
176-
grob$vp <- viewport(
177-
x = 0,
178-
y = 1,
179-
width = grobWidth(grob),
180-
height = grobHeight(grob),
181-
default.units = "npc",
182-
just = c(0, 1)
183-
)
184-
grob
185-
}
186171

187172
#' @importFrom ggplot2 ggplotGrob
188173
get_grob <- function(x, name) {

R/wrap_table.R

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#' Wrap a table in a patchwork compliant patch
2+
#'
3+
#' This function works much like [wrap_elements()] in that it turns the input
4+
#' into patchwork compliant objects that can be added to a composition. However,
5+
#' `wrap_table()` uses the knowledge that the input is a table to provide some
6+
#' very nifty layout options that makes it generally better to use than
7+
#' [wrap_elements()] for this type of object.
8+
#'
9+
#' @param table A gt table or an object coercible to a data frame
10+
#' @param panel what portion of the table should be aligned with the panel
11+
#' region? `"body"` means that any column and row headers will be placed outside
12+
#' the panel region, i.e. the topleft corner of the panel region will be aligned
13+
#' with the topleft data cell. `"full"` means that the whole table will be
14+
#' placed inside the panel region. `"rows"` means that all rows (including column
15+
#' headers) will be placed inside the panel region but row headers will be
16+
#' placed to the left. `"cols"` is the opposite, placing all columns within the
17+
#' panel region but keeping the column header on top of it.
18+
#' @param space How should the dimension of the table influence the final
19+
#' composition? `"fixed"` means that the table width will set the width of the
20+
#' column it occupies and the table height will set the height of the row it
21+
#' occupies. `"free"` is the opposite meaning that the table dimension will not
22+
#' have any influence on the sizing. `"free_x"` and `"free_y"` allows you to
23+
#' free either direction while keeping the remaining fixed. Do note that if you
24+
#' set a specific width or height in [plot_layout()] it will have higher
25+
#' priority than the table dimensions
26+
#' @inheritParams wrap_elements
27+
#'
28+
#' @return A wrapped_table object
29+
#'
30+
#' @export
31+
#'
32+
#' @note This functionality requires v0.11.0 or higher of the gt package
33+
#'
34+
#' @examplesIf requireNamespace("gt", quietly = TRUE) && packageVersion("gt") >= "0.11.0"
35+
#' library(ggplot2)
36+
#' library(gt)
37+
#'
38+
#' p1 <- ggplot(airquality) +
39+
#' geom_line(aes(x = Day, y = Temp, colour = month.name[Month])) +
40+
#' labs(colour = "Month")
41+
#'
42+
#' table <- data.frame(
43+
#' Month = month.name[5:9],
44+
#' "Mean temp." = tapply(airquality$Temp, airquality$Month, mean),
45+
#' "Min temp." = tapply(airquality$Temp, airquality$Month, min),
46+
#' "Max temp." = tapply(airquality$Temp, airquality$Month, max)
47+
#' )
48+
#' gt_tab <- gt(table, rowname_col = "Month")
49+
#'
50+
#' # Default addition usees wrap_table
51+
#' p1 + gt_tab
52+
#'
53+
#' # Default places column and row headers outside panel area. Use wrap_table
54+
#' # to control this
55+
#' p1 + wrap_table(gt_tab, panel = "full")
56+
#'
57+
#' # Tables generally have fixed dimensions and these can be used to control
58+
#' # the size of the area they occupy
59+
#' p2 <- ggplot(airquality) +
60+
#' geom_boxplot(aes(y = month.name[Month], x = Temp)) +
61+
#' scale_y_discrete(name = NULL, limits = month.name[5:9], guide = "none")
62+
#'
63+
#' wrap_table(gt_tab, space = "fixed") + p2
64+
#'
65+
wrap_table <- function(table, panel = c("body", "full", "rows", "cols"), space = c("free", "free_x", "free_y", "fixed"), ignore_tag = FALSE) {
66+
check_installed("gt", version = "0.11.0")
67+
if (!inherits(table, "gt_tbl")) {
68+
table <- try_fetch(
69+
gt::gt(as.data.frame(table)),
70+
error = function(cnd, ...) cli::cli_abort("Unable to convert input table to {.cls gt_tbl}", parent = cnd)
71+
)
72+
}
73+
n_row_headers <- (!all(is.na(table[["_stub_df"]]$row_id))) + (!all(is.na(table[["_stub_df"]]$group_id)))
74+
if (n_row_headers == 2 && !table[["_options"]]$value[[which(table[["_options"]]$parameter == "row_group_as_column")]]) {
75+
n_row_headers <- 1
76+
}
77+
table <- wrap_elements(table, ignore_tag = ignore_tag)
78+
attr(table, "patch_settings")$panel <- arg_match(panel)
79+
attr(table, "patch_settings")$n_row_headers <- n_row_headers
80+
attr(table, "patch_settings")$space <- c(space %in% c("free", "free_x"), space %in% c("free", "free_y"))
81+
class(table) <- c("wrapped_table", class(table))
82+
table
83+
}
84+
85+
#' @export
86+
patchGrob.wrapped_table <- function(x, guides = 'auto') {
87+
panel <- attr(x, "patch_settings")$panel
88+
row_head <- attr(x, "patch_settings")$n_row_headers
89+
space <- attr(x, "patch_settings")$space
90+
91+
x <- NextMethod()
92+
93+
table_loc <- which(x$layout$name == "panel")
94+
table_width <- x$grobs[[table_loc]]$widths
95+
table_height <- x$grobs[[table_loc]]$heights
96+
97+
if (panel %in% c("body", "rows")) {
98+
col_head <- x$grobs[[table_loc]]$layout$t[x$grobs[[table_loc]]$layout$name == "table_body"] - 1
99+
if (col_head > 0) {
100+
height <- sum(x$grobs[[table_loc]]$heights[1:col_head])
101+
x$grobs[[table_loc]]$vp$y <- x$grobs[[table_loc]]$vp$y + height
102+
x$heights[PANEL_ROW - 2] <- height
103+
104+
table_height <- table_height[-(1:col_head)]
105+
}
106+
}
107+
if (panel %in% c("body", "cols") && row_head > 0) {
108+
width <- sum(x$grobs[[table_loc]]$widths[1:row_head])
109+
x$grobs[[table_loc]]$vp$x <- x$grobs[[table_loc]]$vp$x - width
110+
x$widths[PANEL_COL - 2] <- width
111+
112+
table_width <- table_width[-(1:row_head)]
113+
}
114+
if (!space[1]) {
115+
x$widths[PANEL_COL] <- if (inherits(table_width, "simpleUnit")) sum(table_width) else Reduce(`+`, table_width)
116+
}
117+
if (!space[2]) {
118+
x$heights[PANEL_ROW] <- if (inherits(table_height, "simpleUnit")) sum(table_height) else Reduce(`+`, table_height)
119+
}
120+
x
121+
}
122+
123+
#' @export
124+
#' @importFrom grid viewport grobWidth grobHeight grobTree
125+
as_patch.gt_tbl <- function(x, ...) {
126+
check_installed("gt", version = "0.11.0")
127+
grob <- gt::as_gtable(x)
128+
loc <- grob$layout[grob$layout$name == "table",]
129+
grob <- grob[loc$t:loc$b, loc$l:loc$r]
130+
grob$vp <- viewport(
131+
x = 0,
132+
y = 1,
133+
width = grobWidth(grob),
134+
height = grobHeight(grob),
135+
default.units = "npc",
136+
just = c(0, 1)
137+
)
138+
grob
139+
}

man/wrap_table.Rd

Lines changed: 84 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)