Color cells depending of their value for each row of a data frame - r

I have a data frame which looks like this:
header1 header2 header3 header4 ...
rowname1 1 2 3 4
rowname2 4 3 2 1
rowname3 2 4 1 3
rowname4 1 4 3 2
...
I would like to make a color gradient depending of the values for each row. Typically I would like the maximum value of each row to be colored green, the minimum value of each row colored red, and the other cells to be colored gradually depending of their value (second worst would be orange, second best would be yellow, etc ...).
An example of what I would like to obtain:
Could you please help me in solving this matter ?

Here is a possibility with DT.
dat <- data.frame(
V1 = rpois(6,5),
V2 = rpois(6,5),
V3 = rpois(6,5),
V4 = rpois(6,5),
V5 = rpois(6,5),
V6 = rpois(6,5)
)
library(DT)
js <- c(
"function(row, data, num, index){",
" data.shift();", # remove row name
" var min = Math.min.apply(null, data);",
" var max = Math.max.apply(null, data);",
" for(var i=0; i<data.length; i++){",
" var f = (data[i] - min)/(max-min);",
" var h = 120*f;",
" var color = 'hsl(' + h + ', 100%, 50%)';",
" $('td:eq('+(i+1)+')', row).css('background-color', color);",
" }",
"}"
)
datatable(dat, options = list(rowCallback = JS(js)))
To add black borders, do
datatable(dat, options = list(rowCallback = JS(js))) %>%
formatStyle(1:(ncol(dat)-1), `border-right` = "solid 1px")
The above solution assumes that you display the row names in the table. If you don't want to display the row names, do:
js <- c(
"function(row, data, num, index){",
" var min = Math.min.apply(null, data);",
" var max = Math.max.apply(null, data);",
" for(var i=0; i<data.length; i++){",
" var f = (data[i] - min)/(max-min);",
" var h = 120*f;",
" var color = 'hsl(' + h + ', 100%, 50%)';",
" $('td:eq('+i+')', row).css('background-color', color);",
" }",
"}"
)
datatable(dat, rownames = FALSE, options = list(rowCallback = JS(js)))
Edit
As requested by the OP in the chat, here is a variant. Instead of generating a color proportional to the cell value, it generates a color proportional to the rank of the cell value.
js <- c(
"function(row, data, num, index){",
" data.shift();", # remove row name
" var data_uniq = data.filter(function(item, index) {",
" if(data.indexOf(item) == index){",
" return item;",
" }}).sort(function(a,b){return a-b});",
" var n = data_uniq.length;",
" var ranks = data.slice().map(function(v){ return data_uniq.indexOf(v) });",
" for(var i=0; i<data.length; i++){",
" var f = ranks[i]/(n-1);",
" var h = 120*f;",
" var color = 'hsl(' + h + ', 100%, 50%)';",
" $('td:eq('+(i+1)+')', row).css('background-color', color);",
" }",
"}"
)
dat <- as.data.frame(matrix(round(rnorm(24),2), ncol=8))
datatable(dat, options = list(rowCallback = JS(js)))
I've found that the colors are more distinct by replacing var h = 120*f; with
var h = 60*(1 + Math.tan(2*f-1)/Math.tan(1));

Related

How to add multiple scrollbar in R Shiny data table

I am trying to build a shiny app that has the following feature:
It has a parent-child feature that can expand and collapse as per
user interaction (Requirement 1 - Done)
When the rows are expanded, several child rows are displayed in the
data table. I want to introduce multiple scroll bars in this table.
1st scrollbar will be for 1st 4 columns and another scrollbar for the
rest of the columns. (Requirement 2 - Not Done)
The below code is able to produce results for the 1st requirement (using JQuery) however, I am unable to find a way out for Requirement 2.
Can anyone assist here?
packages = c(
'shiny',
'shinydashboard',
'tidyverse',
'dplyr',
'magrittr',
'plotly',
'ggplot2',
'scales',
'DT',
"shinyWidgets",
"fontawesome"
)
for (p in packages) {
if (!require(p, character.only = T)) {
install.packages(p)
}
library(p, character.only = T)
}
DataIn <- mtcars
DataIn <- DataIn %>% tidyr::nest(-cyl)
DataIn <- DataIn %>%
{
bind_cols(data_frame(
' ' = rep(
'<img src=\"https://raw.githubusercontent.com/DataTables/DataTables/master/examples/resources/details_open.png\"/>',
nrow(.)
)
), .)
}
# get dynamic info and strings
nested_columns <-
which(sapply(DataIn, class) == "list") %>% setNames(NULL)
not_nested_columns <-
which(!(seq_along(DataIn) %in% c(1, nested_columns)))
not_nested_columns_str <-
not_nested_columns %>% paste(collapse = "] + '_' + d[") %>% paste0("d[", ., "]")
CallBack <- paste0(
"
table.column(1).nodes().to$().css({cursor: 'pointer'});
// Format data object (the nested table) into another table
var format = function(d) {
if(d != null){
var result = ('<table id=\"child_' + ",
not_nested_columns_str,
" + '\">') + '<thead><tr>'
for (var col in d[",
nested_columns,
"][0]){
result += '<th>' + col + '</th>'
}
result += '</tr></thead></table>'
return result
}else{
return '';
}
}
var format_datatable = function(d) {
var dataset = [];
for (i = 0; i <= d[",
nested_columns,
"].length-1; i++) {
var datarow = $.map(d[",
nested_columns,
"][i], function(value, index) {
return [value];
});
dataset.push(datarow);
}
var subtable = $(('table#child_' + ",
not_nested_columns_str,
")).DataTable({
'data': dataset,
'autoWidth': true,
'deferRender': true,
'info': false,
'lengthChange': false,
'ordering': true,
'paging': false,
'scrollX': false,
'scrollY': false,
'searching': false
// 'fnRowCallback': function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
// $('td', nRow).css('background-color', 'Red')}
});
};
table.on('click', 'td.details-control', function() {
var td = $(this), row = table.row(td.closest('tr'));
if (row.child.isShown()) {
row.child.hide();
td.html('<img src=\"https://raw.githubusercontent.com/DataTables/DataTables/master/examples/resources/details_open.png\"/>');
} else {
row.child(format(row.data())).show();
td.html('<img src=\"https://raw.githubusercontent.com/DataTables/DataTables/master/examples/resources/details_close.png\"/>');
format_datatable(row.data())
}
});"
)
shinyApp(
ui = fluidPage(DT::dataTableOutput('tbl')),
server = function(input, output) {
output$tbl = DT::renderDataTable(datatable(
DataIn,
escape = -2,
# raw HTML in column 2
options = list(columnDefs = list(
list(visible = FALSE, targets = c(0, nested_columns)),
# Hide row numbers and nested columns
list(
orderable = FALSE,
className = 'details-control',
targets = 1
) # turn first column into control column
)),
callback = JS(CallBack)
))
}
)

How can I use last-of in a QUERY-PREPARE()?

I made a huge code and it was taking a very long time to process. Following suggestions here, I made it shorter and better and now it's running much faster. However, I noticed I should be getting a sum of the values whereas I'm just getting the values. I tried using ACCUMULATE TOTAL, but it didn't work because the LAST-OF can only be used with a BREAK BY and my break by is inside the QUERY-PREPARE().
How can I get the sum of the following values?
doc-fiscal.vl-cont-doc
doc-fiscal.vl-bicms
doc-fiscal.vl-icms
doc-fiscal.vl-icmsou
doc-fiscal.vl-ipiou
doc-fiscal.vl-ipi
Bellow is the code I'm using, which is working almost perfectly.
//Query
cQuery = "FOR EACH doc-fiscal WHERE doc-fiscal.dt-docto >= " + QUOTER(pd-data-1) + " AND doc-fiscal.dt-docto <= " + QUOTER(pd-data-2) + " AND (doc-fiscal.cod-observa <> 4 OR doc-fiscal.tipo-nat <> 3) AND doc-fiscal.cd-situacao <> 06 AND doc-fiscal.cd-situacao <> 22".
cQuery = cQuery + semCodEmitente + comCodEmitente + CheckBoxindSitDoc.
cQuery = cQuery + ", EACH natur-oper USE-INDEX natureza WHERE doc-fiscal.nat-operacao = natur-oper.nat-operacao" + modeloEletronico + tipoEntrada + natOper.
cQuery = cQuery + " BREAK BY doc-fiscal.dt-docto BY doc-fiscal.nr-doc-fis ".
QUERY qRelatorio:QUERY-PREPARE(cQuery).
QUERY qRelatorio:QUERY-OPEN().
GET FIRST qRelatorio.
DEF VAR soma-vl-cont-doc AS DECIMAL.
DO WHILE AVAILABLE doc-fiscal:
soma-vl-cont-doc = soma-vl-cont-doc + doc-fiscal.vl-cont-doc.
IF LAST-OF(doc-fiscal.nr-doc-fis) THEN DO:
CREATE tt-relatorio.
ASSIGN
tt-relatorio.nr-doc-fis = doc-fiscal.nr-doc-fis
tt-relatorio.serie = doc-fiscal.serie
tt-relatorio.char-2 = SUBSTRING(doc-fiscal.char-2,155,44, "CHAR")
tt-relatorio.cod-model-nf-eletro = natur-oper.cod-model-nf-eletro
tt-relatorio.tipo = natur-oper.tipo
tt-relatorio.cod-estabel = doc-fiscal.cod-estabel
tt-relatorio.cod-emitente = doc-fiscal.cod-emitente
tt-relatorio.nome-ab-emi = doc-fiscal.nome-ab-emi
tt-relatorio.cgc = doc-fiscal.cgc
tt-relatorio.dt-emis-doc = doc-fiscal.dt-emis-doc
tt-relatorio.dt-docto = doc-fiscal.dt-docto
tt-relatorio.ind-sit-doc = doc-fiscal.ind-sit-doc
tt-relatorio.vl-cont-doc = doc-fiscal.vl-cont-doc
tt-relatorio.vl-bicms = doc-fiscal.vl-bicms
tt-relatorio.vl-icms = doc-fiscal.vl-icms
tt-relatorio.vl-icmsou = doc-fiscal.vl-icmsou
tt-relatorio.vl-ipiou = doc-fiscal.vl-ipiou
tt-relatorio.vl-ipi = doc-fiscal.vl-ipi
tt-relatorio.imp-nota = natur-oper.imp-nota.
GET NEXT qRelatorio.
END.
END.
QUERY qRelatorio:QUERY-CLOSE().
Thanks for the help and sorry for being such a newbie. I hope my question can help other people.
I managed to do it.
I used LAST-OF() METHOD. My code became the following.
DEF VAR soma-vl-cont-doc AS DECIMAL. //INICIO DAS SOMAS
DEF VAR soma-vl-bicms AS DECIMAL.
DEF VAR soma-vl-icms AS DECIMAL.
DEF VAR soma-vl-icmsou AS DECIMAL.
DEF VAR soma-vl-ipiou AS DECIMAL.
DEF VAR soma-vl-ipi AS DECIMAL.
DO WHILE AVAILABLE doc-fiscal:
soma-vl-cont-doc = soma-vl-cont-doc + doc-fiscal.vl-cont-doc.
soma-vl-bicms = soma-vl-bicms + doc-fiscal.vl-bicms.
soma-vl-icms = soma-vl-icms + doc-fiscal.vl-icms.
soma-vl-icmsou = soma-vl-icmsou + doc-fiscal.vl-icmsou.
soma-vl-ipiou = soma-vl-ipiou + doc-fiscal.vl-ipiou.
soma-vl-ipi = soma-vl-ipi + doc-fiscal.vl-ipi.
IF QUERY qRelatorio:LAST-OF(2) THEN DO:
CREATE tt-relatorio.
ASSIGN
tt-relatorio.nr-doc-fis = doc-fiscal.nr-doc-fis
tt-relatorio.serie = doc-fiscal.serie
tt-relatorio.char-2 = SUBSTRING(doc-fiscal.char-2,155,44, "CHAR")
tt-relatorio.cod-model-nf-eletro = natur-oper.cod-model-nf-eletro
tt-relatorio.tipo = natur-oper.tipo
tt-relatorio.cod-estabel = doc-fiscal.cod-estabel
tt-relatorio.cod-emitente = doc-fiscal.cod-emitente
tt-relatorio.nome-ab-emi = doc-fiscal.nome-ab-emi
tt-relatorio.cgc = doc-fiscal.cgc
tt-relatorio.dt-emis-doc = doc-fiscal.dt-emis-doc
tt-relatorio.dt-docto = doc-fiscal.dt-docto
tt-relatorio.ind-sit-doc = doc-fiscal.ind-sit-doc
tt-relatorio.vl-cont-doc = soma-vl-cont-doc
tt-relatorio.vl-bicms = soma-vl-bicms
tt-relatorio.vl-icms = soma-vl-icms
tt-relatorio.vl-icmsou = soma-vl-icmsou
tt-relatorio.vl-ipiou = soma-vl-ipiou
tt-relatorio.vl-ipi = soma-vl-ipi
//tt-relatorio.idi-sit-nf-eletro = nota-fiscal.idi-sit-nf-eletro
tt-relatorio.imp-nota = natur-oper.imp-nota.
soma-vl-cont-doc = 0.
soma-vl-bicms = 0.
soma-vl-icms = 0.
soma-vl-icmsou = 0.
soma-vl-ipiou = 0.
soma-vl-ipi = 0.
END.
GET NEXT qRelatorio.
END.
With this, I managed to get the answers I wanted andthe query is quite fast. If there's any suggestions as to how make it faster, I'm open for them. Thanks.

R shiny format background color of two cells within datatable

I have a datatable (myTable) which is simply a 2x2 datatable (2 rows and 2 columns)
I want to format the background color of only 2 cells within the datatable:
Cell[1,2] should always be green (first row, second column)
Cell[2,1] should always be red (second row, first column)
This is as far as I've got
Any ideas would be appreciated
formatStyle(
myTable,
columns = c(1,2),
fontWeight = 'bold',
backgroundColor = ????,
border = '2px solid #ddd'
)
Does it help ?
library(DT)
changeCellColor <- function(i, j, color){
color <- sprintf("'%s'", color)
c(
"function(row, data, num, index){",
sprintf(" var i = [%s], j = [%s], color = [%s];",
toString(i-1), toString(j), toString(color)),
" var n = i.length;",
" for(let k=0; k < n; k++){",
" if(index == i[k]){",
" $('td:eq(' + j[k] + ')', row)",
" .css({'background-color': color[k]});",
" }",
" }",
"}"
)
}
datatable(iris[1:5,],
options = list(
dom = "t",
rowCallback = JS(changeCellColor(c(1,2), c(2,1), c("blue","red")))
)
)

R Shiny Datatable Child Row Selection and Info Issue

I am having an issue selecting the child rows in a R Shiny DT Table with JS callback.
When expanding the parent row, I try to select the child rows, and all rows are selected in that child (including child's background).
If I select 2nd child row, the background is deselcted and it shows my 2 childs selected (every other click selects all child rows, then shows ones selected repeatedly)
Also, how to get the information on which child rows are selected?
Thank you very much!
Alex B
I am trying to play with the datatable settings in the JS callback.
'''
library(data.table)
library(DT)
library(shiny)
library(jsonlite)
ui <- fluidPage(DT::dataTableOutput(width = "100%", "table"))
server <- function(input, output) {
output$table = DT::renderDataTable({
mtcars_dt = data.table(mtcars)
setkey(mtcars_dt,mpg,cyl)
mpg_dt = unique(mtcars_dt[, list(mpg, cyl)])
setkey(mpg_dt, mpg, cyl)
cyl_dt = unique(mtcars_dt[, list(cyl)])
setkey(cyl_dt, cyl)
mtcars_dt = mtcars_dt[, toJSON(.SD), by = list(mpg,cyl)]
setnames(mtcars_dt,'V1','mtcars')
mtcars_dt[, ' ' := '►']
df1 = mtcars_dt
df1 = df1[c(1,6),]
setcolorder(df1, c(length(df1),c(1:(length(df1) - 1))))
DT::datatable(
data = df1,
rownames = FALSE,
escape = FALSE,
selection="multiple",
options = list(
# dom = 'Bfrti',
stripeClasses = list(),
deferRender = TRUE,
# scrollX = TRUE,
pageLength = 25,
scrollY = "1000",
scroller = TRUE,
scollCollapse = TRUE,
lengthMenu = c(20, 50, 100, 500),
searchHighlight = TRUE,
tabIndex = 1,
columnDefs = list(
list(orderable = FALSE, className = 'details-control', targets = 0),
list(visible = FALSE, targets = -1 )
)
),
callback = JS("
//table.header().to$().css({'background-color': '#000', 'color': '#fff'})
table.column(01).nodes().to$().css({cursor: 'pointer'})
var table_id = 1000
// Format child object into another table
var format = function(table_id, columns) {
if(columns != null){
var result = ('<table id=\"' + table_id + '\"><thead><tr>')
for (var i in columns){
result += '<th>' + columns[i] + '</th>'
}
result += '</tr></thead></table>'
return result
}else{
return ''
}
}
var format_datatable = function( table_id, newtable, columns) {
if(newtable != null){
var column_defs = []
for (var i in columns)
{
if (i == 0)
{
column_defs[i] = {'data': columns[i], 'targets': parseInt(i), 'orderable': false, 'className': 'details-control'}
}
else
{
column_defs[i] = {'data': columns[i], 'targets': parseInt(i)}
}
}
/* alert(JSON.stringify(column_defs)) */
//var printTable = document.getElementById(newtable)
//document.write(newtable)
//document.write(columns)
var subtable = $(('table#' + table_id)).DataTable({
'data': newtable,
'autoWidth': false,
'deferRender': true,
'stripeClasses': [],
'info': false,
'select': { style: 'os',
},
'lengthChange': false,
'ordering': false,
'paging': false,
'scrollX': false,
'scrollY': false,
'searching': false,
'columnDefs': column_defs
}).draw()
}
}
table.on('click', 'td.details-control', function() {
var td = $(this)
var table = $(td).closest('table')
var row = $(table).DataTable().row(td.closest('tr'))
if (row.child.isShown()) {
row.child.hide()
td.html('►')
}
else
{
var row_data = row.data()
if (!Array.isArray(row_data))
{
row_data = Object.keys(row_data).map(function (key) {
return row_data[key]
});
}
var newtable = JSON.parse(row_data[row_data.length-1])
var columns = Object.keys(newtable[0])
table_id++
row.child(format(table_id, columns)).show()
format_datatable(table_id, newtable, columns)
console.log(table_id)
td.html('▼')
}
})
")
)
})
observe({
print(input$table_rows_selected)
print(input$newtable_rows_selected)
})
}
shinyApp(ui = ui, server = server)
'''
I would like to highlight individual child rows and know which child rows are selected. Currently it highlights all child rows each time it clicks.
Here is an attempt. This works, but the selection on the main table is disabled.
library(data.table)
library(DT)
library(shiny)
library(jsonlite)
initComplete <- paste(
"function(settings){",
" var table = settings.oInstance.api();",
" var tbl = table.table().node();",
" var id = $(tbl).closest('.dataTable').attr('id');",
" table.on('click', 'tbody tr', function(){",
" // send selected columns to Shiny",
" setTimeout(function(){",
" var indexes = table.rows({selected:true}).indexes();",
" var indices = Array(indexes.length);",
" for(var i = 0; i < indices.length; ++i){",
" indices[i] = indexes[i];",
" }",
" Shiny.setInputValue('childrow_rows_selected', {child: id, rows: indices});",
" },0);",
" });",
"}",
sep = "\n"
)
ui <- fluidPage(DT::dataTableOutput(width = "100%", "table"))
server <- function(input, output) {
output$table = DT::renderDataTable({
mtcars_dt = data.table(mtcars)
setkey(mtcars_dt,mpg,cyl)
mpg_dt = unique(mtcars_dt[, list(mpg, cyl)])
setkey(mpg_dt, mpg, cyl)
cyl_dt = unique(mtcars_dt[, list(cyl)])
setkey(cyl_dt, cyl)
mtcars_dt = mtcars_dt[, toJSON(.SD), by = list(mpg,cyl)]
setnames(mtcars_dt,'V1','mtcars')
mtcars_dt[, ' ' := '►']
df1 = mtcars_dt
df1 = df1[c(1,6),]
setcolorder(df1, c(length(df1),c(1:(length(df1) - 1))))
DT::datatable(
data = df1,
rownames = FALSE,
escape = FALSE,
selection = "none",
extensions = "Select",
options = list(
# dom = 'Bfrti',
stripeClasses = list(),
deferRender = TRUE,
# scrollX = TRUE,
pageLength = 25,
scrollY = "1000",
scroller = TRUE,
scollCollapse = TRUE,
lengthMenu = c(20, 50, 100, 500),
searchHighlight = TRUE,
tabIndex = 1,
columnDefs = list(
list(orderable = FALSE, className = 'details-control', targets = 0),
list(visible = FALSE, targets = -1 )
)
),
callback = JS("
table.column(0).nodes().to$().css({cursor: 'pointer'});
// var table_id = 1000
// Format child object into another table
var format = function(table_id, columns) {
if(columns != null){
var result = ('<table id=\"' + table_id + '\"><thead><tr>')
for (var i in columns){
result += '<th>' + columns[i] + '</th>'
}
result += '</tr></thead></table>'
return result
}else{
return ''
}
}
var format_datatable = function( table_id, newtable, columns) {
if(newtable != null){
var column_defs = []
for (var i in columns)
{
if (i == 0)
{
column_defs[i] = {'data': columns[i], 'targets': parseInt(i), 'orderable': false, 'className': 'details-control'}
}
else
{
column_defs[i] = {'data': columns[i], 'targets': parseInt(i)}
}
}
var subtable = $(('table#' + table_id)).DataTable({
'data': newtable,",
sprintf("initComplete: %s,", initComplete),
" 'autoWidth': false,
'deferRender': true,
'stripeClasses': [],
'info': false,
'select': {style: 'multi'},
'lengthChange': false,
'ordering': false,
'paging': false,
'scrollX': false,
'scrollY': false,
'searching': false,
'columnDefs': column_defs
}).draw()
}
}
table.on('click', 'td.details-control', function() {
var td = $(this);
var table = $(td).closest('table');
var row = $(table).DataTable().row(td.closest('tr'));
var table_id = 'child' + row.index();
if (row.child.isShown()) {
row.child.hide();
td.html('►');
}
else
{
var row_data = row.data();
if (!Array.isArray(row_data))
{
row_data = Object.keys(row_data).map(function (key) {
return row_data[key];
});
}
var newtable = JSON.parse(row_data[row_data.length-1])
var columns = Object.keys(newtable[0])
//table_id++
row.child(format(table_id, columns)).show()
format_datatable(table_id, newtable, columns)
console.log(table_id)
td.html('▼')
}
})
")
)
})
observe({
# print(input$table_rows_selected)
# print(input$newtable_rows_selected)
print(input$childrow_rows_selected)
})
}
shinyApp(ui = ui, server = server)

How to perform rowcallback on 1 column only with R DT datatable

In a datatable like the one below, I am failing to change the rowCallback to only change 1 column, lets say i = 2. I tried to alter the for (i = 1 .... statement, but I keep ending up with blank tables
datatable(cars/10,options = list(
rowCallback = JS(
"function(row, data) {",
"for (i = 1; i < data.length; i++) {",
"if (data[i]>1000 | data[i]<1){",
"$('td:eq('+i+')', row).html(data[i].toExponential(1));",
"}",
"}",
"}")
)
)
You just have to apply your code for i=2 only:
datatable(cars/10,
options = list(
rowCallback = JS(
"function(row, data) {",
" if (data[2]>1000 || data[2]<1) {",
" $('td:eq(2)', row).html(data[2].toExponential(1));",
" }",
"}")
)
)

Resources