Use several selection colors in DT::datatable? - css

I have set:
output$tableId <-
DT::renderDataTable({DT::datatable(..., selection = "multiple")})
And I would like the background color of the selection to vary depending on of it's the first selection, or the second, etc.
It should be possible to use length(input$tableId_rows_selected) (which is described here) to obtain this behaviour? I'm guessing in combination with creating some CSS modifying this:
.table.dataTable tbody td.active, .table.dataTable tbody tr.active td {
background-color: #007bff;
color: white;
}
I know very little CSS, HTML, and JavaScript, so I find these things difficult.
EDIT 2022-02-14:
After #StéphaneLaurent initial answer below, I want to change my question slightly:
The logic I would prefer the most is:
We have m unique colors. n < m previous rows have been selected.
The n+1th row gets selected and is then coloured in the n+1th color and keeps this color until deselected.
When m rows are currently selected, no more selections are possible. Alternatively, I would also be interested in: When the m+1th row gets selected, it gets coloured in the first color, and other row occupying this color gets deselected. m+1 rows are now chosen, and when the m+2th row gets selected, it gets coloured in the second color, and the other row occupying this color gets deselected. And so on.
Moreover:
Since my app will be running through an iframe on a website, and the underlying data of the app will have 10 to 100 million observations, I think a solution where DT::renderDataTable({...}, server = TRUE) would be good.
I also want the solution to work with the DT::datatable(..., selection = list(mode = "multiple", selected = 1:3, target = "row")) option.
I will try to implement the above using #StéphaneLaurent answer, combined with the tools #YihuiXie illustrate here (app from page linked earlier).

If you use the following CSS, then the first row will be red if you select it, the second will be green, the third will be blue:
#dtable tbody tr.selected:nth-of-type(1) td {
box-shadow: inset 0 0 0 9999px red;
}
#dtable tbody tr.selected:nth-of-type(2) td {
box-shadow: inset 0 0 0 9999px green;
}
#dtable tbody tr.selected:nth-of-type(3) td {
box-shadow: inset 0 0 0 9999px blue;
}
Here dtable is the id.
library(shiny)
library(DT)
css <- "
#dtable tbody tr.selected:nth-of-type(1) td {
box-shadow: inset 0 0 0 9999px red;
}
#dtable tbody tr.selected:nth-of-type(2) td {
box-shadow: inset 0 0 0 9999px green;
}
#dtable tbody tr.selected:nth-of-type(3) td {
box-shadow: inset 0 0 0 9999px blue;
}
"
ui <- fluidPage(
tags$head(
tags$style(
HTML(css)
)
),
br(), br(),
DTOutput("dtable")
)
server <- function(input, output, session) {
output[["dtable"]] <- renderDT({
datatable(iris, selection = "multiple")
})
}
shinyApp(ui, server)
Here is how to generate the CSS for 100 rows with a random color, using the sass package. Save the following scss file, say mystyle.scss:
$s-min: 20;
$s-max: 70;
$l-min: 30;
$l-max: 90;
#for $i from 1 through 100 {
#dtable tbody tr.selected:nth-of-type(#{$i}) td {
box-shadow: inset 0 0 0 9999px hsl(random(360),$s-min+random($s-max+-$s-min),$l-min+random($l-max+-$l-min));
}
}
Now compile it to a CSS file:
library(sass)
sass(sass_file("mystyle.scss"), output = "mystyle.css")
Put the file mystyle.css in the www subfolder of the app, and then include it in the app as follows:
ui <- fluidPage(
tags$head(
tags$link(
href = "mystyle.css", rel = "stylesheet"
)
),
......
EDIT : jQuery solution
library(shiny)
library(DT)
js <- '
var colors = ["red", "green", "blue", "yellow", "purple"];
table.on("select", function(e, dt, type, indexes) {
var count = table.rows({selected: true}).count();
for(var i = 0; i < count; i++) {
$("#dtable tbody tr.selected").eq(i).find("td").css(
"box-shadow", "inset 0 0 0 9999px " + colors[i]
);
}
}).on("deselect", function(e, dt, type, indexes) {
for(var i = 0; i < indexes.length; i++) {
$("#dtable tbody tr").eq(indexes[i]).find("td").css(
"box-shadow", ""
);
}
var count = table.rows({selected: true}).count();
for(var i = 0; i < count; i++) {
$("#dtable tbody tr.selected").eq(i).find("td").css(
"box-shadow", "inset 0 0 0 9999px " + colors[i]
);
}
});
'
ui <- fluidPage(
br(), br(),
DTOutput("dtable")
)
server <- function(input, output, session) {
output[["dtable"]] <- renderDT({
datatable(
iris,
extensions = "Select",
selection = "none",
callback = JS(js),
options = list(
"select" = "multi"
)
)
}, server = FALSE)
}
EDIT: correction of previous edit
Here is the correct JS code:
js <- '
var colors = ["red", "green", "blue", "yellow", "purple"];
var stack = [];
table.on("select", function(e, dt, type, indexes) {
stack.push(indexes[0]);
for(var i = 0; i < stack.length; i++) {
$("#dtable tbody tr").eq(stack[i]).find("td").css(
"box-shadow", "inset 0 0 0 9999px " + colors[i]
);
}
}).on("deselect", function(e, dt, type, indexes) {
var i0 = stack.indexOf(indexes[0]);
$("#dtable tbody tr").eq(stack[i0]).find("td").css(
"box-shadow", ""
);
stack.splice(i0, 1);
for(var i = 0; i < stack.length; i++) {
$("#dtable tbody tr").eq(stack[i]).find("td").css(
"box-shadow", "inset 0 0 0 9999px " + colors[i]
);
}
});
'
EDIT: without the 'Select' extension
js <- '
var colors = ["red", "green", "blue", "yellow", "purple"];
var stack = [];
table.on("click", "tr", function() {
var $rows = $("#dtable tbody tr"); // SIMONSIMON I moved this line
var $row = $(this);
var idx = $row.index();
if($row.hasClass("selected")) {
stack.push(idx);
for(var i = 0; i < stack.length; i++) {
$rows.eq(stack[i]).find("td").css(
"box-shadow", "inset 0 0 0 9999px " + colors[i]
);
}
} else {
var i0 = stack.indexOf(idx);
$rows.eq(stack[i0]).find("td").css(
"box-shadow", ""
);
stack.splice(i0, 1);
for(var i = 0; i < stack.length; i++) {
$rows.eq(stack[i]).find("td").css(
"box-shadow", "inset 0 0 0 9999px " + colors[i]
);
}
}
});
'
......
output[["dtable"]] <- renderDT({
datatable(
iris,
selection = "multiple",
callback = JS(js)
)
}, server = TRUE)

While technically this doesn't do what I originally requested (i.e. changing the selection colouring dynamically), the results from this implementation appear visually similar to the end-user.
The idea behind the solution is to keep track of the row selection counter, update the table via a proxy, and apply conditional formatting.
library(shiny)
library(data.table)
library(DT)
data(iris)
iris <- suppressWarnings(cbind(as.data.table(iris),
currently_selected = numeric(),
selected_as_nr = numeric(),
overwrite = numeric()))
ui <- fluidPage(
dataTableOutput("table"),
)
server <- function(input, output, session) {
iris[, currently_selected := 0]
iris[, selected_as_nr := NA]
iris[, overwrite := 0]
output$table <- DT::renderDataTable({
DT::datatable(iris,
selection = list(mode = "multiple",
selected = 1,
target = "row")) %>%
formatStyle("Sepal.Length",
"selected_as_nr",
backgroundColor = styleEqual(0:4, c("green",
"red",
"black",
"blue",
"yellow")))
})
proxy <- dataTableProxy(outputId = "table")
observeEvent(input$table_rows_selected, {
new_selected_row <- input$table_rows_selected[1]
iris[new_selected_row, currently_selected := (currently_selected + 1) %% 2]
if (iris[new_selected_row, currently_selected] == 1) {
new_selection_nr <- suppressWarnings(iris[, min(setdiff(0:4, unique(selected_as_nr)))])
if (new_selection_nr != "Inf") {
iris[, overwrite := 0]
iris[new_selected_row, selected_as_nr := new_selection_nr]
} else {
overwrite <- iris[, unique(overwrite)]
iris[selected_as_nr == overwrite, currently_selected := 0]
iris[selected_as_nr == overwrite, selected_as_nr := NA]
iris[new_selected_row, selected_as_nr := overwrite]
iris[, overwrite := (overwrite + 1) %% 5]
}
} else {
iris[new_selected_row, selected_as_nr := NA]
}
DT::replaceData(proxy, iris)
})
}
shinyApp(ui, server)

Related

schelling segregation model in shiny

I tried to run the Schelling segregation model in shiny. I got 3 inputes: number of houses, number of neighbors and alike_preference and sorting should be done within 1000 seconds. The problem is I don't get the output.
Also, I put my renderTable() function both in the eventreactive() and loop, and outside them , but still no output was shown.
library(shiny)
# UI
ui <- fluidPage(
titlePanel("Schelling’s model!"),
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "Ic1",
label = "Number of Houses:",
min = 1,
max = 51,
value =1),
sliderInput(inputId = "Ic2",
label = "Number of Neighbours",
min=0,
max=2000,
step=50,
value = 0),
sliderInput(inputId = 'Ic3',
label = 'alike_preference',
min=0,
max=1,
value=0),
br(),
actionButton(inputId = 'Id8','go',
style="color: #fff; background-color: #428fd6; border-color: #2e6da4")
),
# Main panel for displaying outputs ----
mainPanel(
tableOutput(outputId = "map"),
)
)
)
#Now The cycle of changing places in 1000 seconds
server <- function(input, output) {
grid_activation1 <- reactiveVal()
grid_activation1 <- eventReactive(input$Id8 ,{
input$Ic3
group<-c(rep(0,(input$Ic1*input$Ic1)-input$Ic2,rep(1,input$Ic2/2),rep(2,input$Ic2/2)))
grid <- matrix(sample(group,input$Ic1*Input$Ic1,replace=F), ncol= input$Ic1)
image(grid,col=c("black","red","green"),axes=F)
get_neighbors<-function(coords) {
n<-c()
for (i in c(1:8)) {
if (i == 1) {
x<-coords[1] + 1
y<-coords[2]
}
if (i == 2) {
x<-coords[1] + 1
y<-coords[2] + 1
}
if (i == 3) {
x<-coords[1]
y<-coords[2] + 1
}
if (i == 4) {
x<-coords[1] - 1
y<-coords[2] + 1
}
if (i == 5) {
x<-coords[1] - 1
y<-coords[2]
}
if (i == 6) {
x<-coords[1] - 1
y<-coords[2] - 1
}
if (i == 7) {
x<-coords[1]
y<-coords[2] - 1
}
if (i == 8) {
x<-coords[1] + 1
y<-coords[2] - 1
}
if (x < 1) {
x<-51
}
if (x > 51) {
x<-1
}
if (y < 1) {
y<-51
}
if (y > 51) {
y<-1
}
n<-rbind(n,c(x,y))
}
n
}
for (t in c(1:1000)) {
happy_cells<-c()
unhappy_cells<-c()
for (j in c(1:input$Ic1)) {
for (k in c(1:input$Ic1)) {
current<-c(j,k)
value<-grid[j,k]
if (value > 0) {
like_neighbors<-0
all_neighbors<-0
neighbors<-get_neighbors(current)
for (i in c(1:nrow(neighbors))){
x<-neighbors[i,1]
y<-neighbors[i,2]
if (grid[x,y] > 0) {
all_neighbors<-all_neighbors + 1
}
if (grid[x,y] == value) {
like_neighbors<-like_neighbors + 1
}
}
if (is.nan(like_neighbors / all_neighbors)==FALSE) {
if ((like_neighbors / all_neighbors) < input$Ic3) {
unhappy_cells<-rbind(unhappy_cells,c(current[1],current[2]))
}
else {
happy_cells<-rbind(happy_cells,c(current[1],current[2]))
}
}
else {
happy_cells<-rbind(happy_cells,c(current[1],current[2]))
}
}
}
}
happiness_tracker<-append(happiness_tracker,length(happy_cells)/(length(happy_cells) + length(unhappy_cells)))
rand<-sample(nrow(unhappy_cells))
for (i in rand) {
mover<-unhappy_cells[i,]
mover_val<-grid[mover[1],mover[2]]
move_to<-c(sample(1:input$Ic1,1),sample(1:input$Ic1,1))
move_to_val<-grid[move_to[1],move_to[2]]
while (move_to_val > 0 ){
move_to<-c(sample(1:input$Ic1,1),sample(1:input$Ic1,1))
move_to_val<-grid[move_to[1],move_to[2]]
}
grid[mover[1],mover[2]]<-0
grid[move_to[1],move_to[2]]<-mover_val
}
image(grid,col=c("black","red","green"),axes=F)
# I put output plotting in for loop to see changes Continuously
output$map <- renderTable({
grid_activation1
})
}
})
}
shinyApp(ui = ui, server = server)

Problem getting current list of input values in Shiny

I'm having quite a few problems to get the updated values from several coonditionalPanels. I created a reactive variable parList which should contain the parN_sig input variables. These variables should come from conditional panels and they are all named parN_sig. I do not know why but I always end up getting values from the double_sig panel when another panel is shown at the UI.
Here's my code:
ui.r
jscode <- "
shinyjs.disableTab = function(name) {
var tab = $('.nav li a[data-value=' + name + ']');
tab.bind('click.tab', function(e) {
e.preventDefault();
return false;
});
tab.addClass('disabled');
}
shinyjs.enableTab = function(name) {
var tab = $('.nav li a[data-value=' + name + ']');
tab.unbind('click.tab');
tab.removeClass('disabled');
}
"
css <- "
.nav li a.disabled {
background-color: #aaa !important;
color: #333 !important;
cursor: not-allowed !important;
border-color: #aaa !important;
}"
# Create Shiny object
library(shiny)
library(shinythemes)
library(shinyjs)
library(ggplot2)
library(grid)
library(egg)
# source("input.r")
source("functions.r")
formula_tabs<-tabsetPanel(
tabPanel("double_sig",
withMathJax("$$y=d+\\frac{a}{1+exp^{-b*(t-c)}}+\\frac{e}{1+exp^{-f*(t-g)}}$$")
),
tabPanel("gompertz",
withMathJax("$$y=b*exp^{\\ln(\\frac{c}{b})*exp^{-a*t}}$$")
),
tabPanel("verhulst",
withMathJax("$$y=\\frac{b*c}{b+(b-c)*exp^{-a*t}}$$")
),
id = "formulas",
type = "tabs"
)
fluidPage(useShinyjs(),theme = shinytheme("lumen"),useShinyjs(),tags$style("#params { display:none; } #formulas { display:none; }"),
extendShinyjs(text = jscode),inlineCSS(css),
navbarPage("Protein turnover model",id="tabs",
tabsetPanel(tabPanel("Input data",
checkboxInput("multiple","Single file",value = FALSE),
uiOutput("mult_files"),
uiOutput("sing_file"),
actionButton("disp_distr","Show distributions"),
plotOutput("distr_plot"),
plotOutput("distr_stade")
),
tabPanel("Weight fitting",
fileInput("weight_data","Choose weight data to be fitted",accept = c("text/csv")),
div(style="display:inline-block",selectInput("method_we","Select fitting formula",choices = c("Logistic"="verhulst","Gompertz"="gompertz","Empiric"="empirique","Log polynomial"="log_poly","Double sigmoid"="double_sig"))),
div(style="display:inline-block",formula_tabs), ## "Contois"="contois",,"Noyau"="seed",
conditionalPanel("input.method_we=='verhulst'",textInput("par1_sig","Enter value of a",value =0.1),
textInput("par2_sig","Enter value of b",value = 100),
textInput("par3_sig","Enter value of c",value = 1)),
conditionalPanel("input.method_we=='gompertz'",textInput("par1_sig","Enter value of a",value =0.065),
textInput("par2_sig","Enter value of b",value = 114.39),
textInput("par3_sig","Enter value of c",value = 0.52)),
conditionalPanel("input.method_we=='empirique'",textInput("par1_sig","Enter value of a",value =5.38),
textInput("par2_sig","Enter value of b",value = 8),
textInput("par3_sig","Enter value of c",value = 7)),
conditionalPanel("input.method_we=='double_sig'",textInput("par1_sig","Enter value of a",value = 48),
textInput("par2_sig","Enter value of b",value = 0.144),
textInput("par3_sig","Enter value of c",value = 35),
textInput("par4_sig","Enter value of d",value = 0.4),
textInput("par5_sig","Enter value of e",value = 48),
textInput("par6_sig","Enter value of f",value = 0.042),
textInput("par7_sig","Enter value of g",value = 90)),
actionButton("fit_op","Fit"),
plotOutput("fitplot")
),
tabPanel("mRNA fitting and calculation",
textInput("ksmin","Value of ksmin",value =3*4*3*3.6*24),
selectInput("fit_mrna","Select fitting formula",choices=c("3rd degree polynomial"="3_deg","6th degree polynomial"="6_deg","3rd degree logarithmic polynomial"="3_deg_log")),
actionButton("run_loop","Run calculation"),
disabled(downloadButton("downFile","Save results"))
),
tabPanel("Results",id="tabRes",
uiOutput("select_res"),
plotOutput("fit_prot_plot")
)
))
)
server.r
library(shiny)
library(shinythemes)
library(shinyjs)
library(ggplot2)
library(grid)
library(egg)
# source("input.r")
source("functions.r")
function(input, output, session) {
js$disableTab("tabRes")
fit_op<-reactiveValues(data=NULL)
run_calc<-reactiveValues(data=NULL)
en_but<-reactiveValues(enable=FALSE)
theme<<-theme(panel.background = element_blank(),panel.border=element_rect(fill=NA),panel.grid.major = element_blank(),panel.grid.minor = element_blank(),strip.background=element_blank(),axis.text.x=element_text(colour="black"),axis.text.y=element_text(colour="black"),axis.ticks=element_line(colour="black"),plot.margin=unit(c(1,1,1,1),"line"))
output$mult_files<-renderUI({
if (!input$multiple){
tagList(fileInput("prot_file","Choose protein file"),
fileInput("mrna_file","Choose transcript file"))
}
})
output$sing_file<-renderUI({
if (input$multiple){
tagList(textInput("protein_tab","Name of protein tab",value = "Proteines"),
textInput("rna_tab","Name of mRNA tab",value = "Transcrits"),
fileInput("data_file","Choose xls/xlsx file",accept=c(".xls",".xlsx")))
}
})
observeEvent(input$method_we, {
# updateTabsetPanel(session, "params", selected = input$method_we)
updateTabsetPanel(session,"formulas",selected = input$method_we)
})
observe({
if(!is.null(input$data_file)){
inFile<-input$data_file
list_data<-loadData(inFile$datapath,input$rna_tab,input$protein_tab,poids=F)
mrna_data<-list_data$mrna
prot_data<-list_data$prot
test_list<-list_data$parse
test_list<<-sample(test_list,3)
clean_mrna_data<<-mrna_data[,-which(is.na(as.numeric(as.character(colnames(mrna_data)))))]
clean_prot_data<<-prot_data[,-which(is.na(as.numeric(as.character(colnames(prot_data)))))]
}
})
observe({
if((!is.null(input$prot_file)) & (!is.null(input$mrna_file))){
protFile<-input$prot_file
mrnaFile<-input$mrna_file
prot_data<-loadData(protFile$datapath,"","",poids=F)
mrna_data<-loadData(mrnaFile$datapath,"","",poids=F)
clean_mrna_data<<-mrna_data[,-which(is.na(as.numeric(as.character(colnames(mrna_data)))))]
clean_prot_data<<-prot_data[,-which(is.na(as.numeric(as.character(colnames(prot_data)))))]
total_data<-merge(mrna_data,prot_data)
lista<-vector("list",nrow(mrna_data))
for (i in seq(1,nrow(total_data))){
lista[[i]]<-list("Protein_ID"=total_data[i,"Protein"],"Transcrit_ID"=total_data[i,"Transcrit"],"Transcrit_val"=as.matrix(total_data[i,3:29]),"Protein_val"=as.matrix(total_data[i,30:ncol(total_data)]),"DPA"=t)
}
# test_list<<-lista
test_list<<-sample(lista,3)
}
})
observeEvent(input$disp_distr,{
print("Plotting...")
output$distr_plot<-renderPlot({print(combineGraphs(clean_mrna_data,clean_prot_data,"",moyenne = T))})
print("Finished")
})
# parList<-reactiveValues()
# observe({
# for (i in reactiveValuesToList(input)){
# print(i)
# if (grepl("par[1-9]+_sig",i,perl = T)){
# newlist[[input[[i]]]]<-input[[i]]
# }
# }
# # })
parList<-reactive({
x<-reactiveValuesToList(input)
x_ind<-grep("par[1-9]+",names(x),perl = T)
newlist<-vector("list",length(x_ind))
names(newlist)<-names(x[x_ind])
for (el in names(newlist)){
newlist[[el]]<-as.numeric(as.character(input[[el]]))
}
names(newlist)<-gsub("_sig","",names(newlist))
newlist<-newlist[order(names(newlist))]
newlist
})
observeEvent(input$fit_op,{
browser()
print(parList())
inFile<-input$weight_data
days_kiwi<-rep(c(0,13,26,39,55,76,118,179,222), each = 3)
poids_data<-loadData(inFile$datapath,"","",poids=T)
print("Fitting...")
tryCatch({
coefs_poids<<-fitPoids_v2(poids_data[,1],poids_data[,2],input$method_we,parList())
},
warning = function(warn){
showNotification(paste0(warn), type = 'warning')
},
error = function(err){
showNotification(paste0(err), type = 'err')
})
print(coefs_poids$coefs)
val_mu<-mu(c(poids_data$DPA),input$method_we,coefs_poids$coefs,coefs_poids$formula,dpa_analyse = NULL)
data_mu<-data.frame("DPA"=c(poids_data$DPA),"Mu"=val_mu)
g_mu<<-ggplot(data_mu,aes(x=DPA,y=Mu))+geom_line()+theme+xlab("DPA")+ylab("Growth rate (days^-1)")
fit_op$state<-TRUE
print("Finished!!")
output$fitplot<-renderPlot({
req((fit_op$state)==TRUE,exists("coefs_poids"))
ggarrange(coefs_poids$graph,g_mu,ncol=2)
})
})
observeEvent(input$run_loop,{
if (input$fit_mrna!=""){
ksmin=as.numeric(as.character(input$ksmin))
score=0
cont<-0
poids_coef<<-coefs_poids$coefs
formula_poids<<-coefs_poids$formula
mess<-showNotification(paste("Running..."),duration = NULL,type = "message")
for (el in test_list){
tryCatch({
run_calc$run<-TRUE
cont<-cont+1
print(cont)
norm_data<-normaMean(el$Protein_val,el$Transcrit_val,ksmin)
fittedmrna<<-fit_testRNA(el$DPA,norm_data$mrna,"3_deg")
par_k<-solgss_Borne(el$DPA,as.vector(norm_data$prot),as.numeric(norm_data$ks),score)
par_k[["plot_fit_prot"]]<-plotFitProt(el$DPA,as.vector(norm_data$prot),par_k$prot_fit)
X<-matrice_sens(el$DPA,par_k[["solK"]][,1])
diff<-(par_k[["error"]][["errg"]][1]*norm(as.vector(norm_data$prot),"2"))^2
par_k[["corr_matrix"]]<-matrice_corr(X,length(norm_data$prot),diff)
if (!is.null(par_k)){
test_list[[cont]]$SOL<-par_k
# write.csv(test_list[[cont]][["SOL"]][["solK"]],paste("solK/",paste(test_list[[cont]][["Transcrit_ID"]],"_Sol_ks_kd.csv"),sep = ""))
}
},error=function(e){showNotification(paste0("Protein fitting not achieved for ",el$Transcrit_ID,sep=" "),type = "error",duration = NULL)})
}
valid_res<<-Filter(function(x) {length(x) > 5}, test_list)
print(valid_res[[1]])
mess<-showNotification(paste("Finished!!"),duration = NULL,type = "message")
en_but$enable<-TRUE
}
})
output$downFile<-downloadHandler(
filename = function(){
paste("results_KsKd-",Sys.Date(),".zip",sep="")
},
content = function(file){
owd <- setwd(tempdir())
on.exit(setwd(owd))
files <- NULL;
for (res in valid_res){
fileName<-paste(res[["Transcrit_ID"]],"_Sol_ks_kd.csv",sep = "")
write.csv(res[["SOL"]][["solK"]],fileName)
files<-c(files,fileName)
}
zip(zipfile = file,files = files)
if(file.exists(paste0(file, ".zip"))) {file.rename(paste0(file, ".zip"), file)}
},contentType = "application/zip"
)
observe({
if (en_but$enable){
enable("downFile")
}
})
}
I recently started using Shiny, so any help would be extremely appreciated. Thanks in advance!
Welcome to SO.
You've identified the problem yourself: "...are all named parN_sig". That means you don't have several inputs, you only have one. So you always get the value of (say) input$par2_sig from the first conditional panel regardless of which panel you're trying to access.
You have two options:
Provide unique names for every textInput. That will be a pain. Or...
Use modules. They take a bit of getting used to, but are worth the effrt in the end.
If you set the module up correctly, you'll be able to use the same module for each conditional panel, even though they have different numbers of textinputs.
See this page for help on creating your first module.

Changing background-image (or any two worded css properties) in rhandsontable

There are a lot of examples out there that illustrate how to use a custom rendering with rhandsontables, but unfortunately they all use a single worded css properties, like color or background.
How about two worded css-properties like background-color, background-image, font-size etc.? Replacing the hyphen(-) with a dot(.) doesn't work.
And using hyphens breaks the code and throws this error:
ReferenceError: invalid assignment left-hand side
In that code example, I want to assign a linear-gradient as background-image and change the color to red, if the value is "F".
The red color happens, but no gradient appears.
How can I fix that?
library(rhandsontable)
library(shiny)
DF = data.frame(val = 1:10, bool = TRUE, big = LETTERS[1:10], stringsAsFactors = FALSE)
ui <- fluidPage(
rHandsontableOutput("tbk")
)
server <- function(input, output) {
output$tbk <- renderRHandsontable({
rhandsontable(DF, width = 550, height = 300) %>%
hot_cols(renderer = "
function (instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (value == 'F') {
td.style.background-image = 'linear-gradient(to right, transparent, green)';
td.style.color = 'red';
} else if(value == 'J') {
td.style.background = 'lightgreen';
} else if(value == 'A' | value == 'x') {
td.style.background = 'lightblue'}
}")
})
}
shinyApp(ui, server)
If I tried few more minutes, I wouldn't have needed to ask the question. But anyway, it might be useful for other people which are strugling with that, as I didn't find too much reference on that.
The solution is to remove the hyphen and dot and make the first letter upper-case.
So background-image becomes backgroundImage, or font-size becomes fontSize!
library(rhandsontable)
library(shiny)
DF = data.frame(val = 1:10, bool = TRUE, big = LETTERS[1:10], stringsAsFactors = FALSE)
ui <- fluidPage(
rHandsontableOutput("tbk")
)
server <- function(input, output) {
output$tbk <- renderRHandsontable({
rhandsontable(DF, width = 550, height = 300) %>%
hot_cols(renderer = "
function (instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (value == 'F') {
td.style.backgroundImage = 'linear-gradient(to right, transparent, green)';
td.style.fontSize = '25px';
td.style.color = 'red';
} else if(value == 'J') {
td.style.background = 'lightgreen';
} else if(value == 'A' | value == 'x') {
td.style.background = 'lightblue'}
}")
})
}
shinyApp(ui, server)

Change cell background of rHandsontable with afterChange event on client side

I'd like to change the background color of a handsontable cell after it's been edited by the user on the client side. The handsontable is defined through a Shiny application; so this is really a question about how to define event hooks in rHandsontable in the Shiny application. The general use case that I'm trying to accomplish is: users edit cell data; the background color change to indicate that it's been changed and is pending saving to a database; the change is passed back to Shiny's observeEvent(); the change is sent to an external database and saved; the the rHandsontable is redrawn on the output with default background coloring that removes the color set be the change. The result is the flicker indicates that data has been saved. And if there's a database connection error or other issue, the color will persist, indicating the data is unsaved. I've been able to accomplish a working example, pasted below.
Specific question: the hook is currently implemented using hot_col(1,renderer=change_hook) however, this isn't about rendering the cell, just a way that allows adding the hook. I assume hot_table() is the correct function, but can it be used to register events? More generally, is there a more built-in way of accomplishing this?
change_hook <- "
function(instance, td, row, col, prop, value, cellProperties) {
Handsontable.hooks.add('afterChange', function(changes,source) {
if (source === 'edit' || source === 'undo' || source === 'autofill' || source === 'paste') {
row = changes[0][0];
col = changes[0][1];
oldval = changes[0][2];
newval = changes[0][3];
if (oldval !== newval) {
cell = this.getCell(row,col);
cell.style.background = 'pink';
}
}
},instance);
Handsontable.renderers.TextRenderer.apply(this, arguments);
}"
ui <- div(width=300,rHandsontableOutput(outputId="hTable"))
server <- function(input, output, session) {
df<-data.frame(col1=c("Hands","on","Table"),col2=c(100,200,300),stringsAsFactors = F)
hTable <- reactiveVal(df)
observeEvent(input$hTable, {
withProgress(message = "Saving changes to database...", value=0.5, {
Sys.sleep(1)
incProgress(1, detail = "done")
input_hTable <- hot_to_r(input$hTable)
hTable(input_hTable)
})
})
output$hTable <- renderRHandsontable({
rhandsontable(hTable(),stretchH="all",height=300) %>%
hot_col(1,renderer=change_hook)
})
}
shinyApp(ui, server)
After searching for a while, I have used the info from these pages (handsontable, htmlwidgets) to change the background color of an edited cell.
In the afterChange function, list of all changed cells will be kept. In the afterRender function the background color of those cells will be changed to yellow. In the afterLoadData function, the background color of changed cells will be reset to white and it will be emptied.
library(shiny)
library(rhandsontable)
change_hook <- "function(el,x) {
var hot = this.hot;
var cellChanges = [];
var changefn = function(changes,source) {
if (source === 'edit' || source === 'undo' || source === 'autofill' || source === 'paste') {
row = changes[0][0];
col = changes[0][1];
oldval = changes[0][2];
newval = changes[0][3];
if (oldval !== newval) {
var cell = hot.getCell(row, col);
cell.style.background = 'pink';
cellChanges.push({'rowid':row, 'colid':col});
}
}
}
var renderfn = function(isForced) {
for(i = 0; i < cellChanges.length; i++)
{
var rowIndex = cellChanges[i]['rowid'];
var columnIndex = cellChanges[i]['colid'];
var cell = hot.getCell(rowIndex, columnIndex);
cell.style.background = 'yellow';
}
}
var loadfn = function(initialLoad) {
for(i = 0; i < cellChanges.length; i++)
{
var rowIndex = cellChanges[i]['rowid'];
var columnIndex = cellChanges[i]['colid'];
var cell = hot.getCell(rowIndex, columnIndex);
cell.style.background = 'white';
}
cellChanges = []
}
hot.addHook('afterChange', changefn);
hot.addHook('afterRender', renderfn);
hot.addHook('afterLoadData', loadfn);
} "
ui <- div(actionButton(inputId = "reset_button",label = "Reset")
,rHandsontableOutput(outputId="hTable"))
server <- function(input, output, session) {
df<-data.frame(col1=c("Hands","on","Table"),col2=c(100,200,300),stringsAsFactors = F)
reset <- reactiveVal(0)
hTable <- reactiveVal(df)
output$hTable <- renderRHandsontable({
r = reset()
rht = rhandsontable(hTable(),reset=r,stretchH="all",height=300)%>%
hot_col('col1',readOnly=T)
reset(0)
htmlwidgets::onRender(rht,change_hook)
})
observeEvent(input$reset_button,
{
reset(1)
})
}
shinyApp(ui, server)
The code below changes the background color of a rhandsontable cell after it's been edited by a user.
library(shiny)
library(rhandsontable)
ui <- fluidPage(
titlePanel ('Give it a try and make changes!'),
mainPanel(rHandsontableOutput('table'))
)
server <- function(input, output, session) {
df <- data.frame(SYMBOLS = c('AAPL', 'ALRM', 'AMZN', 'BABA', 'CRM', 'CSCO',
'FB'),
NAMES = c('APPLE', 'ALARM.com', 'AMAZON', 'ALIBABA',
'SALESFORCE', 'CISCO', 'FACEBOOK'),
NOTE = c('sale', '', 'buy', '', '', '', 'watch'))
output$table <- renderRHandsontable(
rhandsontable(df))
rv <- reactiveValues(row = c(), col = c())
observeEvent(input$table$changes$changes, {
rv$row <- c(rv$row, input$table$changes$changes[[1]][[1]])
rv$col <- c(rv$col, input$table$changes$changes[[1]][[2]])
output$table <- renderRHandsontable({
rhandsontable(hot_to_r(input$table), row_highlight = rv$row,
col_highlight = rv$col) %>%
hot_cols(
renderer = "
function(instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (instance.params) {
hrows = instance.params.row_highlight;
hrows = hrows instanceof Array ? hrows : [hrows];
hcols = instance.params.col_highlight;
hcols = hcols instanceof Array ? hcols : [hcols];
}
for (let i = 0; i < hrows.length; i++) {
if (instance.params && hrows[i] == row && hcols[i] == col) {
td.style.background = 'pink';
}}
}"
)
})
})
}
shinyApp(ui, server)
Each time a cell is changed,
The row index of this cell (input$table$changes$changes[[1]][[1]]) and its column index (input$table$changes$changes[[1]][[2]]) are appended to the reactive variable rv$row and rv$col, respectively,
The rHandsontable is rendered again using the reactive variable (rv) that includes the indices of all the cells that have been changed so far, and finally,
A for loop in the javascript renderer goes through all the indices in rv one pair at a time against all cols and rows of the table. If there is a match in both row and col indices the background color of this cell is changed to pink.

Shiny: How to change the shape and/or size of the clicked point?

I would like to change the shape and size of the clicked point in the below plot. How to achieve it? For this toy plot, I have reduced the number of points from original 100k to 2k. So, the expected solution should be highly scalable and do not deviate from the original plot i.e., all the colors before and after the update of the click point should be the same.
library(shiny)
library(plotly)
df <- data.frame(X=runif(2000,0,2), Y=runif(2000,0,20),
Type=c(rep(c('Type1','Type2'),600),
rep(c('Type3','Type4'),400)),
Val=sample(LETTERS,2000,replace=TRUE))
# table(df$Type, df$Val)
ui <- fluidPage(
title = 'Select experiment',
sidebarLayout(
sidebarPanel(
checkboxGroupInput("SelType", "Select Types to plot:",
choices = unique(df$Type),
selected = NA)
),
mainPanel(
plotlyOutput("plot", width = "400px"),
verbatimTextOutput("click")
)
)
)
server <- function(input, output, session) {
output$plot <- renderPlotly({
if(length(input$SelType) != 0){
df <- subset(df, Type %in% input$SelType)
p <- ggplot(df, aes(X, Y, col = as.factor(Val))) +
geom_point()
}else{
p <- ggplot(df, aes(X, Y, col = as.factor(Val))) +
geom_point()
}
ggplotly(p) %>% layout(height = 800, width = 800)
})
output$click <- renderPrint({
d <- event_data("plotly_click")
if (is.null(d)) "Click events appear here (double-click to clear)"
else cat("Selected point associated with value: ", d$Val)
})
}
shinyApp(ui, server)
A related question has been asked here, but that approach of highlighting the point with a color does not work(when the number of levels of a variable is high, it is difficult to hard code a color which might be already present in the plot).
Plotly's restyle function won't help us here but we can still use the onclick event together with a little bit of JavaScript. The code has acceptable performance for 10,000 points.
We can get the point which was clicked on in JavaScript using:
var point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[data.points[0].curveNumber].getElementsByClassName('point')[data.points[0].pointNumber];
(scatterlayer is the layer where all the scatterplot elements are located,
scatter[n] is the n-th scatter plot and point[p] is the p-th point in it)
Now we just make this point a lot bigger (or whatever other shape/transformation you want):
point.setAttribute('d', 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z');
In order to get the possibility to revert everything, we store the unaltered info about the point together with the rest of the Plotly information:
var plotly_div = document.getElementsByClassName('plotly')[0];
plotly_div.backup = {curveNumber: data.points[0].curveNumber,
pointNumber: data.points[0].pointNumber,
d: point.attributes['d'].value
}
and later we can restore the point:
var old_point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[plotly_div.backup.curveNumber].getElementsByClassName('point')[plotly_div.backup.pointNumber]
old_point.setAttribute('d', plotly_div.backup.d);
Now we can add all the code to the plotly widget.
javascript <- "
function(el, x){
el.on('plotly_click', function(data) {
var point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[data.points[0].curveNumber].getElementsByClassName('point')[data.points[0].pointNumber];
var plotly_div = document.getElementsByClassName('plotly')[0];
if (plotly_div.backup !== undefined) {
var old_point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[plotly_div.backup.curveNumber].getElementsByClassName('point')[plotly_div.backup.pointNumber]
if (old_point !== undefined) {
old_point.setAttribute('d', plotly_div.backup.d);
}
}
plotly_div.backup = {curveNumber: data.points[0].curveNumber,
pointNumber: data.points[0].pointNumber,
d: point.attributes['d'].value,
style: point.attributes['style'].value
}
point.setAttribute('d', 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z');
});
}"
[...]
ggplotly(p) %>% onRender(javascript)
Alternatively you could make a new SVG element based on the location of the clicked point but in the color and shape you would like.
You can try it here without R/Shiny.
//create some random data
var data = [];
for (var i = 0; i < 10; i += 1) {
data.push({x: [],
y: [],
mode: 'markers',
type: 'scatter'});
for (var p = 0; p < 200; p += 1) {
data[i].x.push(Math.random());
data[i].y.push(Math.random());
}
}
//create the plot
var myDiv = document.getElementById('myDiv');
Plotly.newPlot(myDiv, data, layout = { hovermode:'closest'});
//add the same click event as the snippet above
myDiv.on('plotly_click', function(data) {
//let's check if some traces are hidden
var traces = document.getElementsByClassName('legend')[0].getElementsByClassName('traces');
var realCurveNumber = data.points[0].curveNumber;
for (var i = 0; i < data.points[0].curveNumber; i += 1) {
if (traces[i].style['opacity'] < 1) {
realCurveNumber -= 1
}
}
data.points[0].curveNumber = realCurveNumber;
var point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[data.points[0].curveNumber].getElementsByClassName('point')[data.points[0].pointNumber];
var plotly_div = document.getElementsByClassName('plotly')[0];
if (plotly_div.backup !== undefined) {
var old_point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[plotly_div.backup.curveNumber].getElementsByClassName('point')[plotly_div.backup.pointNumber]
if (old_point !== undefined) {
old_point.setAttribute('d', plotly_div.backup.d);
}
}
plotly_div.backup = {curveNumber: data.points[0].curveNumber,
pointNumber: data.points[0].pointNumber,
d: point.attributes['d'].value,
style: point.attributes['style'].value
}
point.setAttribute('d', 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z');
});
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv">

Resources