Shiny: Merge Cells in Dt::Datatable

Shiny: Merge cells in DT::datatable

It is possible with the help of the datatables-rowsgroup library. Here is an example:

library(shiny)
library(DT)

dat <- iris[c(1,2,3,51,52,53,101,102,103), c(5,1,2,3,4)]

ui <- fluidPage(
DTOutput("table")
)

server <- function(input, output){
output[["table"]] <- renderDT({
dtable <- datatable(dat, rownames = FALSE,
options = list(
rowsGroup = list(0) # merge cells of column 1
))
path <- "U:/Data/shiny/DT/www" # folder containing dataTables.rowsGroup.js
dep <- htmltools::htmlDependency(
"RowsGroup", "2.0.0",
path, script = "dataTables.rowsGroup.js")
dtable$dependencies <- c(dtable$dependencies, list(dep))
dtable
})
}

shinyApp(ui, server)

Sample Image

Shiny merge cells in DT::table on two or more columns

All you need to do is use the answer you linked (with the datatables-rowsgroup plugin) and specify extra columns to merge on duplicate results:

dtable <- datatable(
dat,
rownames = FALSE,
options = list(
rowsGroup = list(0,4))) # merge cells of column 1 and 5

Sample Image

How to merge 2 row cells in data table?

The first cell text should be in the upper header, not in the second.

library(DT)
library(shiny)
library(dplyr)
library(htmltools)
library(data.table)

data <-
data.frame(
ID = c(1,1,1,2,2,2,3,3,3),
Period = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
Values = c(5, 10, 15, 0, 2, 4, 3, 6, 9),
State = c("X0","X1","X2","X0","X2","X0", "X2","X1","X0")
)

numTransit <- function(x, from=1, to=3){
setDT(x)
unique_state <- unique(x$State)
all_states <- setDT(expand.grid(list(from_state = unique_state, to_state = unique_state)))
dcast(x[, .(from_state = State[from],
to_state = State[to]),
by = ID]
[,.N, c("from_state", "to_state")]
[all_states,on = c("from_state", "to_state")],
to_state ~ from_state, value.var = "N"
)
}

ui <- fluidPage(
tags$head(tags$style(".datatables .display {margin-left: 0;}")), # < left-align the table
h4(strong("Base data frame:")),
tableOutput("data"),
h4(strong("Transition table inputs:")),
numericInput("transFrom", "From period:", 1, min = 1, max = 3),
numericInput("transTo", "To period:", 2, min = 1, max = 3),
h4(strong("Output transition table:")),
DTOutput("resultsDT"),
)

server <- function(input, output, session) {
results <-
reactive({
results <- numTransit(data, input$transFrom, input$transTo) %>%
replace(is.na(.), 0) %>%
bind_rows(summarise_all(., ~(if(is.numeric(.)) sum(.) else "Sum")))
results <- cbind(results, Sum = rowSums(results[,-1]))
})

output$data <- renderTable(data)

output$resultsDT <- renderDT(server=FALSE, {
req(results())
datatable(
data = results(),
rownames = FALSE,
filter = 'none',
container = tags$table(
class = 'display',
tags$thead(
tags$tr(
tags$th(rowspan = 2, colnames(results())[1], style = "border-right: solid 1px;"),
tags$th(colspan = 10, sprintf('From state where initial period = %s', input$transFrom))
),
tags$tr(
mapply(tags$th, colnames(results())[-1], style = sprintf("border-right: solid %spx;", rep(0, ncol(results()) - 1L)), SIMPLIFY = FALSE)
)
)
),
options = list(scrollX = F
, dom = 'ft'
, lengthChange = T
, pagingType = "numbers" # hides Next and Previous buttons
, autoWidth = T
, info = FALSE # hide the "Showing 1 of 2..." at bottom of table
, searching = FALSE # removes search box
),
class = "display"
) %>%
formatStyle(c(1), `border-right` = "solid 1px")
})

}

shinyApp(ui, server)

Sample Image

Merge columns in DT::datatable

This may work for you, using htmltools

library(shiny)
library(DT)
library(htmltools)

tbl <- data.frame("A" = c( 1L, "question"),
"B" = c( 2L, "answer"))

container_dt= withTags(table(
class = 'display',
thead(
tr(
th(class = 'dt-center',colspan = 2, 'AB')),
tr(
lapply((c('foo', 'bar')), th)))))


ui <- fluidPage(
dataTableOutput("table")
)

server <- function(input, output) {

output$table <- renderDT({
datatable(tbl, container = container_dt, rownames = F, class = "",
options = list(autoWidth = T,
columnDefs = list(list(className = "dt-center", targets = "_all"),
list(width = "40px", targets = "_all"))))
})
}

shinyApp(ui = ui, server = server)

Sample Image

Editing multiple cells in a datatable in shiny

See below, this will allow multiple changes and each one is tracked.

library(shiny)
library(shinydashboard)
library(tidyverse)
library(DT)

header <- dashboardHeader(title = "demo")
sidebar <- dashboardSidebar(
sidebarMenu(id = 'sidebarmenu',
menuItem("admin", tabName = "admin", icon = icon("adjust")),
downloadButton("downloadResults","Download Results")
)
)

body <- dashboardBody(
tabItems(
tabItem(
tabName = 'admin', class = 'active',
fluidRow(
box(
dataTableOutput('userTable'), width = 6
)
)
)
)
)

ui <- dashboardPage(title = 'admin function test', header, sidebar, body, skin='blue')

server <- function(input, output, session){

dat <- data.frame(userName = c("John","Mary","Mike"), start = c("06/08/2019","01/01/2019","23/10/2019"), stringsAsFactors = FALSE)

output$userTable <- renderDataTable({
DT::datatable(isolate(dat),
editable = TRUE,
rownames = FALSE)
})

###Tracking Changes###
rvs <- reactiveValues(
data = NA #dynamic data object
)

observe({
rvs$data <- dat
})

proxy = dataTableProxy('userTable')
observe({
DT::replaceData(proxy, rvs$data, rownames = FALSE, resetPaging = FALSE)
})

observeEvent(input$userTable_cell_edit, {
rvs$data <<- editData(rvs$data, input$userTable_cell_edit, rownames = FALSE)
})

# observeEvent(
# input$do,{
# write.csv(rvs$data,'userTest.csv', row.names = FALSE)
# })

output$downloadResults <- downloadHandler(
filename = function(){paste("userTest.csv.csv", sep = "")},
content = function(file){write.csv(rvs$data, file, row.names = FALSE)}
)

}

shinyApp(ui = ui, server = server)

If you want multiple cells to be editable at the same time, add this line to your datatable:

editable = list(target = "all") #can be all, row, or column

Few tips when posting in the future:

  1. Create a reproducible example, having it pull data/save data from a file on your computer can't be reproduced
  2. Keep the code simple as possible to focus on your issue. Throwing renderUI's for the body and sidebar just clutters your code.

How to extend a table cell across multiple columns when rendered in R Shiny?

We could use a custom container to add the column group names as real column headers:

# ui.R

ui <- fluidPage(
DT::dataTableOutput("table")
)

# server.R

library(DT)
df <- data.frame(A = c(1, 1, 1), B = c(1, 1, 1), C = c(1, 1, 1),
D = c(2, 2, 2), E = c(2, 2, 2), F = c(2, 2, 2),
G = c(3, 3, 3), H = c(3, 3, 3), I = c(3, 3, 3))

myContainer <- htmltools::withTags(table(
class = 'display',
thead(
tr(
th(),
th(colspan = 3, 'Trial 1', class = "dt-center"),
th(colspan = 3, 'Trial 2', class = "dt-center"),
th(colspan = 3, 'Trial 3', class = "dt-center")
),
tr(
th(),
lapply(names(df), th)
)
)
))

server <- function(input, output) {

output$table <- DT::renderDataTable({
DT::datatable(df, container = myContainer,
options = list(dom = "t", ordering = FALSE,
columnDefs = list(list(className = "dt-center", targets = "_all"))
))
})

}

runApp(list(ui = ui, server = server))

Output:

Sample Image

Alternativly, if you really want that first row to have colspans we can use the initComplete option to call a JavaScript function when the table is rendered. Here is the server part only:

jsc <- '
function(settings, json) {
$("td:contains(\'Trial\')").attr("colspan", "3").css("text-align", "center");
$("tbody > tr:first-child > td:empty").remove();
}'

server <- function(input, output) {
output$table <- DT::renderDataTable({
df <- data.frame(A = c("Trial 1", 1, 1, 1), B = c("", 1, 1, 1), C = c("", 1, 1, 1),
D = c("Trial 2", 2, 2, 2), E = c("", 2, 2, 2), F = c("", 2, 2, 2),
G = c("Trial 3", 3, 3, 3), H = c("", 3, 3, 3), I = c("", 3, 3, 3))
DT::datatable(df, options = list(dom = "t", ordering = FALSE, initComplete = JS(jsc)))
})

}

In the first row of the JS function we select all cells that contain the word Trial and add the corresponding attributes and styles. Afterwards we select all empty cells, that are a direct descendant of a row element which in turn is the first child of the body of the table and remove them from the DOM. Here you find a general reference on CSS selectors like >.

Output:

Sample Image

How to merge 2 row headers in a single column in a data table and insert a reactive object?

Please reference these related posts that lead to the solution shown at the bottom. The posts that built up to this solution are How to merge to row cells in data table?, How to add a vertical line to the first column header in a data table?, and How to add reactive object to secondary column header in output table?

Solution:

library(DT)
library(shiny)
library(dplyr)
library(htmltools)
library(data.table)

data <-
data.frame(
ID = c(1,1,1,2,2,2,3,3,3),
Period = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
Values = c(5, 10, 15, 0, 2, 4, 3, 6, 9),
State = c("X0","X1","X2","X0","X2","X0", "X2","X1","X0")
)

numTransit <- function(x, from=1, to=3){
setDT(x)
unique_state <- unique(x$State)
all_states <- setDT(expand.grid(list(from_state = unique_state, to_state = unique_state)))
dcast(x[, .(from_state = State[from],
to_state = State[to]),
by = ID]
[,.N, c("from_state", "to_state")]
[all_states,on = c("from_state", "to_state")],
to_state ~ from_state, value.var = "N"
)
}

ui <- fluidPage(
tags$head(tags$style(".datatables .display {margin-left: 0;}")), # < left-align the table
h4(strong("Base data frame:")),
tableOutput("data"),
h4(strong("Transition table inputs:")),
numericInput("transFrom", "From period:", 1, min = 1, max = 3),
numericInput("transTo", "To period:", 2, min = 1, max = 3),
h4(strong("Output transition table:")),
DTOutput("resultsDT"),
)

server <- function(input, output, session) {
results <-
reactive({
results <- numTransit(data, input$transFrom, input$transTo) %>%
replace(is.na(.), 0) %>%
bind_rows(summarise_all(., ~(if(is.numeric(.)) sum(.) else "Sum")))
results <- cbind(results, Sum = rowSums(results[,-1]))
})

output$data <- renderTable(data)

output$resultsDT <- renderDT(server=FALSE, {
req(results())
datatable(
data = results(),
rownames = FALSE,
filter = 'none',
container = tags$table(
class = 'display',
tags$thead(
tags$tr(
tags$th(rowspan = 2, sprintf('To state where end period = %s', input$transTo), style = "border-right: solid 1px;"),
tags$th(colspan = 10, sprintf('From state where initial period = %s', input$transFrom))
),
tags$tr(
mapply(tags$th, colnames(results())[-1], style = sprintf("border-right: solid %spx;", rep(0, ncol(results()) - 1L)), SIMPLIFY = FALSE)
)
)
),
options = list(scrollX = F
, dom = 'ft'
, lengthChange = T
, pagingType = "numbers" # hides Next and Previous buttons
, autoWidth = T
, info = FALSE # hide the "Showing 1 of 2..." at bottom of table
, searching = FALSE # removes search box
),
class = "display"
) %>%
formatStyle(c(1), `border-right` = "solid 1px")
})

}

shinyApp(ui, server)

Combining selectInput and DT::datatable editing in Shiny

A few modifications to achieve expected behavior :

  • dtProxy should be created only once at server launch
  • observeEvent(input$dt_cell_edit,...) should be independent of observeEvent(input$s_internal_idNew,...)
  • df_showed() should also be updated, as df()
library(tidyverse); library(DT); library(shiny)

df <- data.frame(internal_idNew=c(1, 2, 3, 4), col_1=c("this", "is", "a", "column"))

ui <- fluidPage(
#filter df
selectInput("s_internal_idNew", "Record id (new)", choices=c(1:nrow(df))),
#dt output
dataTableOutput("dt")
)

server <- function(input, output) {
#reactive df
df <- reactiveVal({df})
#reactive df filtered
df_showed <- reactiveVal({})

#create proxy dt once
dt_proxy <- dataTableProxy("dt")


observeEvent(input$s_internal_idNew, {
#filter a row matching the internal id
df_showed(df() %>% filter(internal_idNew==input$s_internal_idNew))
#render dt
output$dt <- DT::renderDataTable(df_showed(), editable=list(target = "cell", disable = list(columns =c(0))), options=list(dom = 't', bSort=FALSE, pageLength=1), rownames = FALSE, selection = "none")

})

#edit dt - separate from previous reactive
observeEvent(input$dt_cell_edit, {
this <- df()
showed <- df_showed()

#extract edited value to edit df
col_name <- showed %>% names() %>% .[input$dt_cell_edit$col+1]
row_name <- input$s_internal_idNew %>% as.numeric()
value_name <- coerceValue(input$dt_cell_edit$value, showed[row_name, col_name])

#store edited values in reactive df
this[row_name, col_name] <- value_name
df(this)
df_showed(this[row_name, ]) # Also updated
#replace data in datatable
replaceData(dt_proxy, df_showed(), resetPaging = TRUE, rownames = FALSE)
})

}

shinyApp(ui = ui, server = server)

Sample Image

How to change color of cells in a DT::dataTable in shiny, based on cells in another column?

Your code gives the following error:

renderDataTable ignores ... arguments when expr yields a datatable object;

Since you now return a DT::datatable object to renderDT and not a dataframe. This is a bit similar to this Q/A. So now you have to move all arguments to the DT::datatable constructor:

render_dt = function(data) {
renderDT(data)
}

and

  dt_d9 <- datatable(d9, editable = 'cell', rownames = FALSE, extensions = 'Buttons', options = list(dom = 'Bfrtip', buttons = I('colvis'))) %>% formatStyle(
'R/Y/G', 'Y', #'R/Y/G' is the column cell I'm trying to change values for based on column 'Y'
backgroundColor = styleEqual(c(0, 1), c('red', 'yellow'))
)

output$x9 = render_dt(dt_d9)


Related Topics



Leave a reply



Submit