blob: 5017159313dc2d25fef0b8f2d1201316ade0c5a2 [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.
bsalzer85933272018-02-12 17:26:46 -050026#' @param colnum A numeric that determines how many columns the text should span.
27#' The default setting will have the text span the entire length.
georgegui4cc925b2018-03-01 12:02:45 -060028#' @param bold A T/F value to control whether the text should be bolded.
29#' @param italic A T/F value to control whether the text should to be emphasized.
Brian Salzera3f43ad2018-03-04 15:03:20 -050030#' @param hline_before A T/F value that addes a horizontal line before the group_row label. Default
31#' value is False.
georgegui4cc925b2018-03-01 12:02:45 -060032#' @param hline_after A replicate of `hline.after` in xtable. It
Hao Zhu6191e742018-03-01 13:09:08 -050033#' addes a hline after the row
georgegui4cc925b2018-03-01 12:02:45 -060034#' @param extra_latex_after Extra LaTeX text to be added after the row.
Hao Zhu2742ffc2018-10-17 11:23:44 -040035#' @param indent A T?F value to control whether list items are indented.
Hao Zhubd95bb22017-05-22 16:08:49 -040036#'
Hao Zhu78e61222017-05-24 20:53:35 -040037#' @examples x <- knitr::kable(head(mtcars), "html")
38#' # Put Row 2 to Row 5 into a Group and label it as "Group A"
39#' group_rows(x, "Group A", 2, 5)
40#'
Hao Zhu62cdde52017-05-20 22:16:03 -040041#' @export
Hao Zhu49483bf2017-09-12 11:21:00 -040042group_rows <- function(kable_input, group_label = NULL,
43 start_row = NULL, end_row = NULL,
44 index = NULL,
Hao Zhud972e7f2017-05-22 13:27:15 -040045 label_row_css = "border-bottom: 1px solid;",
Hao Zhu49483bf2017-09-12 11:21:00 -040046 latex_gap_space = "0.3em",
georgegui4cc925b2018-03-01 12:02:45 -060047 escape = TRUE, latex_align = "l", colnum = NULL,
Hao Zhu2742ffc2018-10-17 11:23:44 -040048 bold = TRUE,
49 italic = FALSE,
50 hline_before = FALSE,
51 hline_after = FALSE,
52 extra_latex_after = NULL,
53 indent = TRUE) {
Hao Zhu49483bf2017-09-12 11:21:00 -040054
Hao Zhu62cdde52017-05-20 22:16:03 -040055 kable_format <- attr(kable_input, "format")
56 if (!kable_format %in% c("html", "latex")) {
Hao Zhu401ebd82018-01-14 17:10:20 -050057 warning("Please specify format in kable. kableExtra can customize either ",
58 "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ",
59 "for details.")
Hao Zhu62cdde52017-05-20 22:16:03 -040060 return(kable_input)
61 }
Hao Zhu49483bf2017-09-12 11:21:00 -040062 if (is.null(index)) {
63 if (kable_format == "html") {
Salzer8f49eb62018-02-12 22:19:06 -050064 if(!missing(latex_align)) warning("latex_align parameter is not used in HTML Mode,
Salzer15550852018-02-12 22:09:14 -050065 use label_row_css instead.")
Hao Zhu49483bf2017-09-12 11:21:00 -040066 return(group_rows_html(kable_input, group_label, start_row, end_row,
Hao Zhu2742ffc2018-10-17 11:23:44 -040067 label_row_css, escape, colnum, indent))
Hao Zhu49483bf2017-09-12 11:21:00 -040068 }
69 if (kable_format == "latex") {
70 return(group_rows_latex(kable_input, group_label, start_row, end_row,
georgegui4cc925b2018-03-01 12:02:45 -060071 latex_gap_space, escape, latex_align, colnum,
Hao Zhu2742ffc2018-10-17 11:23:44 -040072 bold, italic, hline_before, hline_after,
73 extra_latex_after, indent))
Hao Zhu49483bf2017-09-12 11:21:00 -040074 }
75 } else {
76 index <- group_row_index_translator(index)
77 out <- kable_input
78 if (kable_format == "html") {
79 for (i in 1:nrow(index)) {
Salzer8f49eb62018-02-12 22:19:06 -050080 if(!missing(latex_align)) warning("latex_align parameter is not used in HTML Mode,
Salzer15550852018-02-12 22:09:14 -050081 use label_row_css instead.")
Hao Zhu49483bf2017-09-12 11:21:00 -040082 out <- group_rows_html(out, index$header[i],
83 index$start[i], index$end[i],
Hao Zhu2742ffc2018-10-17 11:23:44 -040084 label_row_css, escape, colnum, indent)
Hao Zhu49483bf2017-09-12 11:21:00 -040085 }
86 }
87 if (kable_format == "latex") {
88 for (i in 1:nrow(index)) {
89 out <- group_rows_latex(out, index$header[i],
90 index$start[i], index$end[i],
georgegui4cc925b2018-03-01 12:02:45 -060091 latex_gap_space, escape, latex_align, colnum,
Hao Zhu2742ffc2018-10-17 11:23:44 -040092 bold, italic, hline_before, hline_after,
93 extra_latex_after, indent)
Hao Zhu49483bf2017-09-12 11:21:00 -040094 }
95 }
96 return(out)
Hao Zhu62cdde52017-05-20 22:16:03 -040097 }
Hao Zhu49483bf2017-09-12 11:21:00 -040098}
99
100group_row_index_translator <- function(index) {
101 index <- standardize_header_input(index)
102 index$start <- cumsum(c(1, index$colspan))[1:length(index$colspan)]
103 index$end <- cumsum(index$colspan)
104 index$header <- trimws(index$header)
105 index <- index[index$header != "", ]
106 return(index)
Hao Zhu62cdde52017-05-20 22:16:03 -0400107}
108
Hao Zhud972e7f2017-05-22 13:27:15 -0400109group_rows_html <- function(kable_input, group_label, start_row, end_row,
Hao Zhu2742ffc2018-10-17 11:23:44 -0400110 label_row_css, escape, colnum, indent) {
Hao Zhu62cdde52017-05-20 22:16:03 -0400111 kable_attrs <- attributes(kable_input)
Hao Zhu558c72f2017-07-24 15:12:00 -0400112 kable_xml <- read_kable_as_xml(kable_input)
Hao Zhu62cdde52017-05-20 22:16:03 -0400113 kable_tbody <- xml_tpart(kable_xml, "tbody")
114
Hao Zhuac7e70f2017-08-02 00:18:36 -0400115 if (escape) {
116 group_label <- escape_html(group_label)
117 }
118
Hao Zhu62cdde52017-05-20 22:16:03 -0400119 group_header_rows <- attr(kable_input, "group_header_rows")
120 group_seq <- seq(start_row, end_row)
121 if (!is.null(group_header_rows)) {
122 group_seq <- positions_corrector(group_seq, group_header_rows,
123 length(xml_children(kable_tbody)))
124 }
125
126 # Insert a group header row
127 starting_node <- xml_child(kable_tbody, group_seq[1])
bsalzer85933272018-02-12 17:26:46 -0500128 kable_ncol <- ifelse(is.null(colnum),
129 length(xml_children(starting_node)),
130 colnum)
Hao Zhu62cdde52017-05-20 22:16:03 -0400131 group_header_row_text <- paste0(
Hao Zhud972e7f2017-05-22 13:27:15 -0400132 '<tr groupLength="', length(group_seq), '"><td colspan="', kable_ncol,
133 '" style="', label_row_css, '"><strong>', group_label,
134 "</strong></td></tr>"
Hao Zhu62cdde52017-05-20 22:16:03 -0400135 )
136 group_header_row <- read_xml(group_header_row_text, options = "COMPACT")
137 xml_add_sibling(starting_node, group_header_row, .where = "before")
138
139 # add indentations to items
Hao Zhuf2dfd142017-07-24 14:43:28 -0400140 out <- as_kable_xml(kable_xml)
Hao Zhu62cdde52017-05-20 22:16:03 -0400141 attributes(out) <- kable_attrs
142 attr(out, "group_header_rows") <- c(attr(out, "group_header_rows"), group_seq[1])
Hao Zhu2742ffc2018-10-17 11:23:44 -0400143 if (indent) {
144 out <- add_indent_html(out, positions = seq(start_row, end_row))
145 }
Hao Zhu62cdde52017-05-20 22:16:03 -0400146 return(out)
147}
Hao Zhud972e7f2017-05-22 13:27:15 -0400148
Hao Zhufc14c9b2017-05-22 14:03:22 -0400149group_rows_latex <- function(kable_input, group_label, start_row, end_row,
georgegui4cc925b2018-03-01 12:02:45 -0600150 gap_space, escape, latex_align, colnum,
Brian Salzera3f43ad2018-03-04 15:03:20 -0500151 bold = T, italic = F, hline_before = F ,hline_after = F,
Hao Zhu2742ffc2018-10-17 11:23:44 -0400152 extra_latex_after = NULL, indent) {
Hao Zhud972e7f2017-05-22 13:27:15 -0400153 table_info <- magic_mirror(kable_input)
Hao Zhu3fc0e882018-04-03 16:06:41 -0400154 out <- solve_enc(kable_input)
Hao Zhud972e7f2017-05-22 13:27:15 -0400155
Hao Zhu064990d2017-10-17 18:08:42 -0400156 if (table_info$duplicated_rows) {
157 dup_fx_out <- fix_duplicated_rows_latex(out, table_info)
158 out <- dup_fx_out[[1]]
159 table_info <- dup_fx_out[[2]]
160 }
161
Hao Zhuac7e70f2017-08-02 00:18:36 -0400162 if (escape) {
Hao Zhuf94a26f2018-04-05 17:42:55 -0400163 group_label <- input_escape(group_label, latex_align)
Hao Zhuac7e70f2017-08-02 00:18:36 -0400164 }
165
Hao Zhuf94a26f2018-04-05 17:42:55 -0400166 if (bold) {
georgegui4cc925b2018-03-01 12:02:45 -0600167 group_label <- paste0("\\\\textbf{", group_label, "}")
168 }
Hao Zhuf94a26f2018-04-05 17:42:55 -0400169 if (italic) group_label <- paste0("\\\\textit{", group_label, "}")
Hao Zhud972e7f2017-05-22 13:27:15 -0400170 # Add group label
Hao Zhud972e7f2017-05-22 13:27:15 -0400171 if (table_info$booktabs) {
Hao Zhu37dbe3f2018-05-14 11:16:06 -0400172 rowtext <- table_info$contents[start_row + table_info$position_offset]
georgegui4cc925b2018-03-01 12:02:45 -0600173 pre_rowtext <- paste0(
Hao Zhud972e7f2017-05-22 13:27:15 -0400174 "\\\\addlinespace[", gap_space, "]\n",
Brian Salzera3f43ad2018-03-04 15:03:20 -0500175 ifelse(hline_before,"\\\\hline\n", ""),
bsalzer85933272018-02-12 17:26:46 -0500176 "\\\\multicolumn{", ifelse(is.null(colnum),
177 table_info$ncol,
178 colnum),
georgegui4cc925b2018-03-01 12:02:45 -0600179 "}{", latex_align, "}{", group_label,
180 "}\\\\\\\\\n", ifelse(hline_after, "\\\\hline\n", '')
Hao Zhud972e7f2017-05-22 13:27:15 -0400181 )
Hao Zhud972e7f2017-05-22 13:27:15 -0400182 } else {
Leo83f05132018-05-10 14:12:16 +0800183 rowtext <- table_info$contents[start_row + 1]
Hao Zhud972e7f2017-05-22 13:27:15 -0400184 rowtext <- paste0("\\\\hline\n", rowtext)
georgegui4cc925b2018-03-01 12:02:45 -0600185 pre_rowtext <- paste0(
186 "\\\\hline\n\\\\multicolumn{", table_info$ncol, "}{", latex_align,"}{",
187 group_label, "}\\\\\\\\\n"
Hao Zhud972e7f2017-05-22 13:27:15 -0400188 )
189 }
georgegui4cc925b2018-03-01 12:02:45 -0600190 if(!is.null(extra_latex_after)){
191 pre_rowtext <- paste0(pre_rowtext,
192 regex_escape(extra_latex_after, double_backslash = TRUE))
193 }
194 new_rowtext <- paste0(pre_rowtext, rowtext)
Quôc Peyroted7e6242018-09-05 14:59:16 +0200195 out <- sub(paste0(rowtext, "\\\\\\\\\n"),
196 paste0(new_rowtext, "\\\\\\\\\n"),
197 out)
Hao Zhu8f202992017-07-15 02:20:18 -0400198 out <- gsub("\\\\addlinespace\n", "", out)
Hao Zhud2c0f732017-08-26 10:40:14 -0400199 out <- structure(out, format = "latex", class = "knitr_kable")
Hao Zhu32f43f72017-06-20 18:24:54 -0400200 table_info$group_rows_used <- TRUE
201 attr(out, "kable_meta") <- table_info
Hao Zhu2742ffc2018-10-17 11:23:44 -0400202 if (indent) {
203 out <- add_indent_latex(out, seq(start_row, end_row))
204 }
Hao Zhud972e7f2017-05-22 13:27:15 -0400205 return(out)
206}
Hao Zhube853f72018-05-20 18:52:26 -0400207
208#' Automatically figuring out the group_row index
209#'
210#' @description This helper function allows users to build the `group_row`
211#' index more quickly and use `group_rows` in a way that is similar with
212#' `collapse_rows`.
213#'
214#' @param x The index column. A vector. For example `c("a", "a", "b", "b", "b")``
215#'
216#' @export
217auto_index <- function(x) {
218 x_rle <- rle(x)
219 index <- x_rle$lengths
220 names(index) <- x_rle$values
221 return(index)
222}