blob: ec33c5a34884f6130c15955cd5020ea47cbc9c68 [file] [log] [blame]
Hao Zhu3166f062017-06-26 07:51:46 -10001#' Collapse repeated rows to multirow cell
Hao Zhu2a87e8e2017-06-14 15:49:33 -04002#'
Hao Zhu8a160b12017-06-26 13:41:35 -10003#' @description Collapse same values in columns into multirow cells. This
4#' feature does similar things with `group_rows`. However, unlike `group_rows`,
5#' it analyzes existing columns, finds out rows that can be grouped together,
6#' and make them multirow cells. Note that if you want to use `column_spec` to
7#' specify column styles, you should use `column_spec` before `collapse_rows`.
8#'
9#' @param kable_input Output of `knitr::kable()` with `format` specified
10#' @param columns Numeric column positions where rows need to be collapsed.
Hao Zhu12b0ade2018-01-13 16:19:58 -050011#' @param latex_hline Option controlling the behavior of adding hlines to table.
12#' Choose from `full`, `major`, `none`.
Hao Zhu8a160b12017-06-26 13:41:35 -100013#'
Hao Zhu5a7689e2017-06-26 15:37:24 -100014#' @examples dt <- data.frame(a = c(1, 1, 2, 2), b = c("a", "a", "a", "b"))
15#' x <- knitr::kable(dt, "html")
16#' collapse_rows(x)
17#'
Hao Zhuf4b35292017-06-25 22:38:37 -100018#' @export
Hao Zhu12b0ade2018-01-13 16:19:58 -050019collapse_rows <- function(kable_input, columns = NULL,
20 latex_hline = c("full", "major", "none")) {
Hao Zhuf4b35292017-06-25 22:38:37 -100021 # if (is.null(columns)) {
22 # stop("Please specify numeric positions of columns you want to collapse.")
23 # }
Hao Zhu2a87e8e2017-06-14 15:49:33 -040024 kable_format <- attr(kable_input, "format")
25 if (!kable_format %in% c("html", "latex")) {
Hao Zhu401ebd82018-01-14 17:10:20 -050026 warning("Please specify format in kable. kableExtra can customize either ",
27 "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ",
28 "for details.")
Hao Zhu2a87e8e2017-06-14 15:49:33 -040029 return(kable_input)
30 }
31 if (kable_format == "html") {
32 return(collapse_rows_html(kable_input, columns))
33 }
34 if (kable_format == "latex") {
Hao Zhu12b0ade2018-01-13 16:19:58 -050035 latex_hline <- match.arg(latex_hline, c("full", "major", "none"))
36 return(collapse_rows_latex(kable_input, columns, latex_hline))
Hao Zhu2a87e8e2017-06-14 15:49:33 -040037 }
38}
39
40collapse_rows_html <- function(kable_input, columns) {
41 kable_attrs <- attributes(kable_input)
Hao Zhu558c72f2017-07-24 15:12:00 -040042 kable_xml <- read_kable_as_xml(kable_input)
Hao Zhu2a87e8e2017-06-14 15:49:33 -040043 kable_tbody <- xml_tpart(kable_xml, "tbody")
44
45 kable_dt <- rvest::html_table(xml2::read_html(as.character(kable_input)))[[1]]
Hao Zhuf4b35292017-06-25 22:38:37 -100046 if (is.null(columns)) {
47 columns <- seq(1, ncol(kable_dt))
48 }
Hao Zhu23456762018-03-26 12:30:10 -040049 if (!is.null(kable_attrs$header_above)) {
50 kable_dt_col_names <- unlist(kable_dt[kable_attrs$header_above, ])
51 kable_dt <- kable_dt[-(1:kable_attrs$header_above),]
52 names(kable_dt) <- kable_dt_col_names
53 }
54 kable_dt$row_id <- seq(nrow(kable_dt))
Hao Zhu2a87e8e2017-06-14 15:49:33 -040055 collapse_matrix <- collapse_row_matrix(kable_dt, columns)
56
57 for (i in 1:nrow(collapse_matrix)) {
58 matrix_row <- collapse_matrix[i, ]
Hao Zhu38cdcdb2017-06-27 09:08:30 -100059 names(matrix_row) <- names(collapse_matrix)
Hao Zhu3166f062017-06-26 07:51:46 -100060 target_row <- xml_child(kable_tbody, i)
61 row_node_rm_count <- 0
62 for (j in 1:length(matrix_row)) {
63 collapsing_col <- as.numeric(sub("x", "", names(matrix_row)[j])) -
64 row_node_rm_count
65 target_cell <- xml_child(target_row, collapsing_col)
66 if (matrix_row[j] == 0) {
67 xml_remove(target_cell)
68 row_node_rm_count <- row_node_rm_count + 1
69 } else if (matrix_row[j] != 1) {
70 xml_attr(target_cell, "rowspan") <- matrix_row[j]
71 xml_attr(target_cell, "style") <- paste0(
72 xml_attr(target_cell, "style"),
73 "vertical-align: middle !important;")
Hao Zhu2a87e8e2017-06-14 15:49:33 -040074 }
75 }
76 }
77
Hao Zhuf2dfd142017-07-24 14:43:28 -040078 out <- as_kable_xml(kable_xml)
Hao Zhu2a87e8e2017-06-14 15:49:33 -040079 attributes(out) <- kable_attrs
Hao Zhuf2100832018-01-11 16:20:29 -050080 if (!"kableExtra" %in% class(out)) class(out) <- c("kableExtra", class(out))
Hao Zhu2a87e8e2017-06-14 15:49:33 -040081 return(out)
82}
83
Hao Zhuf4b35292017-06-25 22:38:37 -100084collapse_row_matrix <- function(kable_dt, columns, html = T) {
85 if (html) {
86 column_block <- function(x) c(x, rep(0, x - 1))
87 } else {
88 column_block <- function(x) c(rep(0, x - 1), x)
89 }
90 mapping_matrix <- list()
91 for (i in columns) {
92 mapping_matrix[[paste0("x", i)]] <- unlist(lapply(
93 rle(kable_dt[, i])$length, column_block))
94 }
95 mapping_matrix <- data.frame(mapping_matrix)
96 return(mapping_matrix)
97}
98
Hao Zhu12b0ade2018-01-13 16:19:58 -050099collapse_rows_latex <- function(kable_input, columns, latex_hline) {
Hao Zhuf4b35292017-06-25 22:38:37 -1000100 table_info <- magic_mirror(kable_input)
Hao Zhu064990d2017-10-17 18:08:42 -0400101 out <- enc2utf8(as.character(kable_input))
102
Hao Zhuf4b35292017-06-25 22:38:37 -1000103 if (is.null(columns)) {
104 columns <- seq(1, table_info$ncol)
105 }
Hao Zhu064990d2017-10-17 18:08:42 -0400106
Hao Zhuf4b35292017-06-25 22:38:37 -1000107 contents <- table_info$contents
108 kable_dt <- kable_dt_latex(contents)
Hao Zhu01b15b82018-01-12 17:48:21 -0500109 collapse_matrix <- collapse_row_matrix(kable_dt, columns, html = FALSE)
Hao Zhuf4b35292017-06-25 22:38:37 -1000110
111 new_kable_dt <- kable_dt
Hao Zhuf4b35292017-06-25 22:38:37 -1000112 for (j in seq(1:ncol(collapse_matrix))) {
113 column_align <- table_info$align_vector_origin[columns[j]]
114 column_width <- ifelse(
115 is.null(table_info$column_width[[paste0("column_", columns[j])]]),
116 "*", table_info$column_width[paste0("column_", columns[j])])
117 for (i in seq(1:nrow(collapse_matrix))) {
118 new_kable_dt[i, j] <- collapse_new_dt_item(
119 kable_dt[i, j], collapse_matrix[i, j], column_width, align = column_align
120 )
121 }
122 }
Hao Zhu654c91f2017-07-03 14:03:34 -0400123
124 midrule_matrix <- collapse_row_matrix(kable_dt, seq(1, table_info$ncol),
125 html = F)
126 midrule_matrix[setdiff(seq(1, table_info$ncol), columns)] <- 1
127
128 ex_bottom <- length(contents) - 1
129 contents[2:ex_bottom] <- paste0(contents[2:ex_bottom], "\\\\\\\\")
130 if (!table_info$booktabs) {
131 contents[2:ex_bottom] <- paste0(contents[2:ex_bottom], "\n\\\\hline")
132 }
Hao Zhu01b15b82018-01-12 17:48:21 -0500133
134 new_contents <- c()
Hao Zhuf4b35292017-06-25 22:38:37 -1000135 for (i in seq(1:nrow(collapse_matrix))) {
136 new_contents[i] <- paste0(new_kable_dt[i, ], collapse = " & ")
Hao Zhu12b0ade2018-01-13 16:19:58 -0500137 table_info$contents[i + 1] <- new_contents[i]
Hao Zhu654c91f2017-07-03 14:03:34 -0400138 if (i != nrow(collapse_matrix)) {
Hao Zhu12b0ade2018-01-13 16:19:58 -0500139 row_midrule <- switch(
140 latex_hline,
141 "none" = "",
142 "full" = midline_groups(which(as.numeric(midrule_matrix[i, ]) > 0),
143 table_info$booktabs),
144 "major" = ifelse(
145 sum(as.numeric(midrule_matrix[i, ]) > 0) == ncol(midrule_matrix),
146 midline_groups(which(as.numeric(midrule_matrix[i, ]) > 0),
147 table_info$booktabs),
148 ""
149 )
150 )
Hao Zhu654c91f2017-07-03 14:03:34 -0400151 new_contents[i] <- paste0(new_contents[i], "\\\\\\\\\n", row_midrule)
152 }
Hao Zhuf4b35292017-06-25 22:38:37 -1000153 out <- sub(contents[i + 1], new_contents[i], out)
154 }
Hao Zhu8f202992017-07-15 02:20:18 -0400155 out <- gsub("\\\\addlinespace\n", "", out)
Hao Zhuf4b35292017-06-25 22:38:37 -1000156
157 out <- structure(out, format = "latex", class = "knitr_kable")
158 table_info$collapse_rows <- TRUE
159 attr(out, "kable_meta") <- table_info
160 return(out)
161}
162
163kable_dt_latex <- function(x) {
164 data.frame(do.call(rbind, str_split(x[-1], " & ")), stringsAsFactors = FALSE)
165}
166
167collapse_new_dt_item <- function(x, span, width = NULL, align) {
168 if (span == 0) return("")
169 if (span == 1) return(x)
170 out <- paste0(
171 "\\\\multirow\\{", -span, "\\}\\{",
172 ifelse(is.null(width), "\\*", width),
173 "\\}\\{",
174 switch(align,
175 "l" = "\\\\raggedright\\\\arraybackslash ",
176 "c" = "\\\\centering\\\\arraybackslash ",
177 "r" = "\\\\raggedleft\\\\arraybackslash "),
178 x, "\\}"
179 )
180 return(out)
Hao Zhu2a87e8e2017-06-14 15:49:33 -0400181}
Hao Zhu654c91f2017-07-03 14:03:34 -0400182
183midline_groups <- function(x, booktabs = T) {
184 diffs <- c(1, diff(x))
185 start_indexes <- c(1, which(diffs > 1))
Hao Zhu12b0ade2018-01-13 16:19:58 -0500186 end_indexes <- c(start_indexes - 1, length(x))
Hao Zhu654c91f2017-07-03 14:03:34 -0400187 ranges <- paste0(x[start_indexes], "-", x[end_indexes])
188 if (booktabs) {
189 out <- paste0("\\\\cmidrule{", ranges, "}")
190 } else {
191 out <- paste0("\\\\cline{", ranges, "}")
192 }
193 out <- paste0(out, collapse = "\n")
194 return(out)
195}