Can I have real perfectly aligned cells in the grouping rows, instead of this misaligned workaround?
The use of extension "RowGroup" is mandatory because of the collapsing
Ideally cells should not be individually hardcoded in JS as in this WA

library(shiny)
library(DT)
ui <- fluidPage(
tags$head(
tags$style(HTML("
.dt-group-header {
display: grid;
grid-template-columns: 12fr 16fr 16fr 16fr;
font-weight: bold;
cursor: pointer;
padding: 4px 8px;
}
.dt-group-header div {
padding-right: 5px;
}
.dt-group-header div:first-child {
text-align: left;
}
.dt-group-header div:nth-child(n+2) {
text-align: right;
}
"))
),
DTOutput("tbl")
)
server <- function(input, output, session) {
dat <- data.frame(
group = c("A", "A", "B", "B", "C"),
value1 = c(10, 20, 30, 40, 50),
value2 = c(5, 15, 25, 35, 45),
value3 = c(2, 4, 6, 8, 10)
)
# list of groups, used for collapsed state
groups <- unique(as.character(dat$group))
output$tbl <- renderDT({
datatable(
dat,
rownames = FALSE,
extensions = "RowGroup",
options = list(
order = list(list(0, "asc")),
columnDefs = list(
# format numeric columns
list(
targets = 1:3,
render = JS(
"function(data,type,row,meta){",
" if(type!=='display') return data;",
" var num=parseFloat(data);",
" if(isNaN(num)) return data;",
" var f=Math.abs(num).toLocaleString(undefined,{minimumFractionDigits:0});",
" return num<0?'($'+f+')':'$'+f;",
"}"
)
)
),
rowGroup = list(
dataSrc = 0,
startRender = JS(
paste(
"function(rows, group){",
" if(!window.collapsedGroups) window.collapsedGroups={};",
" var collapsed = !!window.collapsedGroups[group];",
" var triangle = (group !== '') ? (collapsed ? '<span>▸</span> ' : '<span>▾</span> ') : '';",
" rows.nodes().each(function(r){ r.style.display = collapsed ? 'none' : ''; });",
" var t1 = rows.data().pluck(1).reduce(function(a,b){ return (+a)+(+b); }, 0);",
" var t2 = rows.data().pluck(2).reduce(function(a,b){ return (+a)+(+b); }, 0);",
" var t3 = rows.data().pluck(3).reduce(function(a,b){ return (+a)+(+b); }, 0);",
" function fmt(n){ var f=Math.abs(n).toLocaleString(undefined,{minimumFractionDigits:0}); return n<0?'($'+f+')':'$'+f; }",
" return '<div class=\"dt-group-header\" data-name=\"'+group+'\">' +",
" '<div>'+triangle+group+'</div>' +",
" '<div>'+fmt(t1)+'</div>' +",
" '<div>'+fmt(t2)+'</div>' +",
" '<div>'+fmt(t3)+'</div>' +",
" '</div>';",
"}",
collapse = "\n"
)
)
)
),
callback = JS(
sprintf("window.collapsedGroups={%s};", paste(sprintf("'%s':true", groups), collapse = ", ")),
"
table.on('click', '.dt-group-header', function(){
var name = $(this).data('name');
if(name !== undefined){
window.collapsedGroups[name] = !window.collapsedGroups[name];
table.draw(false);
}
});
"
)
)
})
}
shinyApp(ui, server)
Assuming you mean that the grouping row headers should align with the table headers, you can
display: table in the header row wrappers and display: table-cell; in the cellslibrary(shiny)
library(DT)
ui <- fluidPage(
tags$head(
tags$style(HTML("
.dt-group-header {
display: table;
width: 100%;
table-layout: fixed;
font-weight: bold;
cursor: pointer;
}
.dt-group-cell:first-child {
display: table-cell;
text-align: left;
}
.dt-group-cell:not(:first-child) {
display: table-cell;
text-align: right;
padding-right: 10px; /* adjust not-group name cell-padding here 10px is perfectly aligned - 20px for offset (better overview)*/
}
table.dataTable thead th, table.dataTable tbody tr th {
padding-left: 0px !important; /* remove left right padding because it makes things much more difficult*/
padding-right: 0px !important;
}
"))
),
DTOutput("tbl")
)
server <- function(input, output, session) {
dat <- data.frame(
group = c("A", "A", "B", "B", "C"),
value1 = c(10, 20, 30, 40, 50),
value2 = c(5, 15, 25, 35, 45),
value3 = c(2, 4, 6, 8, 10),
value4 = c(2, 4, 6, 8, 10)
)
# list of groups, used for collapsed state
groups <- unique(as.character(dat$group))
output$tbl <- renderDT({
datatable(
dat,
rownames = FALSE,
extensions = "RowGroup",
options = list(
order = list(list(0, "asc")),
columnDefs = list(
# format numeric columns
list(
targets = 1:(ncol(dat)-1), # regard JS indeces
render = JS(
"function(data,type,row,meta){",
" if(type!=='display') return data;",
" var num=parseFloat(data);",
" if(isNaN(num)) return data;",
" var f=Math.abs(num).toLocaleString(undefined,{minimumFractionDigits:0});",
" return num<0?'($'+f+')':'$'+f;",
"}"
)
)
),
rowGroup = list(
dataSrc = 0,
startRender = JS(
paste(
"function(rows, group){",
" if(!window.collapsedGroups) window.collapsedGroups={};",
" var collapsed = !!window.collapsedGroups[group];",
" var columnWidths = [];", # determine the percentage ratios of each column to assign to cell styles
" $('#DataTables_Table_0 > thead > tr > th').each(function(i) {",
" header_width = $('#DataTables_Table_0 > thead > tr').width();",
" columnWidths[i] = ($(this).width()/ header_width);",
" });",
" var triangle = (group !== '') ? (collapsed ? '<span>▸</span> ' : '<span>▾</span> ') : '';",
" rows.nodes().each(function(r){ r.style.display = collapsed ? 'none' : ''; });",
" function fmt(n){ var f=Math.abs(n).toLocaleString(undefined,{minimumFractionDigits:0}); return n<0?'($'+f+')':'$'+f; }",
" var cells = '';",
" columnWidths.forEach(function(w, i){",
" if(i === 0){",
" cells += '<div class=\"dt-group-cell\" style=\"width:'+w+'%\">'+triangle+group+'</div>';",
" } else {",
" var total = rows.data().pluck(i).reduce(function(a,b){ return (+a)+(+b); }, 0);",
" cells += '<div class=\"dt-group-cell\" style=\"width:'+w+'%\">'+fmt(total)+'</div>';",
" }",
" });",
" return '<div class=\"dt-group-header\" data-name=\"'+group+'\">' + cells + '</div>';",
"}",
collapse = "\n"
)
)
)
),
callback = JS(
sprintf("window.collapsedGroups={%s};", paste(sprintf("'%s':true", groups), collapse = ", ")),
"
table.on('click', '.dt-group-header', function(){
var name = $(this).data('name');
if(name !== undefined){
window.collapsedGroups[name] = !window.collapsedGroups[name];
table.draw(false);
}
});
"
)
)
})
}
shinyApp(ui, server)
giving your desired result. Here I added dotted borderlines for clarity