blob: 009226efc9d13ec1a93f18a0463fc0c1e1174aa3 [file] [log] [blame]
Hao Zhu62cdde52017-05-20 22:16:03 -04001#' Put a few rows of a table into one category
2#'
Hao Zhubd95bb22017-05-22 16:08:49 -04003#' @description Group a few rows in a table together under a label.
4#'
5#' @param kable_input Output of `knitr::kable()` with `format` specified
6#' @param group_label A character string for the name of the group
7#' @param start_row A numeric value that tells the function in which row the
8#' group starts. Note that the counting excludes header rows and other group
9#' labeling rows
10#' @param end_row A numeric value that tells the function in which row the group
11#' ends.
Hao Zhu49483bf2017-09-12 11:21:00 -040012#' @param index A named vector providing the index for robust row-grouping tasks.
13#' Basically, you can use it in the same way as `add_header_above()`.
Hao Zhubd95bb22017-05-22 16:08:49 -040014#' @param label_row_css A character string for any customized css used for the
15#' labeling row. By default, the labeling row will have a solid black line
16#' underneath. Only useful for HTML documents.
17#' @param latex_gap_space A character value telling LaTeX how large the gap
18#' between the previous row and the group labeling row. Only useful for LaTeX
19#' documents.
Hao Zhuac7e70f2017-08-02 00:18:36 -040020#' @param escape A T/F value showing whether special characters should be
21#' escaped.
Salzer8f49eb62018-02-12 22:19:06 -050022#' @param latex_align Adjust justification of group_label in latex only. Value should be "c" for
Salzer15550852018-02-12 22:09:14 -050023#' centered on row, "r" for right justification, or "l" for left justification. Default
24#' Value is "l" If using html, the alignment can be set by using the label_row_css
25#' parameter.
Hao Zhu779569e2019-04-26 17:06:33 -040026#' @param latex_wrap_text T/F for wrapping long text. Default is off. Whenever
27#' it is turned on, the table will take up the entire line. It's recommended
28#' to use this with full_width in kable_styling.
bsalzer85933272018-02-12 17:26:46 -050029#' @param colnum A numeric that determines how many columns the text should span.
30#' The default setting will have the text span the entire length.
georgegui4cc925b2018-03-01 12:02:45 -060031#' @param bold A T/F value to control whether the text should be bolded.
32#' @param italic A T/F value to control whether the text should to be emphasized.
Brian Salzera3f43ad2018-03-04 15:03:20 -050033#' @param hline_before A T/F value that addes a horizontal line before the group_row label. Default
34#' value is False.
georgegui4cc925b2018-03-01 12:02:45 -060035#' @param hline_after A replicate of `hline.after` in xtable. It
Hao Zhu6191e742018-03-01 13:09:08 -050036#' addes a hline after the row
georgegui4cc925b2018-03-01 12:02:45 -060037#' @param extra_latex_after Extra LaTeX text to be added after the row.
Hao Zhuebdb3c22020-08-12 08:27:38 -040038#' @param indent A T/F value to control whether list items are indented.
Hao Zhu03e33332020-08-19 01:09:43 -040039#' @param monospace T/F value to control whether the text of the
40#' selected column need to be monospaced (verbatim)
Hao Zhu718fa3f2020-08-19 08:23:28 -040041#' @param underline T/F value to control whether the text of the
Hao Zhu03e33332020-08-19 01:09:43 -040042#' selected row need to be underlined
43#' @param strikeout T/F value to control whether the text of the
44#' selected row need to be striked out.
45#' @param color A character string for column text color. Here please
46#' pay attention to the differences in color codes between HTML and LaTeX.
47#' @param background A character string for column background color. Here please
48#' pay attention to the differences in color codes between HTML and LaTeX.
Hao Zhubd95bb22017-05-22 16:08:49 -040049#'
Hao Zhu9399dcc2020-08-26 17:27:38 -040050#' @examples
51#' \dontrun{
52#' x <- knitr::kable(head(mtcars), "html")
Hao Zhu78e61222017-05-24 20:53:35 -040053#' # Put Row 2 to Row 5 into a Group and label it as "Group A"
Hao Zhu72917f92019-03-15 18:41:42 -040054#' pack_rows(x, "Group A", 2, 5)
Hao Zhu9399dcc2020-08-26 17:27:38 -040055#' }
Hao Zhu78e61222017-05-24 20:53:35 -040056#'
Hao Zhu62cdde52017-05-20 22:16:03 -040057#' @export
Hao Zhu49483bf2017-09-12 11:21:00 -040058group_rows <- function(kable_input, group_label = NULL,
59 start_row = NULL, end_row = NULL,
60 index = NULL,
Hao Zhud972e7f2017-05-22 13:27:15 -040061 label_row_css = "border-bottom: 1px solid;",
Hao Zhu49483bf2017-09-12 11:21:00 -040062 latex_gap_space = "0.3em",
Hao Zhu779569e2019-04-26 17:06:33 -040063 escape = TRUE, latex_align = "l",
64 latex_wrap_text = FALSE,
65 colnum = NULL,
Hao Zhu2742ffc2018-10-17 11:23:44 -040066 bold = TRUE,
67 italic = FALSE,
68 hline_before = FALSE,
69 hline_after = FALSE,
70 extra_latex_after = NULL,
Hao Zhu03e33332020-08-19 01:09:43 -040071 indent = TRUE,
72 monospace = FALSE, underline = FALSE, strikeout = FALSE,
73 color = NULL, background = NULL) {
Hao Zhu49483bf2017-09-12 11:21:00 -040074
Hao Zhu62cdde52017-05-20 22:16:03 -040075 kable_format <- attr(kable_input, "format")
76 if (!kable_format %in% c("html", "latex")) {
Hao Zhu401ebd82018-01-14 17:10:20 -050077 warning("Please specify format in kable. kableExtra can customize either ",
78 "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ",
79 "for details.")
Hao Zhu62cdde52017-05-20 22:16:03 -040080 return(kable_input)
81 }
Adrien Le Guilloufdffec62019-09-04 00:03:52 +020082
Hao Zhu49483bf2017-09-12 11:21:00 -040083 if (is.null(index)) {
84 if (kable_format == "html") {
Hao Zhu779569e2019-04-26 17:06:33 -040085 if (!missing(latex_align)) warning("latex_align parameter is not used in HTML Mode,
Salzer15550852018-02-12 22:09:14 -050086 use label_row_css instead.")
Hao Zhu49483bf2017-09-12 11:21:00 -040087 return(group_rows_html(kable_input, group_label, start_row, end_row,
Adrien Le Guilloufdffec62019-09-04 00:03:52 +020088 label_row_css, escape, colnum, indent,
Hao Zhu03e33332020-08-19 01:09:43 -040089 bold, italic, monospace, underline, strikeout,
90 color, background))}
Hao Zhu49483bf2017-09-12 11:21:00 -040091 if (kable_format == "latex") {
92 return(group_rows_latex(kable_input, group_label, start_row, end_row,
georgegui4cc925b2018-03-01 12:02:45 -060093 latex_gap_space, escape, latex_align, colnum,
Hao Zhu2742ffc2018-10-17 11:23:44 -040094 bold, italic, hline_before, hline_after,
Hao Zhu03e33332020-08-19 01:09:43 -040095 extra_latex_after, indent, latex_wrap_text,
96 monospace, underline, strikeout,
97 color, background))
Hao Zhu49483bf2017-09-12 11:21:00 -040098 }
99 } else {
100 index <- group_row_index_translator(index)
101 out <- kable_input
102 if (kable_format == "html") {
103 for (i in 1:nrow(index)) {
Hao Zhu779569e2019-04-26 17:06:33 -0400104 if (!missing(latex_align)) warning("latex_align parameter is not used in HTML Mode,
Salzer15550852018-02-12 22:09:14 -0500105 use label_row_css instead.")
Hao Zhu49483bf2017-09-12 11:21:00 -0400106 out <- group_rows_html(out, index$header[i],
107 index$start[i], index$end[i],
Adrien Le Guilloufdffec62019-09-04 00:03:52 +0200108 label_row_css, escape, colnum, indent,
Hao Zhu03e33332020-08-19 01:09:43 -0400109 bold, italic, monospace, underline, strikeout,
110 color, background)
Hao Zhu49483bf2017-09-12 11:21:00 -0400111 }
112 }
113 if (kable_format == "latex") {
114 for (i in 1:nrow(index)) {
115 out <- group_rows_latex(out, index$header[i],
116 index$start[i], index$end[i],
georgegui4cc925b2018-03-01 12:02:45 -0600117 latex_gap_space, escape, latex_align, colnum,
Hao Zhu2742ffc2018-10-17 11:23:44 -0400118 bold, italic, hline_before, hline_after,
Hao Zhu03e33332020-08-19 01:09:43 -0400119 extra_latex_after, indent, latex_wrap_text,
120 monospace, underline, strikeout,
121 color, background)
Hao Zhu49483bf2017-09-12 11:21:00 -0400122 }
123 }
124 return(out)
Hao Zhu62cdde52017-05-20 22:16:03 -0400125 }
Hao Zhu49483bf2017-09-12 11:21:00 -0400126}
127
128group_row_index_translator <- function(index) {
129 index <- standardize_header_input(index)
130 index$start <- cumsum(c(1, index$colspan))[1:length(index$colspan)]
131 index$end <- cumsum(index$colspan)
132 index$header <- trimws(index$header)
133 index <- index[index$header != "", ]
134 return(index)
Hao Zhu62cdde52017-05-20 22:16:03 -0400135}
136
Hao Zhud972e7f2017-05-22 13:27:15 -0400137group_rows_html <- function(kable_input, group_label, start_row, end_row,
Hao Zhucedf90e2020-08-12 08:45:34 -0400138 label_row_css, escape, colnum, indent,
Hao Zhu03e33332020-08-19 01:09:43 -0400139 bold, italic, monospace, underline, strikeout,
140 color, background) {
Hao Zhu62cdde52017-05-20 22:16:03 -0400141 kable_attrs <- attributes(kable_input)
Hao Zhu558c72f2017-07-24 15:12:00 -0400142 kable_xml <- read_kable_as_xml(kable_input)
Hao Zhu62cdde52017-05-20 22:16:03 -0400143 kable_tbody <- xml_tpart(kable_xml, "tbody")
144
Hao Zhuac7e70f2017-08-02 00:18:36 -0400145 if (escape) {
146 group_label <- escape_html(group_label)
147 }
148
Hao Zhu62cdde52017-05-20 22:16:03 -0400149 group_header_rows <- attr(kable_input, "group_header_rows")
150 group_seq <- seq(start_row, end_row)
151 if (!is.null(group_header_rows)) {
152 group_seq <- positions_corrector(group_seq, group_header_rows,
153 length(xml_children(kable_tbody)))
jokorn69c8bd52019-06-29 11:11:11 +0200154 # Update the old group_header_rows attribute with their new positions
155 kable_attrs$group_header_rows <- ifelse(kable_attrs$group_header_rows > group_seq[1],
156 kable_attrs$group_header_rows+1,
157 kable_attrs$group_header_rows)
Hao Zhu62cdde52017-05-20 22:16:03 -0400158 }
159
160 # Insert a group header row
161 starting_node <- xml_child(kable_tbody, group_seq[1])
bsalzer85933272018-02-12 17:26:46 -0500162 kable_ncol <- ifelse(is.null(colnum),
163 length(xml_children(starting_node)),
164 colnum)
Adrien Le Guilloufdffec62019-09-04 00:03:52 +0200165
166 if (bold) group_label <- paste0("<strong>", group_label, "</strong>")
167 if (italic) group_label <- paste0("<em>", group_label, "</em>")
168
Hao Zhu8b16a6c2020-08-18 16:59:20 -0400169 if (label_row_css == "border-bottom: 1px solid;") {
170 if (!is.null(attr(kable_input, "lightable_class"))) {
171 lightable_class <- attr(kable_input, "lightable_class")
172 if (lightable_class %in% c(
173 "lightable-classic", "lightable-classic-2", "lightable-minimal")) {
174 label_row_css <- "border-bottom: 0;"
175 }
176 if (lightable_class %in% c("lightable-paper")) {
177 label_row_css <- "border-bottom: 1px solid #00000020;"
178 }
179 if (lightable_class %in% c("lightable-material")) {
180 label_row_css <- "border-bottom: 1px solid #eee; "
181 }
182 if (lightable_class %in% c("lightable-material-dark")) {
183 label_row_css <- "border-bottom: 1px solid #FFFFFF12; color: #FFFFFF60;"
184 }
185 }
186 }
Hao Zhu03e33332020-08-19 01:09:43 -0400187 if (monospace) {
188 label_row_css <- paste0(label_row_css, "font-family: monospace;")
189 }
190 if (underline) {
191 label_row_css <- paste0(label_row_css, "text-decoration: underline;")
192 }
193 if (strikeout) {
194 label_row_css <- paste0(label_row_css, "text-decoration: line-through;")
195 }
196 if (!is.null(color)) {
197 label_row_css <- paste0(label_row_css, "color: ", html_color(color),
198 " !important;")
199 }
200 if (!is.null(background)) {
201 label_row_css <- paste0(label_row_css, "background-color: ",
202 html_color(background), " !important;")
203 }
Hao Zhu8b16a6c2020-08-18 16:59:20 -0400204
Hao Zhu62cdde52017-05-20 22:16:03 -0400205 group_header_row_text <- paste0(
Hao Zhud972e7f2017-05-22 13:27:15 -0400206 '<tr groupLength="', length(group_seq), '"><td colspan="', kable_ncol,
Adrien Le Guilloufdffec62019-09-04 00:03:52 +0200207 '" style="', label_row_css, '">', group_label, "</td></tr>")
208
Hao Zhu62cdde52017-05-20 22:16:03 -0400209 group_header_row <- read_xml(group_header_row_text, options = "COMPACT")
210 xml_add_sibling(starting_node, group_header_row, .where = "before")
211
212 # add indentations to items
Hao Zhuf2dfd142017-07-24 14:43:28 -0400213 out <- as_kable_xml(kable_xml)
Hao Zhu62cdde52017-05-20 22:16:03 -0400214 attributes(out) <- kable_attrs
215 attr(out, "group_header_rows") <- c(attr(out, "group_header_rows"), group_seq[1])
Hao Zhu2742ffc2018-10-17 11:23:44 -0400216 if (indent) {
217 out <- add_indent_html(out, positions = seq(start_row, end_row))
218 }
Hao Zhu62cdde52017-05-20 22:16:03 -0400219 return(out)
220}
Hao Zhud972e7f2017-05-22 13:27:15 -0400221
Hao Zhufc14c9b2017-05-22 14:03:22 -0400222group_rows_latex <- function(kable_input, group_label, start_row, end_row,
georgegui4cc925b2018-03-01 12:02:45 -0600223 gap_space, escape, latex_align, colnum,
Hao Zhu779569e2019-04-26 17:06:33 -0400224 bold = T, italic = F, hline_before = F, hline_after = F,
Hao Zhu03e33332020-08-19 01:09:43 -0400225 extra_latex_after = NULL, indent, latex_wrap_text = F,
226 monospace = F, underline = F, strikeout = F,
227 color = NULL, background = NULL) {
Hao Zhud972e7f2017-05-22 13:27:15 -0400228 table_info <- magic_mirror(kable_input)
Hao Zhu3fc0e882018-04-03 16:06:41 -0400229 out <- solve_enc(kable_input)
Hao Zhud972e7f2017-05-22 13:27:15 -0400230
Hao Zhu064990d2017-10-17 18:08:42 -0400231 if (table_info$duplicated_rows) {
232 dup_fx_out <- fix_duplicated_rows_latex(out, table_info)
233 out <- dup_fx_out[[1]]
234 table_info <- dup_fx_out[[2]]
235 }
236
Hao Zhuac7e70f2017-08-02 00:18:36 -0400237 if (escape) {
Hao Zhuf94a26f2018-04-05 17:42:55 -0400238 group_label <- input_escape(group_label, latex_align)
Hao Zhu718fa3f2020-08-19 08:23:28 -0400239 } else {
240 group_label <- sim_double_escape(group_label)
Hao Zhuac7e70f2017-08-02 00:18:36 -0400241 }
242
Hao Zhuf94a26f2018-04-05 17:42:55 -0400243 if (bold) {
georgegui4cc925b2018-03-01 12:02:45 -0600244 group_label <- paste0("\\\\textbf{", group_label, "}")
245 }
Hao Zhu779569e2019-04-26 17:06:33 -0400246
Hao Zhuf94a26f2018-04-05 17:42:55 -0400247 if (italic) group_label <- paste0("\\\\textit{", group_label, "}")
Hao Zhu03e33332020-08-19 01:09:43 -0400248
249 if (monospace) {
250 group_label <- paste0("\\\\ttfamily\\{", group_label, "\\}")
251 }
252 if (underline) {
253 group_label <- paste0("\\\\underline\\{", group_label, "\\}")
254 }
255 if (strikeout) {
256 group_label <- paste0("\\\\sout\\{", group_label, "\\}")
257 }
258 if (!is.null(color)) {
259 group_label <- paste0("\\\\textcolor", latex_color(color), "\\{",
260 group_label, "\\}")
261 }
262 if (!is.null(background)) {
263 group_label <- paste0("\\\\cellcolor", latex_color(background), "\\{",
264 group_label, "\\}")
265 }
Hao Zhud972e7f2017-05-22 13:27:15 -0400266 # Add group label
Hao Zhu779569e2019-04-26 17:06:33 -0400267 if (latex_wrap_text) {
268 latex_align <- switch(
269 latex_align,
270 "l" = "p{\\\\linewidth}",
271 "c" = ">{\\\\centering\\\\arraybackslash}p{\\\\linewidth}",
272 "r" = ">{\\\\centering\\\\arraybackslash}p{\\\\linewidth}"
273 )
274 }
275
276
Hao Zhu334376d2020-08-19 00:45:09 -0400277 rowtext <- table_info$contents[start_row + table_info$position_offset]
Hao Zhud972e7f2017-05-22 13:27:15 -0400278 if (table_info$booktabs) {
Hao Zhu334376d2020-08-19 00:45:09 -0400279 pre_rowtext <- paste0("\\\\addlinespace[", gap_space, "]\n")
Hao Zhud972e7f2017-05-22 13:27:15 -0400280 } else {
Hao Zhu334376d2020-08-19 00:45:09 -0400281 pre_rowtext <- ''
282 hline_after <- TRUE
Hao Zhud972e7f2017-05-22 13:27:15 -0400283 }
Hao Zhu334376d2020-08-19 00:45:09 -0400284 pre_rowtext <- paste0(
285 pre_rowtext,
286 ifelse(hline_before,"\\\\hline\n", ""),
287 "\\\\multicolumn{", ifelse(is.null(colnum),
288 table_info$ncol,
289 colnum),
290 "}{", latex_align,"}{", group_label,
291 "}\\\\\\\\\n", ifelse(hline_after, "\\\\hline\n", '')
292 )
georgegui4cc925b2018-03-01 12:02:45 -0600293 if(!is.null(extra_latex_after)){
294 pre_rowtext <- paste0(pre_rowtext,
295 regex_escape(extra_latex_after, double_backslash = TRUE))
296 }
297 new_rowtext <- paste0(pre_rowtext, rowtext)
Hao Zhu9c3007e2020-08-03 13:52:38 -0400298 if (start_row + 1 == table_info$nrow &
Hao Zhu334376d2020-08-19 00:45:09 -0400299 !is.null(table_info$repeat_header_latex) & table_info$booktabs) {
Hao Zhu9c3007e2020-08-03 13:52:38 -0400300 out <- sub(paste0(rowtext, "\\\\\\\\\\*\n"),
301 paste0(new_rowtext, "\\\\\\\\\\*\n"),
302 out)
303 } else {
304 out <- sub(paste0(rowtext, "\\\\\\\\\n"),
305 paste0(new_rowtext, "\\\\\\\\\n"),
306 out)
307 }
308
Hao Zhu8f202992017-07-15 02:20:18 -0400309 out <- gsub("\\\\addlinespace\n", "", out)
Hao Zhud2c0f732017-08-26 10:40:14 -0400310 out <- structure(out, format = "latex", class = "knitr_kable")
Hao Zhu32f43f72017-06-20 18:24:54 -0400311 table_info$group_rows_used <- TRUE
312 attr(out, "kable_meta") <- table_info
Hao Zhu2742ffc2018-10-17 11:23:44 -0400313 if (indent) {
314 out <- add_indent_latex(out, seq(start_row, end_row))
315 }
Hao Zhud972e7f2017-05-22 13:27:15 -0400316 return(out)
317}
Hao Zhube853f72018-05-20 18:52:26 -0400318
319#' Automatically figuring out the group_row index
320#'
321#' @description This helper function allows users to build the `group_row`
322#' index more quickly and use `group_rows` in a way that is similar with
323#' `collapse_rows`.
324#'
325#' @param x The index column. A vector. For example `c("a", "a", "b", "b", "b")``
326#'
327#' @export
328auto_index <- function(x) {
Hao Zhu6adba332020-08-19 11:48:51 -0400329 if (is.factor(x)) x <- as.character(x)
Hao Zhube853f72018-05-20 18:52:26 -0400330 x_rle <- rle(x)
331 index <- x_rle$lengths
332 names(index) <- x_rle$values
333 return(index)
334}
Hao Zhu72917f92019-03-15 18:41:42 -0400335
336#' @rdname group_rows
337#' @export
338pack_rows <- group_rows