blob: ba713a1f80e6b9db678b55726458a1c735295b53 [file] [log] [blame]
Hao Zhu4f10f652017-06-16 14:03:44 -04001#' Add a heading column to the left side of the table
Hao Zhu96a50b52017-06-14 18:09:35 -04002#'
Hao Zhu4f10f652017-06-16 14:03:44 -04003#' @description This function uses the same syntax as add_header_above. It will
4#' add a heading column with grouped rows to the left side of the table. It can
5#' act as an alternative way to `group_rows` to show grouped information. As
6#' `add_header_above`, users can use this function to add multiple layers of
7#' heading columns one by one.
8#'
9#' @param kable_input Output of `knitr::kable()` with `format` specified
10#' @param header A (named) character vector with `rowspan` as values. For
11#' example, `c("xxx" = 1, "title" = 2)` can be used to create a new header column
12#' for a 3-row table with "xxx" spanning row 1 and "title" spanning row 2 & 3 (
13#' two rows). For convenience, when `rowspan` equals to 1, users can drop the
14#' ` = 1` part. As a result, `c("xxx", "title" = 2)` is the same as
15#' `c("xxx" = 1, "title" = 2)`.
16#' @param header_name Column name that that extra column
17#' @param align Column alignment. you can choose from "c", "l" or "r"
18#' @param ... extra variables for latex or html. For LaTeX table, you can have
19#' a TRUE/FALSE option `full_midline` to control if the mid line needs to be
20#' extended to the end of row.
Hao Zhu96a50b52017-06-14 18:09:35 -040021#'
22#' @export
Hao Zhu2ce42b92017-06-15 17:15:33 -040023add_header_left <- function(kable_input, header = NULL, header_name = "",
Hao Zhu520d91b2017-06-16 01:41:26 -040024 align = "c", ...) {
Hao Zhu96a50b52017-06-14 18:09:35 -040025 if (is.null(header)) return(kable_input)
26 kable_format <- attr(kable_input, "format")
27 if (!kable_format %in% c("html", "latex")) {
28 stop("Please specify output format in your kable function. Currently ",
29 "generic markdown table using pandoc is not supported.")
30 }
31 if (kable_format == "html") {
Hao Zhu2ce42b92017-06-15 17:15:33 -040032 return(add_header_left_html(kable_input, header, header_name, align))
Hao Zhu96a50b52017-06-14 18:09:35 -040033 }
34 if (kable_format == "latex") {
Hao Zhu520d91b2017-06-16 01:41:26 -040035 return(add_header_left_latex(kable_input, header, header_name, align, ...))
Hao Zhu96a50b52017-06-14 18:09:35 -040036 }
37}
38
39# HTML
Hao Zhu2ce42b92017-06-15 17:15:33 -040040add_header_left_html <- function(kable_input, header, header_name, align) {
Hao Zhu96a50b52017-06-14 18:09:35 -040041 kable_attrs <- attributes(kable_input)
42 kable_xml <- read_xml(as.character(kable_input), options = "COMPACT")
43 kable_thead <- xml_tpart(kable_xml, "thead")
44 kable_tbody <- xml_tpart(kable_xml, "tbody")
45
Hao Zhu2ce42b92017-06-15 17:15:33 -040046 align <- switch(align, "c" = "center", "l" = "left", "r" = "right")
47
Hao Zhu96a50b52017-06-14 18:09:35 -040048 new_header <- paste0(
Hao Zhu2ce42b92017-06-15 17:15:33 -040049 '<th style="text-align:', align, '; vertical-align: bottom;" rowspan="',
Hao Zhu96a50b52017-06-14 18:09:35 -040050 length(xml_children(kable_thead)), '">', header_name, '</th>'
51 )
52 new_header <- read_xml(new_header, options = c("COMPACT"))
53 xml_add_child(xml_child(kable_thead, 1), new_header, .where = 0)
54
Hao Zhu2ce42b92017-06-15 17:15:33 -040055 header <- standardize_header(header, length(xml_children(kable_tbody)))
Hao Zhu96a50b52017-06-14 18:09:35 -040056 for (i in 1:nrow(header)) {
57 new_row_item <- paste0(
Hao Zhu2ce42b92017-06-15 17:15:33 -040058 '<td style="text-align:', align, '; vertical-align: middle;" rowspan="',
Hao Zhu96a50b52017-06-14 18:09:35 -040059 header$rowspan[i], '">', header$header[i], '</td>')
60 new_row_item <- read_xml(new_row_item, options = "COMPACT")
61 target_row <- xml_child(kable_tbody, header$row[i])
62 xml_add_child(target_row, new_row_item, .where = 0)
63 }
64
65 out <- structure(as.character(kable_xml), format = "html",
66 class = "knitr_kable")
67 attributes(out) <- kable_attrs
68 return(out)
69}
70
Hao Zhu2ce42b92017-06-15 17:15:33 -040071standardize_header <- function(header, n_row) {
Hao Zhu96a50b52017-06-14 18:09:35 -040072 header_names <- names(header)
73
74 if (is.null(header_names)) {
75 return(data.frame(header = header, row = 1:length(header),
76 rowspan = 1, row.names = NULL))
77 }
78
79 names(header)[header_names == ""] <- header[header_names == ""]
80 header[header_names == ""] <- 1
81 header_names <- names(header)
82 header <- as.numeric(header)
83 names(header) <- header_names
84 if (sum(header) < n_row) {
85 header <- c(header, " " = n_row - sum(header))
86 }
87 row_pos <- c(1, cumsum(header)[-length(header)] + 1)
88 return(data.frame(
89 header = names(header),
90 row = row_pos, rowspan = header, row.names = NULL
91 ))
92}
93
Hao Zhu520d91b2017-06-16 01:41:26 -040094add_header_left_latex <- function(kable_input, header, header_name, align,
95 full_midline = F) {
Hao Zhu2ce42b92017-06-15 17:15:33 -040096 table_info <- magic_mirror(kable_input)
97 usepackage_latex("multirow")
98 if (!table_info$booktabs) {
99 warning("add_header_left only supports LaTeX table with booktabs. Please",
100 " use kable(..., booktabs = T) in your kable function.")
101 }
102 out <- as.character(kable_input)
103 contents <- table_info$contents
104 header_name <- escape_latex(header_name)
105 header <- standardize_header(header, length(contents) - 1)
106 header$header <- escape_latex(header$header)
107 header$header <- gsub("\\\\", "\\\\\\\\", header$header)
108 header$row <- header$row + 1
109 header$row_end <- header$row + header$rowspan - 1
110
111 # Align
112 out <- sub(paste0(table_info$begin_tabular, "\\{"),
113 paste0(table_info$begin_tabular, "{", align,
114 ifelse(table_info$booktabs, "", "|")),
115 out, perl = T)
Hao Zhu520d91b2017-06-16 01:41:26 -0400116 table_info$align_vector <- c(align, table_info$align_vector)
Hao Zhu2ce42b92017-06-15 17:15:33 -0400117
118 # Header
119 if (!is.null(table_info$new_header_row)) {
120 new_header_row <- table_info$new_header_row
121 for (i in 1:length(new_header_row)) {
122 out <- sub(regex_escape(new_header_row[i]),
123 paste0(" & ", new_header_row[i]), out)
124 cline_old <- cline_gen(table_info$header_df[[i]], table_info$booktabs)
125 cline_old <- regex_escape(cline_old)
126 table_info$header_df[[i]] <- rbind(
127 data.frame(header = " ", colspan = 1),
128 table_info$header_df[[i]]
129 )
130 cline_new <- cline_gen(table_info$header_df[[i]], table_info$booktabs)
131 out <- sub(cline_old, cline_new, out)
132 }
133 }
134 out <- sub(contents[1], paste0(header_name, " & ", contents[1]), out)
135 table_info$contents[1] <- paste0(header_name, " & ", contents[1])
136
137 # move existing midrules if exists
138 out_lines <- read_lines(out)
139 tbody_start_row <- which(out_lines == "\\midrule")
140 tbody_end_row <- which(out_lines == "\\bottomrule")
141 before_tbody <- out_lines[seq(1, tbody_start_row)]
142 tbody <- out_lines[seq(tbody_start_row + 1, tbody_end_row - 1)]
143 after_tbody <- out_lines[seq(tbody_end_row, length(out_lines))]
144
145 # Remove addlinespace in this case
146 tbody <- tbody[tbody != "\\addlinespace"]
147
148 midrule_exist <- str_sub(tbody, 1, 9) == "\\cmidrule"
149 if (sum(midrule_exist) > 0) {
150 existing_midrules <- which(midrule_exist)
151 tbody[existing_midrules] <- unlist(lapply(
152 tbody[existing_midrules], cmidrule_plus_one
153 ))
154 out <- paste0(c(before_tbody, tbody, after_tbody), collapse = "\n")
155 }
156
157 for (j in 1:nrow(header)) {
158 new_row_pre <- paste0(
159 "\\\\multirow\\{", -header$rowspan[j], "\\}\\{\\*\\}\\{", header$header[j], "\\} & "
160 )
161 new_row_text <- paste0(new_row_pre, contents[header$row_end[j]])
162 out <- sub(contents[header$row_end[j]], new_row_text, out)
163 table_info$contents[header$row_end[j]] <- new_row_text
164 if (j != nrow(header)) {
165 out <- sub(
166 paste0(contents[header$row_end[j]], "\\\\\\\\\n"),
167 paste0(contents[header$row_end[j]],
Hao Zhu520d91b2017-06-16 01:41:26 -0400168 "\\\\\\\\\n\\\\cmidrule[0.5pt](l{2pt}r{2pt}){1-",
169 ifelse(full_midline, str_count(tbody[1], " & ") + 2, 1),
170 "}\n"),
Hao Zhu2ce42b92017-06-15 17:15:33 -0400171 out
172 )
173 }
174 }
175
176 for (k in setdiff(seq(2, length(contents)), header$row_end)) {
177 out <- sub(contents[k],
178 paste0(" & ", contents[k]),
179 out)
180 table_info$contents[k] <- paste0(" & ", contents[k])
181 }
182
183 out <- structure(out, format = "latex", class = "knitr_kable")
184 attr(out, "original_kable_meta") <- table_info
185 return(out)
186}
187
188cmidrule_plus_one <- function(x) {
189 start_pos <- as.numeric(str_match(x, "\\)\\{(.*)-")[2]) + 1
190 stop_pos <- as.numeric(str_match(x, "-(.*)\\}")[2]) + 1
191 return(
192 paste0("\\cmidrule[0.5pt](l{2pt}r{2pt}){", start_pos, "-", stop_pos, "}")
193 )
Hao Zhu96a50b52017-06-14 18:09:35 -0400194}