blob: f9602e8a5b1b6ad4bf1fd4382cdde6f3c13c0458 [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 Zhuf4b35292017-06-25 22:38:37 -100012#' @export
13collapse_rows <- function(kable_input, columns = NULL) {
14 # if (is.null(columns)) {
15 # stop("Please specify numeric positions of columns you want to collapse.")
16 # }
Hao Zhu2a87e8e2017-06-14 15:49:33 -040017 kable_format <- attr(kable_input, "format")
18 if (!kable_format %in% c("html", "latex")) {
19 message("Currently generic markdown table using pandoc is not supported.")
20 return(kable_input)
21 }
22 if (kable_format == "html") {
23 return(collapse_rows_html(kable_input, columns))
24 }
25 if (kable_format == "latex") {
26 return(collapse_rows_latex(kable_input, columns))
27 }
28}
29
30collapse_rows_html <- function(kable_input, columns) {
31 kable_attrs <- attributes(kable_input)
32 kable_xml <- read_xml(as.character(kable_input), options = "COMPACT")
33 kable_tbody <- xml_tpart(kable_xml, "tbody")
34
35 kable_dt <- rvest::html_table(xml2::read_html(as.character(kable_input)))[[1]]
Hao Zhuf4b35292017-06-25 22:38:37 -100036 if (is.null(columns)) {
37 columns <- seq(1, ncol(kable_dt))
38 }
Hao Zhu2a87e8e2017-06-14 15:49:33 -040039 kable_dt$row_id <- rownames(kable_dt)
40 collapse_matrix <- collapse_row_matrix(kable_dt, columns)
41
42 for (i in 1:nrow(collapse_matrix)) {
43 matrix_row <- collapse_matrix[i, ]
Hao Zhu3166f062017-06-26 07:51:46 -100044 target_row <- xml_child(kable_tbody, i)
45 row_node_rm_count <- 0
46 for (j in 1:length(matrix_row)) {
47 collapsing_col <- as.numeric(sub("x", "", names(matrix_row)[j])) -
48 row_node_rm_count
49 target_cell <- xml_child(target_row, collapsing_col)
50 if (matrix_row[j] == 0) {
51 xml_remove(target_cell)
52 row_node_rm_count <- row_node_rm_count + 1
53 } else if (matrix_row[j] != 1) {
54 xml_attr(target_cell, "rowspan") <- matrix_row[j]
55 xml_attr(target_cell, "style") <- paste0(
56 xml_attr(target_cell, "style"),
57 "vertical-align: middle !important;")
Hao Zhu2a87e8e2017-06-14 15:49:33 -040058 }
59 }
60 }
61
62 out <- structure(as.character(kable_xml), format = "html",
63 class = "knitr_kable")
64 attributes(out) <- kable_attrs
65 return(out)
66}
67
Hao Zhuf4b35292017-06-25 22:38:37 -100068collapse_row_matrix <- function(kable_dt, columns, html = T) {
69 if (html) {
70 column_block <- function(x) c(x, rep(0, x - 1))
71 } else {
72 column_block <- function(x) c(rep(0, x - 1), x)
73 }
74 mapping_matrix <- list()
75 for (i in columns) {
76 mapping_matrix[[paste0("x", i)]] <- unlist(lapply(
77 rle(kable_dt[, i])$length, column_block))
78 }
79 mapping_matrix <- data.frame(mapping_matrix)
80 return(mapping_matrix)
81}
82
Hao Zhu2a87e8e2017-06-14 15:49:33 -040083collapse_rows_latex <- function(kable_input, columns) {
Hao Zhuf4b35292017-06-25 22:38:37 -100084 table_info <- magic_mirror(kable_input)
85 if (is.null(columns)) {
86 columns <- seq(1, table_info$ncol)
87 }
88 if (!table_info$booktabs) {
89 warning("add_header_left only supports LaTeX table with booktabs. Please",
90 " use kable(..., booktabs = T) in your kable function.")
91 }
92 out <- as.character(kable_input)
93 contents <- table_info$contents
94 kable_dt <- kable_dt_latex(contents)
95 collapse_matrix <- collapse_row_matrix(kable_dt, columns, html = F)
96
97 new_kable_dt <- kable_dt
98 new_contents <- c()
99 for (j in seq(1:ncol(collapse_matrix))) {
100 column_align <- table_info$align_vector_origin[columns[j]]
101 column_width <- ifelse(
102 is.null(table_info$column_width[[paste0("column_", columns[j])]]),
103 "*", table_info$column_width[paste0("column_", columns[j])])
104 for (i in seq(1:nrow(collapse_matrix))) {
105 new_kable_dt[i, j] <- collapse_new_dt_item(
106 kable_dt[i, j], collapse_matrix[i, j], column_width, align = column_align
107 )
108 }
109 }
110 for (i in seq(1:nrow(collapse_matrix))) {
111 new_contents[i] <- paste0(new_kable_dt[i, ], collapse = " & ")
112 out <- sub(contents[i + 1], new_contents[i], out)
113 }
114
115 out <- structure(out, format = "latex", class = "knitr_kable")
116 table_info$collapse_rows <- TRUE
117 attr(out, "kable_meta") <- table_info
118 return(out)
119}
120
121kable_dt_latex <- function(x) {
122 data.frame(do.call(rbind, str_split(x[-1], " & ")), stringsAsFactors = FALSE)
123}
124
125collapse_new_dt_item <- function(x, span, width = NULL, align) {
126 if (span == 0) return("")
127 if (span == 1) return(x)
128 out <- paste0(
129 "\\\\multirow\\{", -span, "\\}\\{",
130 ifelse(is.null(width), "\\*", width),
131 "\\}\\{",
132 switch(align,
133 "l" = "\\\\raggedright\\\\arraybackslash ",
134 "c" = "\\\\centering\\\\arraybackslash ",
135 "r" = "\\\\raggedleft\\\\arraybackslash "),
136 x, "\\}"
137 )
138 return(out)
Hao Zhu2a87e8e2017-06-14 15:49:33 -0400139}