Keep formatting when exporting table with DT (DataTables buttons extension)

题意:在使用 DT(DataTables 按钮扩展)导出表格时保持格式。

问题背景:

I made a shiny app where someone uploads a file, some ratios are computed, and those ratios can be formatted using sliders for thresholds. I use DT::formatStyle for this and it is working really fine. As far as I understand this function, it creates a callback to handle the conditional formatting.

我制作了一个 Shiny 应用,用户可以上传文件,计算一些比率,并可以通过滑块为阈值格式化这些比率。我使用 `DT::formatStyle` 来实现这一点,效果很好。据我了解,这个函数创建了一个回调来处理条件格式化。

Then, I want to export the data, using the buttons extension in DT. I want to keep the formatting when exporting to pdf or printing. It turns out that this doesn't work: the data is exported without any formatting. I tried to set exportOptions(list(stripHtml = FALSE)), but it still doesn't work.

然后,我想使用 DT 的按钮扩展导出数据。我希望在导出到 PDF 或打印时保留格式。结果发现,这并不起作用:导出的数据没有任何格式。我尝试设置 `exportOptions(list(stripHtml = FALSE))`,但仍然不行。

What surprises me as well, is that even when I print directly from Firefox (as File/Print... ; I have tried with Firefox only, and the app will only be run in Firefox), the color is dropped, but font weight is kept. I suspect that I may have to tweak the CSS but I do not know how to do that.

让我感到惊讶的是,即使我直接从 Firefox 打印(如文件/打印...;我只在 Firefox 上尝试过,并且应用只会在 Firefox 中运行),颜色也会丢失,但字体粗细会保留。我怀疑我可能需要调整 CSS,但我不知道该怎么做。

I would like to have a way to make the pdf and/or the print "as is", the closest to what I see in the browser. Below is an example:

我希望有一种方法使 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)

I can export to pdf or print, but the display is modified. I could also generate a pdf with rmarkdown and knitr, but this would be twice the work, and it feels like I miss something using the buttons extension.

我可以导出为 PDF 或打印,但显示效果被修改了。我也可以使用 `rmarkdown` 和 `knitr` 生成 PDF,但这将是两倍的工作,而且我觉得在使用按钮扩展时错过了一些东西。

问题解决:

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

总结:你无法保留格式;必须编写自定义 JavaScript 函数。

PDF and print buttons have very different behaviors.

PDF 和打印按钮的行为非常不同。

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.

当你点击打印按钮时,使用用户代理(在此用例中是浏览器)将 HTML 文档呈现为分页文档(PDF)。有一个 W3C 标准叫做 CSS Paged Media,定义了如何将 CSS 规则应用于分页媒体。


Theses CSS rules are enclosed in CSS @media print at-rule.

这些 CSS 规则被包含在 CSS 的 `@media print` 规则中。


There's a comprehensive guide about CSS Paged Media here: print-css.rocks.

关于 CSS Paged Media 的详细指南可以在这里找到:print-css.rocks。

Dealing with CSS Paged Media is not straightforward:

处理 CSS 分页媒体并不简单。

  • browsers badly implement CSS Paged Media standards; headless user agents (wkhtmltopdfweasyprintXML 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.   浏览器对 CSS 分页媒体标准的实现不佳;无头用户代理(如 wkhtmltopdf、weasyprint、XML Prince 等)用于生成带有 CSS 分页媒体的 PDF。从 pandoc 2.0 开始,使用这些用户代理变得相当简单,它们可以替代 LaTeX 引擎。
  • 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).   当你打开一个 HTML 文件时,浏览器默认不会应用 @media print(它们应用的是 @media screen 规则)。因此,确定 @media print 规则可能会很困难。我知道的唯一跟踪这些规则的方法是使用 Chrome 开发者工具(打开菜单,选择更多工具和渲染。在渲染面板中,你可以通过选择打印来模拟分页媒体)。

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.

由于你想使用浏览器生成样式化的 PDF,我认为 CSS 分页媒体规则并不是一种可行的方法。此外,使用无头用户代理处理动态 HTML 文档作为 Shiny 应用非常复杂。因此,我的建议是放弃打印按钮。

The PDF button behavior   PDF 按钮的行为

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.

DataTables 库依赖于 pdfmake JavaScript 库来生成 PDF 文件。你可以通过将 JavaScript 函数传递给 pdfHtml5 按钮的 customize 选项来应用自定义样式。该函数用于自定义发送到 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:

为了了解 DataTables 传递给 pdfmake 的 JSON 文档对象的结构,你可以将其输出到浏览器控制台:

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:

你可以修改默认样式。以下是一个更改 tableHeader 样式字体颜色的示例:

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:

要进行进一步的自定义,你需要编写自己的 JavaScript 函数。以下是一个格式化第五列为百分比的示例:

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))+'%'}})}"))

猜你喜欢

转载自blog.csdn.net/suiusoar/article/details/143423215