blob: 5439d754e6e207d3f9bdd04083f55d020fcd70ef [file] [log] [blame]
Hao Zhuf7994dd2017-02-27 16:58:42 -05001#' Add a header row on top of current header
2#'
3#' @description Tables with multiple rows of header rows are extremely useful
4#' to demonstrate grouped data. This function takes the output of a `kable()`
Hao Zhue7c8f702017-10-10 13:22:59 -04005#' function and adds an header row on top of it.
Hao Zhuf7994dd2017-02-27 16:58:42 -05006#'
7#' @param kable_input Output of `knitr::kable()` with `format` specified
8#' @param header A (named) character vector with `colspan` as values. For
9#' example, `c(" " = 1, "title" = 2)` can be used to create a new header row
10#' for a 3-column table with "title" spanning across column 2 and 3. For
11#' convenience, when `colspan` equals to 1, users can drop the ` = 1` part.
12#' As a result, `c(" ", "title" = 2)` is the same as `c(" " = 1, "title" = 2)`.
Hao Zhu32f43f72017-06-20 18:24:54 -040013#' @param bold A T/F value to control whether the text should be bolded.
14#' @param italic A T/F value to control whether the text should to be emphasized.
Hao Zhuac7e70f2017-08-02 00:18:36 -040015#' @param monospace A T/F value to control whether the text of the selected column
16#' need to be monospaced (verbatim)
17#' @param escape A T/F value showing whether special characters should be
18#' escaped.
Hao Zhuf7994dd2017-02-27 16:58:42 -050019#'
Hao Zhu78e61222017-05-24 20:53:35 -040020#' @examples x <- knitr::kable(head(mtcars), "html")
21#' # Add a row of header with 3 columns on the top of the table. The column
22#' # span for the 2nd and 3rd one are 5 & 6.
23#' add_header_above(x, c(" ", "Group 1" = 5, "Group 2" = 6))
24#'
Hao Zhuc1f38412017-02-23 12:13:48 -050025#' @export
Hao Zhuac7e70f2017-08-02 00:18:36 -040026add_header_above <- function(kable_input, header = NULL,
27 bold = FALSE, italic = FALSE,
28 monospace = FALSE, escape = TRUE) {
Hao Zhuc1f38412017-02-23 12:13:48 -050029 kable_format <- attr(kable_input, "format")
30 if (!kable_format %in% c("html", "latex")) {
31 stop("Please specify output format in your kable function. Currently ",
32 "generic markdown table using pandoc is not supported.")
33 }
34 if (kable_format == "html") {
Hao Zhuac7e70f2017-08-02 00:18:36 -040035 return(htmlTable_add_header_above(kable_input, header,
36 bold, italic, monospace, escape))
Hao Zhuc1f38412017-02-23 12:13:48 -050037 }
38 if (kable_format == "latex") {
Hao Zhuac7e70f2017-08-02 00:18:36 -040039 return(pdfTable_add_header_above(kable_input, header,
40 bold, italic, monospace, escape))
Hao Zhuc1f38412017-02-23 12:13:48 -050041 }
42}
43
44# HTML
Hao Zhuac7e70f2017-08-02 00:18:36 -040045htmlTable_add_header_above <- function(kable_input, header,
46 bold, italic, monospace, escape) {
Hao Zhuc1f38412017-02-23 12:13:48 -050047 if (is.null(header)) return(kable_input)
Hao Zhu909ea382017-06-12 15:43:47 -040048 kable_attrs <- attributes(kable_input)
Hao Zhu558c72f2017-07-24 15:12:00 -040049 kable_xml <- read_kable_as_xml(kable_input)
Hao Zhu62cdde52017-05-20 22:16:03 -040050 kable_xml_thead <- xml_tpart(kable_xml, "thead")
Hao Zhuc1f38412017-02-23 12:13:48 -050051
52 header <- standardize_header_input(header)
53
Hao Zhuac7e70f2017-08-02 00:18:36 -040054 if (escape) {
55 header$header <- escape_html(header$header)
56 }
57
Hao Zhuc1f38412017-02-23 12:13:48 -050058 header_rows <- xml_children(kable_xml_thead)
59 bottom_header_row <- header_rows[[length(header_rows)]]
60 kable_ncol <- length(xml_children(bottom_header_row))
61 if (sum(header$colspan) != kable_ncol) {
62 stop("The new header row you provided has a different total number of ",
63 "columns with the original kable output.")
64 }
65
Hao Zhuac7e70f2017-08-02 00:18:36 -040066 new_header_row <- htmlTable_new_header_generator(header,
67 bold, italic, monospace)
Hao Zhuc1f38412017-02-23 12:13:48 -050068 xml_add_child(kable_xml_thead, new_header_row, .where = 0)
Hao Zhuf2dfd142017-07-24 14:43:28 -040069 out <- as_kable_xml(kable_xml)
Hao Zhu909ea382017-06-12 15:43:47 -040070 attributes(out) <- kable_attrs
Hao Zhu6a076462017-03-01 12:59:01 -050071 return(out)
Hao Zhuc1f38412017-02-23 12:13:48 -050072}
73
74standardize_header_input <- function(header) {
75 header_names <- names(header)
76
77 if (is.null(header_names)) {
78 return(data.frame(header = header, colspan = 1, row.names = NULL))
79 }
80
81 names(header)[header_names == ""] <- header[header_names == ""]
82 header[header_names == ""] <- 1
83 header_names <- names(header)
84 header <- as.numeric(header)
85 names(header) <- header_names
86 return(data.frame(header = names(header), colspan = header, row.names = NULL))
87}
88
Hao Zhuac7e70f2017-08-02 00:18:36 -040089htmlTable_new_header_generator <- function(header_df, bold, italic, monospace) {
Hao Zhu32f43f72017-06-20 18:24:54 -040090 row_style <- paste0(
91 ifelse(bold, "font-weight: bold; ", ""),
Hao Zhuac7e70f2017-08-02 00:18:36 -040092 ifelse(italic, "font-style: italic; ", ""),
93 ifelse(monospace, "font-family: monospace; ", "")
Hao Zhu32f43f72017-06-20 18:24:54 -040094 )
Hao Zhuc1f38412017-02-23 12:13:48 -050095 header_items <- apply(header_df, 1, function(x) {
Hao Zhuacc7ceb2017-05-26 10:50:25 -070096 if (trimws(x[1]) == "") {
Hao Zhu6d2faa12017-05-24 02:16:45 -040097 paste0('<th style="border-bottom:hidden"></th>')
98 } else {
Hao Zhuacc7ceb2017-05-26 10:50:25 -070099 paste0('<th style="text-align:center; border-bottom:hidden; ',
Hao Zhu32f43f72017-06-20 18:24:54 -0400100 'padding-bottom:0; padding-left:3px;padding-right:3px;',
101 row_style,
102 '" colspan="',
Hao Zhu4b0c51e2017-08-01 15:21:07 -0400103 x[2], '"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px;">',
Hao Zhuacc7ceb2017-05-26 10:50:25 -0700104 x[1], '</div></th>')
Hao Zhu6d2faa12017-05-24 02:16:45 -0400105 }
Hao Zhuc1f38412017-02-23 12:13:48 -0500106 })
107 header_text <- paste(c("<tr>", header_items, "</tr>"), collapse = "")
108 header_xml <- read_xml(header_text, options = c("COMPACT"))
109 return(header_xml)
110}
111
112# Add an extra header row above the current header in a LaTeX table ------
Hao Zhuac7e70f2017-08-02 00:18:36 -0400113pdfTable_add_header_above <- function(kable_input, header,
114 bold, italic, monospace, escape) {
Hao Zhuc1f38412017-02-23 12:13:48 -0500115 table_info <- magic_mirror(kable_input)
116 header <- standardize_header_input(header)
Hao Zhuac7e70f2017-08-02 00:18:36 -0400117 if (escape) {
118 header$header <- escape_latex(header$header)
119 header$header <- gsub("\\\\", "\\\\\\\\", header$header)
120 }
Hao Zhuc1f38412017-02-23 12:13:48 -0500121 hline_type <- switch(table_info$booktabs + 1, "\\\\hline", "\\\\toprule")
Hao Zhu32f43f72017-06-20 18:24:54 -0400122 new_header_split <- pdfTable_new_header_generator(header, table_info$booktabs,
Hao Zhuac7e70f2017-08-02 00:18:36 -0400123 bold, italic, monospace)
Hao Zhu2ce42b92017-06-15 17:15:33 -0400124 new_header <- paste0(new_header_split[1], "\n", new_header_split[2])
Hao Zhub2b41992017-10-03 12:50:03 -0400125 out <- str_replace(enc2utf8(as.character(kable_input)),
126 hline_type,
127 paste0(hline_type, "\n", new_header))
Hao Zhuc1f38412017-02-23 12:13:48 -0500128 out <- structure(out, format = "latex", class = "knitr_kable")
Hao Zhu2ce42b92017-06-15 17:15:33 -0400129 # new_header_row <- latex_contents_escape(new_header_split[1])
130 if (is.null(table_info$new_header_row)) {
131 table_info$new_header_row <- new_header_split[1]
132 table_info$header_df <- list(header)
133 } else {
134 table_info$new_header_row <- c(table_info$new_header_row, new_header_split[1])
135 table_info$header_df[[length(table_info$header_df) + 1]] <- header
136 }
Hao Zhu32f43f72017-06-20 18:24:54 -0400137 attr(out, "kable_meta") <- table_info
Hao Zhuc1f38412017-02-23 12:13:48 -0500138 return(out)
139}
140
Hao Zhu32f43f72017-06-20 18:24:54 -0400141pdfTable_new_header_generator <- function(header_df, booktabs = FALSE,
Hao Zhuac7e70f2017-08-02 00:18:36 -0400142 bold, italic, monospace) {
Hao Zhuc1f38412017-02-23 12:13:48 -0500143 if (booktabs) {
144 header_df$align <- "c"
145 } else {
146 header_df$align <- "|c|"
147 header_df$align[1] <- "c|"
148 header_df$align[nrow(header_df)] <- "|c"
149 }
150 header_items <- apply(header_df, 1, function(x) {
151 # if(x[2] == 1) return(x[1])
Hao Zhu32f43f72017-06-20 18:24:54 -0400152 paste0('\\\\multicolumn{', x[2], '}{', x[3], '}{',
153 ifelse(bold, "\\\\bfseries ", ""),
154 ifelse(italic, "\\\\em ", ""),
Hao Zhuac7e70f2017-08-02 00:18:36 -0400155 ifelse(monospace, "\\\\ttfamily ", ""),
156 x[1],
157 "}")
Hao Zhuc1f38412017-02-23 12:13:48 -0500158 })
159 header_text <- paste(paste(header_items, collapse = " & "), "\\\\\\\\")
Hao Zhu2ce42b92017-06-15 17:15:33 -0400160 cline <- cline_gen(header_df, booktabs)
161 return(c(header_text, cline))
162}
163
164cline_gen <- function(header_df, booktabs) {
Hao Zhuc1f38412017-02-23 12:13:48 -0500165 cline_end <- cumsum(header_df$colspan)
166 cline_start <- c(0, cline_end) + 1
167 cline_start <- cline_start[-length(cline_start)]
Hao Zhuc05e1812017-02-25 01:45:35 -0500168 cline_type <- switch(booktabs + 1,
169 "\\\\cline{",
170 "\\\\cmidrule(l{2pt}r{2pt}){")
Hao Zhuc1f38412017-02-23 12:13:48 -0500171 cline <- paste0(cline_type, cline_start, "-", cline_end, "}")
172 cline <- cline[trimws(header_df$header) != ""]
173 cline <- paste(cline, collapse = " ")
Hao Zhu2ce42b92017-06-15 17:15:33 -0400174 return(cline)
Hao Zhuc1f38412017-02-23 12:13:48 -0500175}
Hao Zhu2ce42b92017-06-15 17:15:33 -0400176
177