Skip to content

Commit a0e4a6a

Browse files
PolkasAlexandra01Wall Alexandra
authored
release 0.7.5
* New helper function paginate_table. * Refactor Gridify Layout Proposal template. Co-authored-by: AlexandraWall <Alexandrawall09@hotmail.co.uk> Co-authored-by: Wall Alexandra <Alexandra.Wall@ucb.com>
1 parent 2f94fc6 commit a0e4a6a

File tree

10 files changed

+646
-107
lines changed

10 files changed

+646
-107
lines changed

.github/ISSUE_TEMPLATE/08_gridify_layout.yml

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ body:
4141
4242
```r
4343
library(gridify)
44-
library(gt)
45-
library(random.cdisc.data)
46-
4744
example_custom_layout <- gridifyLayout(
4845
nrow = 3,
4946
ncol = 4,
@@ -66,64 +63,7 @@ body:
6663
footer_powered = gridifyCell(row = 3, col = 4, x = 0, hjust = 0)
6764
)
6865
)
69-
70-
gg <- gt::gt(
71-
cadsl |>
72-
dplyr::group_by(ARM, REGION1)|>
73-
dplyr::summarise(
74-
`MEAN AGE` = round(mean(AGE), 2),
75-
`MEAN BMRKR1` = round(mean(BMRKR1), 2)
76-
)
77-
) |>
78-
gt::tab_options(
79-
table.width = gt::pct(80), # Increased from 10% to 80%
80-
data_row.padding = gt::px(10),
81-
table_body.hlines.color = "black",table.font.color = "black",
82-
table.font.weight = "bold",row_group.font.weight = "bold",
83-
table.font.size = 14,
84-
# Add complementary blue background styling
85-
table.background.color = "white", # Slightly lighter blue than layout
86-
heading.background.color = "white",
87-
column_labels.background.color = "white",
88-
table_body.border.top.color = "white",
89-
table_body.border.bottom.color = "white"
90-
) |>
91-
# Use tab_style() to set font colors to white for visibility on blue background
92-
gt::tab_style(
93-
style = gt::cell_text(color = "black", weight = "bold"),
94-
locations = gt::cells_column_labels()
95-
) |>
96-
gt::tab_style(
97-
style = gt::cell_text(color = "#00158F", weight = "bold"),
98-
locations = gt::cells_column_labels()
99-
) |>
100-
gt::tab_style(
101-
style = gt::cell_text(color = "#00158F", weight = "bold"),
102-
locations = gt::cells_row_groups()
103-
)
104-
105-
gridify_object <- gridify(
106-
object = gg,
107-
layout = example_custom_layout
108-
)
109-
110-
gridify_object_fill <- gridify_object |>
111-
set_cell("title", "Gridify's Creative Call") |> #, gpar = grid::gpar(col = "#B8E2F4")) |>
112-
set_cell("poem", "'When standard layouts feel too plain,
113-
And your reports need that custom flair,
114-
Gridify invites you to design and share,
115-
A layout that shows how much you care.
116-
With flexible styling for every text,
117-
Create something bold, something complex,
118-
Scan the QR code, propose your best,
119-
Join the community, pass the creative test.
120-
From corporate branding to research needs,
121-
Your custom layout plants the seeds,
122-
For others seeking that perfect frame,
123-
To showcase data and stake their claim.'\nAnonymous", gpar = grid::gpar(fontface = "bold.italic")) |>
124-
set_cell("footer_company", "UCB team") |> #, gpar = grid::gpar(col = "#B8E2F4")) |>
125-
set_cell("footer_powered", "Powered by gridify", gpar = grid::gpar(fontsize = 18, fontface = "bold")) |>
126-
print()
12766
```
12867
validations:
12968
required: false
69+

DESCRIPTION

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: gridify
33
Title: Enrich Figures and Tables with Custom Headers and Footers and More
4-
Version: 0.7.4.9000
4+
Version: 0.7.5
55
Authors@R: c(
66
person("Maciej", "Nasinski", , "Maciej.Nasinski@ucb.com", role = c("aut", "cre")),
77
person("Alexandra", "Wall", , "Alexandra.Wall@ucb.com", role = "aut"),
@@ -24,7 +24,7 @@ URL: https://pharmaverse.github.io/gridify/
2424
BugReports: https://github.com/pharmaverse/gridify/issues
2525
Encoding: UTF-8
2626
Roxygen: list(markdown = TRUE)
27-
RoxygenNote: 7.3.2
27+
RoxygenNote: 7.3.3
2828
Imports:
2929
grid,
3030
methods
@@ -51,6 +51,7 @@ Collate:
5151
pharma_layout.R
5252
get_layouts.R
5353
layout_issues.R
54+
pagination_utils.R
5455
VignetteBuilder: knitr
5556
Config/testthat/edition: 3
5657
Language: en-GB

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export(gridifyCell)
88
export(gridifyCells)
99
export(gridifyLayout)
1010
export(gridifyObject)
11+
export(paginate_table)
1112
export(pharma_layout_A4)
1213
export(pharma_layout_base)
1314
export(pharma_layout_letter)

NEWS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# gridify 0.7.4.9000
1+
# gridify 0.7.5
22

3+
* Added new `paginate_table()` helper function to simplify splitting data frames into pages for multi-page tables.
4+
* Updated multi-page vignette to demonstrate the new `paginate_table()` function.
35
* Updated `README.md` file.
46

57
# gridify 0.7.4

R/pagination_utils.R

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#' Split a data frame into pages for multi-page tables
2+
#'
3+
#' @description
4+
#' A lightweight utility function to split a data frame into pages based on the number of rows per page.
5+
#' This is useful when creating multi-page tables with `gridify`.
6+
#'
7+
#' @param data A data frame to split into pages.
8+
#' @param rows_per_page Integer or NULL. The maximum number of rows per page.
9+
#' When used with `split_by`, groups larger than `rows_per_page` will be split into multiple pages.
10+
#' When used alone, splits the entire dataset by row count.
11+
#' At least one of `rows_per_page` or `split_by` must be provided.
12+
#' @param split_by Character string or NULL. Name of a column in `data` to split by.
13+
#' Each unique value in this column starts a new page. Can be combined with `rows_per_page`
14+
#' to further split large groups.
15+
#' At least one of `rows_per_page` or `split_by` must be provided.
16+
#' @param fill_empty Character string or NULL. When provided, fills incomplete pages
17+
#' with empty rows to match the target row count. Default is `NULL` (no filling).
18+
#' Providing a value (e.g., `"|"`, `""`, or `"—"`) automatically enables filling and uses that
19+
#' value for all cells in the empty rows. This helps maintain consistent vertical positioning
20+
#' across all pages. The target row count is the maximum page size across all pages.
21+
#'
22+
#' @return A list of data frames, one for each page. When `split_by` is used, the list
23+
#' is named with the group values. If a group spans multiple pages (when combined with
24+
#' `rows_per_page`), multiple list elements will have the same name.
25+
#' When only `rows_per_page` is used, returns an unnamed list.
26+
#'
27+
#' @details
28+
#' This is a simple utility to help with the common task of paginating large tables.
29+
#' After splitting the data, you can use a loop to create multiple `gridify` objects
30+
#' and export them as a multi-page PDF or separate image files.
31+
#'
32+
#' The function does not perform the gridify conversion itself - it only prepares the data.
33+
#' This keeps the package lightweight and flexible.
34+
#'
35+
#' @note This function is designed to work with data frames.
36+
#' It is suited especially for use with gt package.
37+
#'
38+
#' @seealso [gridify()], [export_to()]
39+
#'
40+
#' @examples
41+
#' # Basic usage - split mtcars into pages of 10 rows
42+
#' pages <- paginate_table(mtcars, rows_per_page = 10)
43+
#' length(pages) # Number of pages
44+
#'
45+
#' # With filled last page for consistent positioning
46+
#' pages_filled <- paginate_table(mtcars, rows_per_page = 10, fill_empty = "-")
47+
#' nrow(pages_filled[[1]]) # 10 rows
48+
#' nrow(pages_filled[[length(pages_filled)]]) # Also 10 rows (filled with empty rows)
49+
#'
50+
#' # With empty string fill
51+
#' pages_empty <- paginate_table(mtcars, rows_per_page = 10, fill_empty = " ")
52+
#'
53+
#' # Without filling (default)
54+
#' pages_no_fill <- paginate_table(mtcars, rows_per_page = 10)
55+
#'
56+
#' # Split by a grouping column
57+
#' pages_by_cyl <- paginate_table(mtcars, split_by = "cyl")
58+
#' length(pages_by_cyl) # 3 pages (one for each cylinder count: 4, 6, 8)
59+
#' names(pages_by_cyl) # "4", "6", "8" - named with group values
60+
#'
61+
#' # Split by column with filling to match maximum page size
62+
#' pages_by_cyl_filled <- paginate_table(mtcars, split_by = "cyl", fill_empty = "|")
63+
#' sapply(pages_by_cyl_filled, nrow) # All pages have same number of rows
64+
#' names(pages_by_cyl_filled) # "4", "6", "8"
65+
#'
66+
#' # Combine split_by and rows_per_page: split by cylinder, then by 5 rows
67+
#' pages_combined <- paginate_table(mtcars, split_by = "cyl", rows_per_page = 5)
68+
#' # Groups with more than 5 rows will be split into multiple pages
69+
#' names(pages_combined) # e.g., "4", "6", "8", "8", "8" (8 cylinder group split into 3 pages)
70+
#'
71+
#' # With filling for combined approach
72+
#' pages_combined_filled <- paginate_table(
73+
#' mtcars,
74+
#' split_by = "cyl",
75+
#' rows_per_page = 5,
76+
#' fill_empty = "-"
77+
#' )
78+
#'
79+
#'
80+
#' library(gridify)
81+
#' library(gt)
82+
#' # (to use |> version 4.1.0 of R is required, for lower versions we recommend %>% from magrittr)
83+
#' library(magrittr)
84+
#'
85+
#' # Regular Example with gt
86+
#'
87+
#' pages <- paginate_table(mtcars, rows_per_page = 10, fill_empty = " ")
88+
#'
89+
#' row_height_pixels <- 10
90+
#' font_size <- 12
91+
#' font_type <- "serif"
92+
#'
93+
#' # Create gridify objects for each page
94+
#' gridify_list <- lapply(seq_along(pages), function(page) {
95+
#' gt_table <- gt::gt(pages[[page]]) %>%
96+
#' gt::tab_options(
97+
#' table.width = gt::pct(80),
98+
#' data_row.padding = gt::px(row_height_pixels),
99+
#' table.font.size = font_size,
100+
#' table.font.names = font_type
101+
#' )
102+
#'
103+
#' gridify(
104+
#' gt_table,
105+
#' layout = pharma_layout_A4(global_gpar = grid::gpar(fontfamily = font_type))
106+
#' ) %>%
107+
#' set_cell("title_1", "My Multi-Page Table") %>%
108+
#' set_cell("footer_right", paste("Page", page, "of", length(pages)))
109+
#' })
110+
#'
111+
#' # Export as multi-page PDF
112+
#' temp_my_multipage_table_gt_simple <- tempfile(fileext = ".pdf")
113+
#' export_to(gridify_list, temp_my_multipage_table_gt_simple)
114+
#'
115+
#' # By var Example with gt
116+
#'
117+
#' pages <- paginate_table(mtcars, split_by = "cyl")
118+
#'
119+
#' row_height_pixels <- 10
120+
#' font_size <- 12
121+
#' font_type <- "serif"
122+
#'
123+
#' # Create gridify objects for each page
124+
#' gridify_list <- lapply(seq_along(pages), function(page) {
125+
#' gt_table <- gt::gt(pages[[page]]) %>%
126+
#' gt::tab_options(
127+
#' table.width = gt::pct(80),
128+
#' data_row.padding = gt::px(row_height_pixels),
129+
#' table.font.size = font_size,
130+
#' table.font.names = font_type
131+
#' )
132+
#'
133+
#' gridify(
134+
#' gt_table,
135+
#' layout = pharma_layout_A4(global_gpar = grid::gpar(fontfamily = font_type))
136+
#' ) %>%
137+
#' set_cell("title_1", "My Multi-Page Table") %>%
138+
#' set_cell("by_line", sprintf("cyl is equal to %s", names(pages)[page])) %>%
139+
#' set_cell("footer_right", paste("Page", page, "of", length(pages)))
140+
#' })
141+
#'
142+
#' # Export as multi-page PDF
143+
#' temp_my_multipage_table_gt_by <- tempfile(fileext = ".pdf")
144+
#' export_to(gridify_list, temp_my_multipage_table_gt_by)
145+
#'
146+
#' @export
147+
paginate_table <- function(
148+
data,
149+
rows_per_page = NULL,
150+
split_by = NULL,
151+
fill_empty = NULL) {
152+
153+
if (!is.data.frame(data)) {
154+
stop("`data` must be a data frame.")
155+
}
156+
157+
# Check that at least one of rows_per_page or split_by is provided
158+
if (is.null(rows_per_page) && is.null(split_by)) {
159+
stop("At least one of `rows_per_page` or `split_by` must be provided.")
160+
}
161+
162+
# Validate rows_per_page if provided
163+
if (!is.null(rows_per_page)) {
164+
if (!(is.numeric(rows_per_page) && length(rows_per_page) == 1 && rows_per_page > 0)) {
165+
stop("`rows_per_page` must be a positive integer.")
166+
}
167+
rows_per_page <- as.integer(rows_per_page)
168+
}
169+
170+
# Validate split_by if provided
171+
if (!is.null(split_by)) {
172+
if (!is.character(split_by) || length(split_by) != 1) {
173+
stop("`split_by` must be a single character string.")
174+
}
175+
if (!split_by %in% colnames(data)) {
176+
stop("`split_by` column '", split_by, "' not found in data.")
177+
}
178+
}
179+
180+
if (!is.null(fill_empty) && (!is.character(fill_empty) || length(fill_empty) != 1)) {
181+
stop("`fill_empty` must be NULL or a single character string.")
182+
}
183+
184+
# Split data based on method
185+
if (!is.null(split_by)) {
186+
# Split by column values only - keep names
187+
groups <- split(data, data[[split_by]], drop = TRUE)
188+
# if rows_per_page is null then take max number of rows of the groups
189+
if(is.null(rows_per_page)) rows_per_page <- max(unlist(lapply(groups, nrow)))
190+
} else {
191+
# don't split by column, only have 1 element in list
192+
groups <- list(data)
193+
}
194+
group_names <- names(groups)
195+
196+
pages <- list()
197+
page_names <- character()
198+
199+
for(i in seq_along(groups)){
200+
group <- groups[[i]]
201+
group_name <- group_names[i]
202+
group_rows <- nrow(group)
203+
204+
if (group_rows <= rows_per_page) {
205+
# Group fits in one page
206+
pages <- c(pages, list(group))
207+
page_names <- c(page_names, group_name)
208+
} else {
209+
# Split group into multiple pages
210+
n_pages <- ceiling(group_rows / rows_per_page)
211+
page_assignments <- rep(
212+
seq_len(n_pages),
213+
each = rows_per_page,
214+
length.out = group_rows
215+
)
216+
group_pages <- split(group, page_assignments)
217+
pages <- c(pages, group_pages)
218+
# Repeat group name for each page in this group
219+
page_names <- c(page_names, rep(group_name, n_pages))
220+
221+
}
222+
}
223+
names(pages) <- if (!is.null(split_by)) page_names else NULL
224+
225+
# Fill pages if requested
226+
if (!is.null(fill_empty)) {
227+
228+
pages <- lapply(pages, function(page) {
229+
page_nrows <- nrow(page)
230+
231+
if (page_nrows < rows_per_page) {
232+
rows_difference <- rows_per_page - page_nrows
233+
234+
# Create empty rows with specified fill value
235+
empty_df <- data.frame(
236+
matrix(fill_empty, nrow = rows_difference, ncol = ncol(data)),
237+
stringsAsFactors = FALSE
238+
)
239+
colnames(empty_df) <- colnames(data)
240+
241+
# Append to page
242+
page <- rbind(page, empty_df)
243+
}
244+
245+
page
246+
})
247+
}
248+
249+
pages
250+
}

_pkgdown.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ reference:
4343
- pharma_layout_letter
4444
- layout_issue
4545

46+
- subtitle: gridify utils
47+
contents:
48+
- paginate_table
49+
4650
- title: gridify custom layout development
4751
- subtitle: functions
4852
contents:

inst/WORDLIST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ gt
3737
gtable
3838
labeled
3939
npc
40+
pharmaverse
4041
px
4142
rd
4243
rtables

0 commit comments

Comments
 (0)