You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R Shiny渲染表格时实现单元格跨多列显示?

How to Add Rowspan Merging to DT Datatables in R Shiny

Got it, so you want to implement cell merging (like HTML's rowspan attribute) in a DT datatable for your numeric DataFrame in Shiny, without the hassle of building the table manually? Perfect—DT has some clever ways to handle this, no raw HTML required (well, minimal HTML if you prefer one approach over the other). Let’s break down two reliable solutions:

Solution 1: Use JavaScript Row Callback (No Data Modification)

This approach uses DT's rowCallback option to run JavaScript that dynamically merges cells after the table renders. Great if you don’t want to alter your original DataFrame.

Full Shiny Code

ui.R

fluidPage(
  # Optional: Add CSS to style merged cells nicely
  tags$style(
    HTML("
      #table td[rowspan] {
        vertical-align: middle; /* Center text in merged cells */
      }
      #table td.hidden {
        border: none; /* Remove border from hidden cells */
      }
    ")
  ),
  DT::dataTableOutput("table")
)

server.R

library(shiny)
library(DT)

function(input, output) {
  # Example numeric DataFrame (replace with your data)
  df <- data.frame(
    Group = c("A", "A", "B", "B", "B"),
    Metric1 = c(12.5, 18.3, 9.7, 14.2, 21.1),
    Metric2 = c(45, 52, 38, 49, 63),
    stringsAsFactors = FALSE
  )
  
  output$table <- DT::renderDataTable({
    datatable(df,
              options = list(
                # Disable ordering to keep merged cells intact (see note below for sorting support)
                ordering = FALSE,
                rowCallback = JS(
                  "function(row, data, index) {",
                  "  var table = $('#table').DataTable();",
                  "  var currentGroup = data[0];",
                  "  var prevGroup = index > 0 ? table.cell(index-1, 0).data() : null;",
                  "  ",
                  "  if (currentGroup === prevGroup) {",
                  "    // Hide the current cell and update the previous cell's rowspan",
                  "    $(row).find('td:eq(0)').addClass('hidden').hide();",
                  "    var prevRow = table.row(index-1).node();",
                  "    var rowspan = $(prevRow).find('td:eq(0)').attr('rowspan') || 1;",
                  "    $(prevRow).find('td:eq(0)').attr('rowspan', parseInt(rowspan) + 1);",
                  "  }",
                  "}"
                )
              ),
              selection = "none" # Disable row selection to avoid confusion with merged cells
    )
  })
}

How It Works

  • The JavaScript callback checks each row's group value against the previous row. If they match, it hides the current group cell and increments the rowspan attribute of the first cell in the group.
  • The optional CSS ensures merged cells look clean (vertical centering, no borders on hidden cells).
  • Note: If you need sorting, replace rowCallback with drawCallback so the merging runs every time the table redraws (after sorting, filtering, etc.).

Solution 2: Preprocess Data with HTML (Simpler for Static Groups)

If your grouping is static (you know the row counts per group upfront), you can preprocess your DataFrame to include HTML rowspan tags directly, then tell DT to render the HTML instead of escaping it.

Modified Server Code

library(shiny)
library(DT)
library(dplyr)

function(input, output) {
  df <- data.frame(
    Group = c("A", "A", "B", "B", "B"),
    Metric1 = c(12.5, 18.3, 9.7, 14.2, 21.1),
    Metric2 = c(45, 52, 38, 49, 63),
    stringsAsFactors = FALSE
  )
  
  # Preprocess the Group column to add rowspan HTML
  df_processed <- df %>%
    group_by(Group) %>%
    mutate(
      Group = ifelse(row_number() == 1,
                    sprintf('<td rowspan="%d">%s</td>', n(), Group),
                    '<td class="hidden"></td>')
    ) %>%
    ungroup()
  
  output$table <- DT::renderDataTable({
    datatable(df_processed,
              escape = FALSE, # Critical: Tell DT not to escape HTML tags
              options = list(
                ordering = FALSE,
                columnDefs = list(list(width = "100px", targets = 0)) # Adjust group column width
              ),
              selection = "none"
    )
  })
}

How It Works

  • We use dplyr to group the data, then for each group, we add an HTML <td> tag with rowspan set to the group size for the first row. Subsequent rows get empty hidden <td> tags.
  • Setting escape = FALSE tells DT to render the HTML instead of displaying it as text.
  • This is more straightforward if your data doesn’t change dynamically, as you don’t have to mess with JavaScript.

Key Notes

  • Both approaches work with numeric columns—only the grouping column gets special treatment.
  • If you need to support filtering/sorting, go with Solution 1 using drawCallback instead of rowCallback to re-apply merging after every table update.
  • Adjust the CSS to match your app’s styling—you can tweak borders, padding, and alignment as needed.

内容的提问来源于stack exchange,提问作者michaelmccarthy404

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:09:38