使用DT导出表时保持格式化(DataTables按钮扩展名)

时间:2018-03-02 12:50:16

标签: javascript css r datatables shiny

我制作了一个闪亮的应用程序,其中有人上传文件,计算了一些比率,并且可以使用滑块来设置阈值的格式。我为此使用DT::formatStyle,它工作得非常好。据我了解这个函数,它创建一个回调来处理条件格式。

然后,我想使用DT中的按钮扩展名导出数据。我想在导出为pdf或打印时保留格式。事实证明这不起作用:数据导出时没有任何格式。我试图设置exportOptions(list(stripHtml = FALSE)),但它仍然不起作用。

令我感到惊讶的是,即使我直接从Firefox打印(如文件/打印...;我只尝试使用Firefox,而应用程序只能在Firefox中运行),颜色会被删除,但字体重量保持不变。我怀疑我可能不得不调整CSS,但我不知道该怎么做。

我想有办法按原样制作pdf和/或印刷品,这与我在浏览器中看到的最接近。 以下是一个例子:

library(shiny)
library(DT)
library(dplyr)
data("starwars")

ui <- fluidPage(title = "Ratios",
  sidebarLayout(
    sidebarPanel(width = 2,
                 actionButton("button", "Go"), # Emulates data loading
                 sliderInput("seuil_j", "Threshold J",
                             min = 0,  max = 80, value = 35, step = 0.5)),
    mainPanel( 
      fluidRow(column(width = 12,
                      DT::dataTableOutput("ratios"))))
  )
)

server <- function(input, output, session) {
  donnees_ratios <- reactive({
    req(input$button)
    set.seed(14)
    starwars %>% 
      select(1:10) %>% # DataTables is not happy with list columns
      mutate(signe = sample(c(1, -1), replace = TRUE, size = nrow(.)),
             ratio_j = signe * mass / height) %>% 
      select(name, mass, height, signe, ratio_j, everything())
  })

  output$ratios <- DT::renderDataTable({
    donnees_ratios() %>% 
      creer_DT() %>% 
      formatter_DT(input)
  })
}

creer_DT <- function(donnees) {
  datatable(donnees, 
            rownames = FALSE, 
            class = 'cell-border stripe compact hover',
            extensions = c("Buttons"),
            options = list(
              dom = 'Blfrtip',
              buttons = list(
                list(extend = "pdf", 
                     exportOptions = list(stripHtml = FALSE,
                                                     columns = ':visible'),
                     orientation = 'landscape'),
                list(extend = "print", 
                     exportOptions = list(stripHtml = FALSE,
                                          columns = ':visible')),
               "excel", "csv", "colvis"),
              language = list(
                decimal = ",",
                thousands = "&#8239;"  # small unbreakable space
              )
            )
  )
}

formatter_DT <- function(table, input) {
  table %>% 
    formatPercentage(columns = c("ratio_j"),
                     digits = 1L, dec.mark = ",", mark = "&#8239;") %>%
    formatRound(columns = c("height", "mass"),
                digits = 1L, dec.mark = ",", mark = "&#8239;") %>%
    format_seuil("ratio_j", input$seuil_j)
}

format_seuil <- function(table, column, seuil) {
  # Threshold for the aboslute value, and different coloring if higher or lower
  formatStyle(table, column, 
              fontWeight = styleInterval(
                c(-seuil / 100, seuil / 100), c("bold", "normal", "bold")),
              color = styleInterval(
                c(-seuil / 100, seuil / 100), c("red", "black", "orange")
              ))
}

shinyApp(ui, server)

我可以导出为pdf或打印,但显示被修改。我也可以使用rmarkdownknitr生成一个pdf,但这将是工作的两倍,感觉我错过了使用按钮扩展的东西。

我希望这很清楚,谢谢你的帮助!

弗洛里安

1 个答案:

答案 0 :(得分:1)

tl;dr You cannot keep formatting; you have to write a custom JavaScript function.

PDF and print buttons have very different behaviors.

The print button behavior

When you click the print button, you use the user agent (in this use case, the browser) to render the HTML document as a paged document (PDF). There's a W3C standard named CSS Paged Media that defines how CSS rules are applied for paged media.
Theses CSS rules are enclosed in CSS @media print at-rule.
There's a comprehensive guide about CSS Paged Media here: print-css.rocks.

Dealing with CSS Paged Media is not straightforward:

  • browsers badly implement CSS Paged Media standards; headless user agents (wkhtmltopdf, weasyprint, XML Prince...) are used to generate PDF with CSS Paged Media. Using one of these user agents is quite easy since pandoc 2.0: they can replace a LaTeX engine.
  • when you open a HTML file, browsers do not apply @media print by default (they apply @media screen at-rule). So, it can be hard to figure out @media print rules. The only mean I know to track theses rules is to use the Chrome Developer Tools (open the menu, select More tools and Rendering. In the Rendering panel, you can emulate a paged media selecting print).

Since you want to use a browser to generate a styled PDF, I think CSS paged media rules is an impracticable way. Moreover, using a headless user agent with a dynamic HTML document as a Shiny App is extremely complex. So, my advise is to forget the print button.

The PDF button behavior

DataTables library relies on pdfmake JavaScript library to generate a PDF file. You can apply custom styles passing a JavaScript function to the customize option of the pdfHtml5 button. This function customizes the document object sent to the pdfmake API.

In order to understand the structure of the JSON document object passed by DataTables to pdfmake, you can output it to the browser console:

library(shiny)
library(DT)
library(dplyr)
data("starwars")

ui <- fluidPage(title = "Ratios",
                sidebarLayout(
                  sidebarPanel(width = 2,
                               actionButton("button", "Go"), # Emulates data loading
                               sliderInput("seuil_j", "Threshold J",
                                           min = 0,  max = 80, value = 35, step = 0.5)),
                  mainPanel( 
                    fluidRow(column(width = 12,
                                    DT::dataTableOutput("ratios"))))
                )
)

server <- function(input, output, session) {
  donnees_ratios <- reactive({
    req(input$button)
    set.seed(14)
    starwars %>% 
      select(1:10) %>% # DataTables is not happy with list columns
      mutate(signe = sample(c(1, -1), replace = TRUE, size = nrow(.)),
             ratio_j = signe * mass / height) %>% 
      select(name, mass, height, signe, ratio_j, everything())
  })

  output$ratios <- DT::renderDataTable({
    donnees_ratios() %>% 
      creer_DT() %>% 
      formatter_DT(input)
  })
}

creer_DT <- function(donnees) {
  datatable(donnees, 
            rownames = FALSE, 
            class = 'cell-border stripe compact hover',
            extensions = c("Buttons"),
            options = list(
              dom = 'Blfrtip',
              buttons = list(
                list(extend = "pdf", 
                     exportOptions = list(stripHtml = FALSE,
                                          columns = ':visible'),
                     orientation = 'landscape',
                     customize = JS("function(doc){console.dir(doc);}")),
                list(extend = "print", 
                     exportOptions = list(stripHtml = FALSE,
                                          columns = ':visible')),
                "excel", "csv", "colvis"),
              language = list(
                decimal = ",",
                thousands = "&#8239;"  # small unbreakable space
              )
            )
  )
}

formatter_DT <- function(table, input) {
  table %>% 
    formatPercentage(columns = c("ratio_j"),
                     digits = 1L, dec.mark = ",", mark = "&#8239;") %>%
    formatRound(columns = c("height", "mass"),
                digits = 1L, dec.mark = ",", mark = "&#8239;") %>%
    format_seuil("ratio_j", input$seuil_j)
}

format_seuil <- function(table, column, seuil) {
  # Threshold for the aboslute value, and different coloring if higher or lower
  formatStyle(table, column, 
              fontWeight = styleInterval(
                c(-seuil / 100, seuil / 100), c("bold", "normal", "bold")),
              color = styleInterval(
                c(-seuil / 100, seuil / 100), c("red", "black", "orange")
              ))
}

shinyApp(ui, server)

You can modify a default style. Here's one example changing the font color of the tableHeader style:

customize = JS("function(doc){doc.styles.tableHeader.color='yellow';}"))

For further customization, you have to write your own JavaScript function. Here's an example to format the fifth column with percent:

customize = JS("function(doc){doc.content[1].table.body.forEach(function(el,idx){if(idx>0){el[4].text=String((parseFloat(el[4].text)*100).toFixed(1))+'%'}})}"))