blob: 72ee31ca5dfc4c3224bac6745767fe7280cde00f [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.
11#'
Hao Zhu5a7689e2017-06-26 15:37:24 -100012#' @examples dt <- data.frame(a = c(1, 1, 2, 2), b = c("a", "a", "a", "b"))
13#' x <- knitr::kable(dt, "html")
14#' collapse_rows(x)
15#'
Hao Zhuf4b35292017-06-25 22:38:37 -100016#' @export
17collapse_rows <- function(kable_input, columns = NULL) {
18 # if (is.null(columns)) {
19 # stop("Please specify numeric positions of columns you want to collapse.")
20 # }
Hao Zhu2a87e8e2017-06-14 15:49:33 -040021 kable_format <- attr(kable_input, "format")
22 if (!kable_format %in% c("html", "latex")) {
23 message("Currently generic markdown table using pandoc is not supported.")
24 return(kable_input)
25 }
26 if (kable_format == "html") {
27 return(collapse_rows_html(kable_input, columns))
28 }
29 if (kable_format == "latex") {
30 return(collapse_rows_latex(kable_input, columns))
31 }
32}
33
34collapse_rows_html <- function(kable_input, columns) {
35 kable_attrs <- attributes(kable_input)
36 kable_xml <- read_xml(as.character(kable_input), options = "COMPACT")
37 kable_tbody <- xml_tpart(kable_xml, "tbody")
38
39 kable_dt <- rvest::html_table(xml2::read_html(as.character(kable_input)))[[1]]
Hao Zhuf4b35292017-06-25 22:38:37 -100040 if (is.null(columns)) {
41 columns <- seq(1, ncol(kable_dt))
42 }
Hao Zhu2a87e8e2017-06-14 15:49:33 -040043 kable_dt$row_id <- rownames(kable_dt)
44 collapse_matrix <- collapse_row_matrix(kable_dt, columns)
45
46 for (i in 1:nrow(collapse_matrix)) {
47 matrix_row <- collapse_matrix[i, ]
Hao Zhu38cdcdb2017-06-27 09:08:30 -100048 names(matrix_row) <- names(collapse_matrix)
Hao Zhu3166f062017-06-26 07:51:46 -100049 target_row <- xml_child(kable_tbody, i)
50 row_node_rm_count <- 0
51 for (j in 1:length(matrix_row)) {
52 collapsing_col <- as.numeric(sub("x", "", names(matrix_row)[j])) -
53 row_node_rm_count
54 target_cell <- xml_child(target_row, collapsing_col)
55 if (matrix_row[j] == 0) {
56 xml_remove(target_cell)
57 row_node_rm_count <- row_node_rm_count + 1
58 } else if (matrix_row[j] != 1) {
59 xml_attr(target_cell, "rowspan") <- matrix_row[j]
60 xml_attr(target_cell, "style") <- paste0(
61 xml_attr(target_cell, "style"),
62 "vertical-align: middle !important;")
Hao Zhu2a87e8e2017-06-14 15:49:33 -040063 }
64 }
65 }
66
67 out <- structure(as.character(kable_xml), format = "html",
68 class = "knitr_kable")
69 attributes(out) <- kable_attrs
70 return(out)
71}
72
Hao Zhuf4b35292017-06-25 22:38:37 -100073collapse_row_matrix <- function(kable_dt, columns, html = T) {
74 if (html) {
75 column_block <- function(x) c(x, rep(0, x - 1))
76 } else {
77 column_block <- function(x) c(rep(0, x - 1), x)
78 }
79 mapping_matrix <- list()
80 for (i in columns) {
81 mapping_matrix[[paste0("x", i)]] <- unlist(lapply(
82 rle(kable_dt[, i])$length, column_block))
83 }
84 mapping_matrix <- data.frame(mapping_matrix)
85 return(mapping_matrix)
86}
87
Hao Zhu2a87e8e2017-06-14 15:49:33 -040088collapse_rows_latex <- function(kable_input, columns) {
Hao Zhuf4b35292017-06-25 22:38:37 -100089 table_info <- magic_mirror(kable_input)
90 if (is.null(columns)) {
91 columns <- seq(1, table_info$ncol)
92 }
Hao Zhuf4b35292017-06-25 22:38:37 -100093 out <- as.character(kable_input)
94 contents <- table_info$contents
95 kable_dt <- kable_dt_latex(contents)
96 collapse_matrix <- collapse_row_matrix(kable_dt, columns, html = F)
97
98 new_kable_dt <- kable_dt
99 new_contents <- c()
100 for (j in seq(1:ncol(collapse_matrix))) {
101 column_align <- table_info$align_vector_origin[columns[j]]
102 column_width <- ifelse(
103 is.null(table_info$column_width[[paste0("column_", columns[j])]]),
104 "*", table_info$column_width[paste0("column_", columns[j])])
105 for (i in seq(1:nrow(collapse_matrix))) {
106 new_kable_dt[i, j] <- collapse_new_dt_item(
107 kable_dt[i, j], collapse_matrix[i, j], column_width, align = column_align
108 )
109 }
110 }
Hao Zhu654c91f2017-07-03 14:03:34 -0400111
112 midrule_matrix <- collapse_row_matrix(kable_dt, seq(1, table_info$ncol),
113 html = F)
114 midrule_matrix[setdiff(seq(1, table_info$ncol), columns)] <- 1
115
116 ex_bottom <- length(contents) - 1
117 contents[2:ex_bottom] <- paste0(contents[2:ex_bottom], "\\\\\\\\")
118 if (!table_info$booktabs) {
119 contents[2:ex_bottom] <- paste0(contents[2:ex_bottom], "\n\\\\hline")
120 }
Hao Zhuf4b35292017-06-25 22:38:37 -1000121 for (i in seq(1:nrow(collapse_matrix))) {
122 new_contents[i] <- paste0(new_kable_dt[i, ], collapse = " & ")
Hao Zhu654c91f2017-07-03 14:03:34 -0400123 if (i != nrow(collapse_matrix)) {
124 row_midrule <- midline_groups(which(as.numeric(midrule_matrix[i, ]) > 0),
125 table_info$booktabs)
126 new_contents[i] <- paste0(new_contents[i], "\\\\\\\\\n", row_midrule)
127 }
Hao Zhuf4b35292017-06-25 22:38:37 -1000128 out <- sub(contents[i + 1], new_contents[i], out)
129 }
Hao Zhu654c91f2017-07-03 14:03:34 -0400130 out <- sub("\\\\addlinespace\n", "", out)
Hao Zhuf4b35292017-06-25 22:38:37 -1000131
132 out <- structure(out, format = "latex", class = "knitr_kable")
133 table_info$collapse_rows <- TRUE
134 attr(out, "kable_meta") <- table_info
135 return(out)
136}
137
138kable_dt_latex <- function(x) {
139 data.frame(do.call(rbind, str_split(x[-1], " & ")), stringsAsFactors = FALSE)
140}
141
142collapse_new_dt_item <- function(x, span, width = NULL, align) {
143 if (span == 0) return("")
144 if (span == 1) return(x)
145 out <- paste0(
146 "\\\\multirow\\{", -span, "\\}\\{",
147 ifelse(is.null(width), "\\*", width),
148 "\\}\\{",
149 switch(align,
150 "l" = "\\\\raggedright\\\\arraybackslash ",
151 "c" = "\\\\centering\\\\arraybackslash ",
152 "r" = "\\\\raggedleft\\\\arraybackslash "),
153 x, "\\}"
154 )
155 return(out)
Hao Zhu2a87e8e2017-06-14 15:49:33 -0400156}
Hao Zhu654c91f2017-07-03 14:03:34 -0400157
158midline_groups <- function(x, booktabs = T) {
159 diffs <- c(1, diff(x))
160 start_indexes <- c(1, which(diffs > 1))
161 end_indexes <- c(start_indexes-1, length(x))
162 ranges <- paste0(x[start_indexes], "-", x[end_indexes])
163 if (booktabs) {
164 out <- paste0("\\\\cmidrule{", ranges, "}")
165 } else {
166 out <- paste0("\\\\cline{", ranges, "}")
167 }
168 out <- paste0(out, collapse = "\n")
169 return(out)
170}