blob: 3b22701520ae3e9179f0d76dd43d1e97b6b6da9d [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)
41#' #' @param underline T/F value to control whether the text of the
42#' 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 Zhu78e61222017-05-24 20:53:35 -040050#' @examples x <- knitr::kable(head(mtcars), "html")
51#' # Put Row 2 to Row 5 into a Group and label it as "Group A"
Hao Zhu72917f92019-03-15 18:41:42 -040052#' pack_rows(x, "Group A", 2, 5)
Hao Zhu78e61222017-05-24 20:53:35 -040053#'
Hao Zhu62cdde52017-05-20 22:16:03 -040054#' @export
Hao Zhu49483bf2017-09-12 11:21:00 -040055group_rows <- function(kable_input, group_label = NULL,
56 start_row = NULL, end_row = NULL,
57 index = NULL,
Hao Zhud972e7f2017-05-22 13:27:15 -040058 label_row_css = "border-bottom: 1px solid;",
Hao Zhu49483bf2017-09-12 11:21:00 -040059 latex_gap_space = "0.3em",
Hao Zhu779569e2019-04-26 17:06:33 -040060 escape = TRUE, latex_align = "l",
61 latex_wrap_text = FALSE,
62 colnum = NULL,
Hao Zhu2742ffc2018-10-17 11:23:44 -040063 bold = TRUE,
64 italic = FALSE,
65 hline_before = FALSE,
66 hline_after = FALSE,
67 extra_latex_after = NULL,
Hao Zhu03e33332020-08-19 01:09:43 -040068 indent = TRUE,
69 monospace = FALSE, underline = FALSE, strikeout = FALSE,
70 color = NULL, background = NULL) {
Hao Zhu49483bf2017-09-12 11:21:00 -040071
Hao Zhu62cdde52017-05-20 22:16:03 -040072 kable_format <- attr(kable_input, "format")
73 if (!kable_format %in% c("html", "latex")) {
Hao Zhu401ebd82018-01-14 17:10:20 -050074 warning("Please specify format in kable. kableExtra can customize either ",
75 "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ",
76 "for details.")
Hao Zhu62cdde52017-05-20 22:16:03 -040077 return(kable_input)
78 }
Adrien Le Guilloufdffec62019-09-04 00:03:52 +020079
Hao Zhu49483bf2017-09-12 11:21:00 -040080 if (is.null(index)) {
81 if (kable_format == "html") {
Hao Zhu779569e2019-04-26 17:06:33 -040082 if (!missing(latex_align)) warning("latex_align parameter is not used in HTML Mode,
Salzer15550852018-02-12 22:09:14 -050083 use label_row_css instead.")
Hao Zhu49483bf2017-09-12 11:21:00 -040084 return(group_rows_html(kable_input, group_label, start_row, end_row,
Adrien Le Guilloufdffec62019-09-04 00:03:52 +020085 label_row_css, escape, colnum, indent,
Hao Zhu03e33332020-08-19 01:09:43 -040086 bold, italic, monospace, underline, strikeout,
87 color, background))}
Hao Zhu49483bf2017-09-12 11:21:00 -040088 if (kable_format == "latex") {
89 return(group_rows_latex(kable_input, group_label, start_row, end_row,
georgegui4cc925b2018-03-01 12:02:45 -060090 latex_gap_space, escape, latex_align, colnum,
Hao Zhu2742ffc2018-10-17 11:23:44 -040091 bold, italic, hline_before, hline_after,
Hao Zhu03e33332020-08-19 01:09:43 -040092 extra_latex_after, indent, latex_wrap_text,
93 monospace, underline, strikeout,
94 color, background))
Hao Zhu49483bf2017-09-12 11:21:00 -040095 }
96 } else {
97 index <- group_row_index_translator(index)
98 out <- kable_input
99 if (kable_format == "html") {
100 for (i in 1:nrow(index)) {
Hao Zhu779569e2019-04-26 17:06:33 -0400101 if (!missing(latex_align)) warning("latex_align parameter is not used in HTML Mode,
Salzer15550852018-02-12 22:09:14 -0500102 use label_row_css instead.")
Hao Zhu49483bf2017-09-12 11:21:00 -0400103 out <- group_rows_html(out, index$header[i],
104 index$start[i], index$end[i],
Adrien Le Guilloufdffec62019-09-04 00:03:52 +0200105 label_row_css, escape, colnum, indent,
Hao Zhu03e33332020-08-19 01:09:43 -0400106 bold, italic, monospace, underline, strikeout,
107 color, background)
Hao Zhu49483bf2017-09-12 11:21:00 -0400108 }
109 }
110 if (kable_format == "latex") {
111 for (i in 1:nrow(index)) {
112 out <- group_rows_latex(out, index$header[i],
113 index$start[i], index$end[i],
georgegui4cc925b2018-03-01 12:02:45 -0600114 latex_gap_space, escape, latex_align, colnum,
Hao Zhu2742ffc2018-10-17 11:23:44 -0400115 bold, italic, hline_before, hline_after,
Hao Zhu03e33332020-08-19 01:09:43 -0400116 extra_latex_after, indent, latex_wrap_text,
117 monospace, underline, strikeout,
118 color, background)
Hao Zhu49483bf2017-09-12 11:21:00 -0400119 }
120 }
121 return(out)
Hao Zhu62cdde52017-05-20 22:16:03 -0400122 }
Hao Zhu49483bf2017-09-12 11:21:00 -0400123}
124
125group_row_index_translator <- function(index) {
126 index <- standardize_header_input(index)
127 index$start <- cumsum(c(1, index$colspan))[1:length(index$colspan)]
128 index$end <- cumsum(index$colspan)
129 index$header <- trimws(index$header)
130 index <- index[index$header != "", ]
131 return(index)
Hao Zhu62cdde52017-05-20 22:16:03 -0400132}
133
Hao Zhud972e7f2017-05-22 13:27:15 -0400134group_rows_html <- function(kable_input, group_label, start_row, end_row,
Hao Zhucedf90e2020-08-12 08:45:34 -0400135 label_row_css, escape, colnum, indent,
Hao Zhu03e33332020-08-19 01:09:43 -0400136 bold, italic, monospace, underline, strikeout,
137 color, background) {
Hao Zhu62cdde52017-05-20 22:16:03 -0400138 kable_attrs <- attributes(kable_input)
Hao Zhu558c72f2017-07-24 15:12:00 -0400139 kable_xml <- read_kable_as_xml(kable_input)
Hao Zhu62cdde52017-05-20 22:16:03 -0400140 kable_tbody <- xml_tpart(kable_xml, "tbody")
141
Hao Zhuac7e70f2017-08-02 00:18:36 -0400142 if (escape) {
143 group_label <- escape_html(group_label)
144 }
145
Hao Zhu62cdde52017-05-20 22:16:03 -0400146 group_header_rows <- attr(kable_input, "group_header_rows")
147 group_seq <- seq(start_row, end_row)
148 if (!is.null(group_header_rows)) {
149 group_seq <- positions_corrector(group_seq, group_header_rows,
150 length(xml_children(kable_tbody)))
jokorn69c8bd52019-06-29 11:11:11 +0200151 # Update the old group_header_rows attribute with their new positions
152 kable_attrs$group_header_rows <- ifelse(kable_attrs$group_header_rows > group_seq[1],
153 kable_attrs$group_header_rows+1,
154 kable_attrs$group_header_rows)
Hao Zhu62cdde52017-05-20 22:16:03 -0400155 }
156
157 # Insert a group header row
158 starting_node <- xml_child(kable_tbody, group_seq[1])
bsalzer85933272018-02-12 17:26:46 -0500159 kable_ncol <- ifelse(is.null(colnum),
160 length(xml_children(starting_node)),
161 colnum)
Adrien Le Guilloufdffec62019-09-04 00:03:52 +0200162
163 if (bold) group_label <- paste0("<strong>", group_label, "</strong>")
164 if (italic) group_label <- paste0("<em>", group_label, "</em>")
165
Hao Zhu8b16a6c2020-08-18 16:59:20 -0400166 if (label_row_css == "border-bottom: 1px solid;") {
167 if (!is.null(attr(kable_input, "lightable_class"))) {
168 lightable_class <- attr(kable_input, "lightable_class")
169 if (lightable_class %in% c(
170 "lightable-classic", "lightable-classic-2", "lightable-minimal")) {
171 label_row_css <- "border-bottom: 0;"
172 }
173 if (lightable_class %in% c("lightable-paper")) {
174 label_row_css <- "border-bottom: 1px solid #00000020;"
175 }
176 if (lightable_class %in% c("lightable-material")) {
177 label_row_css <- "border-bottom: 1px solid #eee; "
178 }
179 if (lightable_class %in% c("lightable-material-dark")) {
180 label_row_css <- "border-bottom: 1px solid #FFFFFF12; color: #FFFFFF60;"
181 }
182 }
183 }
Hao Zhu03e33332020-08-19 01:09:43 -0400184 if (monospace) {
185 label_row_css <- paste0(label_row_css, "font-family: monospace;")
186 }
187 if (underline) {
188 label_row_css <- paste0(label_row_css, "text-decoration: underline;")
189 }
190 if (strikeout) {
191 label_row_css <- paste0(label_row_css, "text-decoration: line-through;")
192 }
193 if (!is.null(color)) {
194 label_row_css <- paste0(label_row_css, "color: ", html_color(color),
195 " !important;")
196 }
197 if (!is.null(background)) {
198 label_row_css <- paste0(label_row_css, "background-color: ",
199 html_color(background), " !important;")
200 }
Hao Zhu8b16a6c2020-08-18 16:59:20 -0400201
Hao Zhu62cdde52017-05-20 22:16:03 -0400202 group_header_row_text <- paste0(
Hao Zhud972e7f2017-05-22 13:27:15 -0400203 '<tr groupLength="', length(group_seq), '"><td colspan="', kable_ncol,
Adrien Le Guilloufdffec62019-09-04 00:03:52 +0200204 '" style="', label_row_css, '">', group_label, "</td></tr>")
205
Hao Zhu62cdde52017-05-20 22:16:03 -0400206 group_header_row <- read_xml(group_header_row_text, options = "COMPACT")
207 xml_add_sibling(starting_node, group_header_row, .where = "before")
208
209 # add indentations to items
Hao Zhuf2dfd142017-07-24 14:43:28 -0400210 out <- as_kable_xml(kable_xml)
Hao Zhu62cdde52017-05-20 22:16:03 -0400211 attributes(out) <- kable_attrs
212 attr(out, "group_header_rows") <- c(attr(out, "group_header_rows"), group_seq[1])
Hao Zhu2742ffc2018-10-17 11:23:44 -0400213 if (indent) {
214 out <- add_indent_html(out, positions = seq(start_row, end_row))
215 }
Hao Zhu62cdde52017-05-20 22:16:03 -0400216 return(out)
217}
Hao Zhud972e7f2017-05-22 13:27:15 -0400218
Hao Zhufc14c9b2017-05-22 14:03:22 -0400219group_rows_latex <- function(kable_input, group_label, start_row, end_row,
georgegui4cc925b2018-03-01 12:02:45 -0600220 gap_space, escape, latex_align, colnum,
Hao Zhu779569e2019-04-26 17:06:33 -0400221 bold = T, italic = F, hline_before = F, hline_after = F,
Hao Zhu03e33332020-08-19 01:09:43 -0400222 extra_latex_after = NULL, indent, latex_wrap_text = F,
223 monospace = F, underline = F, strikeout = F,
224 color = NULL, background = NULL) {
Hao Zhud972e7f2017-05-22 13:27:15 -0400225 table_info <- magic_mirror(kable_input)
Hao Zhu3fc0e882018-04-03 16:06:41 -0400226 out <- solve_enc(kable_input)
Hao Zhud972e7f2017-05-22 13:27:15 -0400227
Hao Zhu064990d2017-10-17 18:08:42 -0400228 if (table_info$duplicated_rows) {
229 dup_fx_out <- fix_duplicated_rows_latex(out, table_info)
230 out <- dup_fx_out[[1]]
231 table_info <- dup_fx_out[[2]]
232 }
233
Hao Zhuac7e70f2017-08-02 00:18:36 -0400234 if (escape) {
Hao Zhuf94a26f2018-04-05 17:42:55 -0400235 group_label <- input_escape(group_label, latex_align)
Hao Zhuac7e70f2017-08-02 00:18:36 -0400236 }
237
Hao Zhuf94a26f2018-04-05 17:42:55 -0400238 if (bold) {
georgegui4cc925b2018-03-01 12:02:45 -0600239 group_label <- paste0("\\\\textbf{", group_label, "}")
240 }
Hao Zhu779569e2019-04-26 17:06:33 -0400241
Hao Zhuf94a26f2018-04-05 17:42:55 -0400242 if (italic) group_label <- paste0("\\\\textit{", group_label, "}")
Hao Zhu03e33332020-08-19 01:09:43 -0400243
244 if (monospace) {
245 group_label <- paste0("\\\\ttfamily\\{", group_label, "\\}")
246 }
247 if (underline) {
248 group_label <- paste0("\\\\underline\\{", group_label, "\\}")
249 }
250 if (strikeout) {
251 group_label <- paste0("\\\\sout\\{", group_label, "\\}")
252 }
253 if (!is.null(color)) {
254 group_label <- paste0("\\\\textcolor", latex_color(color), "\\{",
255 group_label, "\\}")
256 }
257 if (!is.null(background)) {
258 group_label <- paste0("\\\\cellcolor", latex_color(background), "\\{",
259 group_label, "\\}")
260 }
Hao Zhud972e7f2017-05-22 13:27:15 -0400261 # Add group label
Hao Zhu779569e2019-04-26 17:06:33 -0400262 if (latex_wrap_text) {
263 latex_align <- switch(
264 latex_align,
265 "l" = "p{\\\\linewidth}",
266 "c" = ">{\\\\centering\\\\arraybackslash}p{\\\\linewidth}",
267 "r" = ">{\\\\centering\\\\arraybackslash}p{\\\\linewidth}"
268 )
269 }
270
271
Hao Zhu334376d2020-08-19 00:45:09 -0400272 rowtext <- table_info$contents[start_row + table_info$position_offset]
Hao Zhud972e7f2017-05-22 13:27:15 -0400273 if (table_info$booktabs) {
Hao Zhu334376d2020-08-19 00:45:09 -0400274 pre_rowtext <- paste0("\\\\addlinespace[", gap_space, "]\n")
Hao Zhud972e7f2017-05-22 13:27:15 -0400275 } else {
Hao Zhu334376d2020-08-19 00:45:09 -0400276 pre_rowtext <- ''
277 hline_after <- TRUE
Hao Zhud972e7f2017-05-22 13:27:15 -0400278 }
Hao Zhu334376d2020-08-19 00:45:09 -0400279 pre_rowtext <- paste0(
280 pre_rowtext,
281 ifelse(hline_before,"\\\\hline\n", ""),
282 "\\\\multicolumn{", ifelse(is.null(colnum),
283 table_info$ncol,
284 colnum),
285 "}{", latex_align,"}{", group_label,
286 "}\\\\\\\\\n", ifelse(hline_after, "\\\\hline\n", '')
287 )
georgegui4cc925b2018-03-01 12:02:45 -0600288 if(!is.null(extra_latex_after)){
289 pre_rowtext <- paste0(pre_rowtext,
290 regex_escape(extra_latex_after, double_backslash = TRUE))
291 }
292 new_rowtext <- paste0(pre_rowtext, rowtext)
Hao Zhu9c3007e2020-08-03 13:52:38 -0400293 if (start_row + 1 == table_info$nrow &
Hao Zhu334376d2020-08-19 00:45:09 -0400294 !is.null(table_info$repeat_header_latex) & table_info$booktabs) {
Hao Zhu9c3007e2020-08-03 13:52:38 -0400295 out <- sub(paste0(rowtext, "\\\\\\\\\\*\n"),
296 paste0(new_rowtext, "\\\\\\\\\\*\n"),
297 out)
298 } else {
299 out <- sub(paste0(rowtext, "\\\\\\\\\n"),
300 paste0(new_rowtext, "\\\\\\\\\n"),
301 out)
302 }
303
Hao Zhu8f202992017-07-15 02:20:18 -0400304 out <- gsub("\\\\addlinespace\n", "", out)
Hao Zhud2c0f732017-08-26 10:40:14 -0400305 out <- structure(out, format = "latex", class = "knitr_kable")
Hao Zhu32f43f72017-06-20 18:24:54 -0400306 table_info$group_rows_used <- TRUE
307 attr(out, "kable_meta") <- table_info
Hao Zhu2742ffc2018-10-17 11:23:44 -0400308 if (indent) {
309 out <- add_indent_latex(out, seq(start_row, end_row))
310 }
Hao Zhud972e7f2017-05-22 13:27:15 -0400311 return(out)
312}
Hao Zhube853f72018-05-20 18:52:26 -0400313
314#' Automatically figuring out the group_row index
315#'
316#' @description This helper function allows users to build the `group_row`
317#' index more quickly and use `group_rows` in a way that is similar with
318#' `collapse_rows`.
319#'
320#' @param x The index column. A vector. For example `c("a", "a", "b", "b", "b")``
321#'
322#' @export
323auto_index <- function(x) {
324 x_rle <- rle(x)
325 index <- x_rle$lengths
326 names(index) <- x_rle$values
327 return(index)
328}
Hao Zhu72917f92019-03-15 18:41:42 -0400329
330#' @rdname group_rows
331#' @export
332pack_rows <- group_rows