PickerInput label issue when inline is TRUE - r

I have a r shiny app in which users have numerous choices they need to pick prior to plotting. In the pickerInput, the label text goes behind the choices. The code below
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query", titleWidth=450),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1)) ## content issue if longer than 6 characters
#arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1)) ## space issue in pickerintput label
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2))
)
dat <- reactive(d)
myfun <- function(df, var1) {
# Rename column of interest
df <- df %>% rename(tempname := !!var1)
df <- df %>% mutate(newvar = tempname) # create newvar
df <- df %>% rename(UQ(var1) := tempname)
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
lapply(1:ngrp, function(i){
pickerInput(paste0("line.vars.",i),
label = paste(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1, `style` = "btn-primary"))
})
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
gives the following output:
How can I expand it so that the label Plac
ebo_NotDrug is fully visible to the left of the last dropdown in the image above? Secondly, if the labels happen to have space, then the display gets messy with labels placed in random places as shown in the output below:

update
I found an easy way to rewrite pickerInput in such a way that it takes a new ratio argument, where you can specify the ration of the label and the actual dropdown menu for the case that inline = TRUE. I think this is the most convenient approach. The downside is that you can only choose numbers adding up to 12, where in your case a split up 55% / 45% would suffice.
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
pickerInput2 <- function (inputId, label = NULL, choices, selected = NULL, multiple = FALSE,
options = list(), choicesOpt = NULL, width = NULL, inline = FALSE, ratio = c(2,10))
{
if (ratio[1] + ratio[2] != 12) stop("`ratio` has to add up 12.")
choices <- shinyWidgets:::choicesWithNames(choices)
selected <- restoreInput(id = inputId, default = selected)
if (!is.null(options) && length(options) > 0)
names(options) <- paste("data", names(options), sep = "-")
if (!is.null(width))
options <- c(options, list(`data-width` = width))
if (!is.null(width) && width %in% c("fit"))
width <- NULL
options <- lapply(options, function(x) {
if (identical(x, TRUE))
"true"
else if (identical(x, FALSE))
"false"
else x
})
maxOptGroup <- options[["data-max-options-group"]]
selectTag <- tag("select", shinyWidgets:::dropNulls(options))
selectTag <- tagAppendAttributes(tag = selectTag, id = inputId,
class = "selectpicker form-control")
selectTag <- tagAppendChildren(tag = selectTag, shinyWidgets:::pickerSelectOptions(choices,
selected, choicesOpt, maxOptGroup))
if (multiple)
selectTag$attribs$multiple <- "multiple"
divClass <- "form-group shiny-input-container"
labelClass <- "control-label"
if (inline) {
divClass <- paste(divClass, "form-horizontal")
selectTag <- tags$div(class = paste0("col-sm-", ratio[2]), selectTag)
labelClass <- paste(labelClass, paste0("col-sm-", ratio[1]))
}
pickerTag <- tags$div(class = divClass, style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), if (!is.null(label))
tags$label(class = labelClass, `for` = inputId, label),
selectTag)
shinyWidgets:::attachShinyWidgetsDep(pickerTag, "picker")
}
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query",
titleWidth=450
),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1)) ## content issue if longer than 6 characters
# arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1)) ## space issue in pickerintput label
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE) # people with R < 4.0 need this line to execute your code correctly
dat <- reactive(d)
myfun <- function(df, var1) { # I have simplified your function
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput2(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
tagList(lapply(1:ngrp, function(i){
pickerInput2(paste0("line.vars.",i),
label = paste(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
ratio = c(7,5),
options = list('max-options-group' = 1, `style` = "btn-primary"))
}))
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
old answer
I figured out, why your code wasn't working for some of us. You are using R >= 4.0 and therefor do not need to set stringsAsFactors = FALSE when defining your data d. Adding this attribute will help run your code on systems with R <= 4.0.
I guess I figured out whats going on. Your pickerInputs have a very narrow width 275px and you have long label names. You can either (i) set the width higher, or (ii) you need to change how pickerInput is splitting up the width between label and dropdown menue. Under the hood it relies on grid.less css classes .col-sm-10 for the dropdown menue and .col-sm-2 for its label. Here it attributes about 17% width to the label (in your case this is too small) and 83% to the dropdown menue (in your case this is too much). You could (A) rewrite the pickerInput function and define your own css classes and then add a custom css where those classes are defined with enough width to display properly (this is what I recommend). Or you can (B) overwrite the default values of gird.less.css with inline CSS adding !important. This is my approach below, just because it is the quickest way to fix this issue. However, it is not a good way, since other elements in your dashboard may rely on those css classes.
Note that I also streamlined myfun. It should still work as expected.
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query",
titleWidth=450
),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
# custom CSS to overwrite grid.less defaults
tags$head(
tags$style(HTML("
.col-sm-10 {
width: 45% !important;
}
.col-sm-2 {
width: 55% !important;
}
"))),
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1)) ## content issue if longer than 6 characters
# arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1)) ## space issue in pickerintput label
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE) # people with R < 4.0 need this line to execute your code correctly
dat <- reactive(d)
myfun <- function(df, var1) { # I have simplified your function
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
tagList(lapply(1:ngrp, function(i){
pickerInput(paste0("line.vars.",i),
label = paste(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1, `style` = "btn-primary"))
}))
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)

Related

How to copy multiple row and column headers in a rendered table when using DT table copy function?

A similar question was posted but never answered: r shiny problem with datatable to copy a table with table head (colspan)
When running the below reproducible code, I'd like the DT "copy" button to include ALL table column and row headers, when there are multiple headers. So far DT copy only copies one header.
I have the code to do this using an action button/observeEvent() outside of DT (not shown in below code), but if possible I'd instead like to use DT's native copy clipboard function (like in the code below) because of other benefits it offers including but not limited to simplicity.
The images at the bottom better explain.
Maybe it's not possible. But maybe it is!
Reproducible code:
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","X9")
)
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;}")),
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, {
datatable(
data = results(),
rownames = FALSE,
extensions = c("Buttons", "Select"), # for Copy button
selection = 'none', # for Copy button
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,
buttons = list(list(extend = "copy",text = 'Copy',exportOptions = list(modifier = list(selected = TRUE)))), # for Copy button
dom = 'Bft', # added 'B' for Copy button
lengthChange = T,
pagingType = "numbers",
autoWidth = T,
info = FALSE,
searching = FALSE)
) %>%formatStyle(c(1), `border-right` = "solid 1px")
})
}
shinyApp(ui, server)
Additional example:
Below is another, simpler example of trying to copy/paste all headers using DT, starting with the example used in post How to copy tableOutput to clipboard? (however adding the "sketch" container to datatable for a second column header to illustrate the copy/paste issue I'm trying to address):
library(shiny)
library(dplyr)
library(DT)
library(htmltools)
df <- mtcars
one <- function(.data, var, na = TRUE) {
return({
.data %>%
group_by(.data[[var]]) %>%
filter(!is.na(.data[[var]])) %>%
tally() %>%
mutate(`%` = 100*n/sum(n))
})
}
# ADDED SKETCH TO ORIGINAL EXAMPLE:
sketch = htmltools::withTags(table(
class = 'display',
thead(tr(th(colspan = 3, 'Table')),
tr(lapply(c('Variable','n','%'),th))
)
))
ui <- fluidPage(
selectInput("var", label = "Select Variable", choices = c("", names(df))),
DTOutput("valu", width = "15%")
)
server <- function(input, output) {
output$valu <- renderDT({
if(input$var != '') {
data <- df %>% one(input$var, na = input$check)
DT::datatable(data,
class = 'cell-border stripe',
rownames = FALSE,
extensions = c("Buttons", "Select"),
selection = 'none',
container = sketch, # ADDED SKETCH CONTAINER TO ORIGINAL EXAMPLE
options =
list(
select = TRUE,
dom = "Bt",
buttons = list(
list(
extend = "copy",
text = 'Copy'))
)) %>% formatStyle(
0,
target = "row",
fontWeight = styleEqual(1, "bold")
)
}
}, server = FALSE)
output$value <- renderTable({
if(input$var != '') {
data <- df %>% one(input$var, na = input$check)
return(data)
}
}, spacing = "xs", bordered = TRUE)
}
shinyApp(ui, server)
Hmm... for Copy I don't know yet. But you can export such a table to Excel and then copy from Excel. I agree this is not highly convenient, but I don't know another way. This requires some JS libraries:
tags$script(src = "xlsx.core.min.js"), # https://github.com/SheetJS/sheetjs/blob/master/dist/xlsx.core.min.js
tags$script(src = "FileSaver.min.js"), # https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js
tags$script(src = "tableexport.min.js"), # https://github.com/clarketm/TableExport/tree/master/dist
tags$link(rel = "stylesheet", href = "tableexport.min.css")
library(shiny)
library(DT)
library(shinyjs)
js_export <-
"
var $table = $('#DTtable').find('table');
var instance = $table.tableExport({
formats: ['xlsx'],
exportButtons: false,
filename: 'myTable',
sheetname: 'Sheet1'
});
var exportData0 = instance.getExportData();
var exportData = exportData0[Object.keys(exportData0)[0]]['xlsx'];
instance.export2file(exportData.data, exportData.mimeType, exportData.filename,
exportData.fileExtension, exportData.merges,
exportData.RTL, exportData.sheetname);
"
ui <- fluidPage(
useShinyjs(),
tags$head(
# put these files in the www subfolder
tags$script(src = "xlsx.core.min.js"),
tags$script(src = "FileSaver.min.js"),
tags$script(src = "tableexport.min.js")
),
DTOutput("DTtable"),
actionButton("export", "Export table", class = "btn-primary")
)
sketch <- htmltools::withTags(table(
class = "display",
thead(
tr(
th(rowspan = 2, "Species"),
th(colspan = 2, "Sepal"),
th(colspan = 2, "Petal")
),
tr(
lapply(rep(c("Length", "Width"), 2), th)
)
)
))
server <- function(input, output, session){
output[["DTtable"]] <- renderDT({
datatable(
head(iris, 6),
container = sketch, rownames = FALSE
) %>%
formatPercentage("Sepal.Length") %>%
formatCurrency("Sepal.Width")
})
observeEvent(input[["export"]], {
runjs(js_export)
})
}
shinyApp(ui, server)
Note that it also takes the formatting into account, but I'm wondering why there are some dates :-/

R shiny: How to copy data derived from plotly_selection events into a data frame/table and update each time by pressing an actionButton?

I'm putting together a shiny app to play around with some athlete GPS data. Essentially, I'm looking to structure my script so that each time the user selects an area of interest on the plotly plot and the "Add" actionButton is clicked, the table below will add the calculated Start_time, Time_at_peak, Max_velocity, Time_to_peak, and Distance_to_peak values.
The issue can be seen in the GIF below: - Once the area of interest is selected and the "Add" button clicked, the first values seem correct. However, when the user selects a second area of interest to add to the table, it overwrites the initial entry and will keep overwriting each time a new selection is made. This is seemingly because because the code is inside the observeEvent(event_data("plotly_selected"), which, confusingly, it needs to be in order to calculate the variables of interest.
I'm currently a little stumped and can't seem to find any relevant information. As such, any guidance would be greatly appreciated!
Here is a we transfer link to some test data that can be uploaded to the app: https://wetransfer.com/downloads/5a7c5da5a7647bdbe133eb3fdac79c6b20211119052848/afe3e5
library(shiny)
library(readr)
library(DT)
library(dplyr)
library(plotly)
library(lubridate)
library(tidyr)
library(purrr)
library(htmlwidgets)
library(shinydashboard)
library(dashboardthemes)
library(shinyjs)
x_df <- data.frame(Start_time = character(1), Time_at_peak = character(1), Max_velocity = integer(1),
Time_to_peak = integer(1), Distance_to_peak = integer(1))
x_df$Start_time <- as.character("0:00:00.0")
x_df$Time_at_peak <- as.character("0:00:00.0")
x_df$Max_velocity <- as.integer(0)
x_df$Time_to_peak <- as.integer(0)
x_df$Distance_to_peak <- as.integer(0)
runApp(shinyApp(
ui=(fluidPage(
titlePanel("Event to Table"),
mainPanel(
fileInput(
inputId = "filedata",
label = "Upload data file (.csv)",
accept = c(".csv")),
plotlyOutput('myPlot'),br(),br(),br(),br(),
DTOutput("testing"), br(), br(),
fluidRow(
valueBoxOutput("starttime", width = 2),
valueBoxOutput("endtime", width = 2),
valueBoxOutput("maxvelocity", width = 2),
valueBoxOutput("timediff", width = 3),
valueBoxOutput("distance", width = 3)
),
useShinyjs(),
fluidRow(
div(style = "text-align:center", actionButton("Add", "Add Data to Table"),
downloadButton("export", "Export Table as .CSV"))), br(),
DTOutput(outputId = "table")))
),
server = (function(input, output, session) {
data<-reactive({
req(input$filedata)
read.csv(input$filedata$datapath, header = TRUE)%>%
rename(Velocity = 'Speed..m.s.',
Player = 'Player.Display.Name',
Latitude = 'Lat',
Longtitude = 'Lon',
AccelImpulse = 'Instantaneous.Acceleration.Impulse',
HeartRate = 'Heart.Rate..bpm.')
})
observe({
thedata<-data()
updateSelectInput(session, 'y', choices = names(data))
})
output$myPlot = renderPlotly({
plot_ly(data = data(), x = ~Time, y = ~Velocity, height = 450,
marker =list(color = 'rgb(132,179,202)', size = 0.1),
line = list(color = 'rgb(132,179,202)', size = 0.1, width = 0.9),
type = 'scatter', mode = 'markers+lines') %>%
layout(dragmode = "select",
showlegend = F,
title = list(text = 'Velocity Trace', font = list(size = 20)),
xaxis = list(title = list(text = "", standoff = 0), nticks = 10),
yaxis = list(title = list(text = "Velocity (m/s)"), nticks = 5, gridcolor = "#46505a"),
font = list(color = 'black'),
margin = list(t = 70))
})
observeEvent(event_data("plotly_selected"), {
event.data <- event_data("plotly_selected")
if (max(event.data$y) < 1.5) {
maxvel <- (max(event.data$y))
maxpos <- match(maxvel, event.data$y)
}
else {
filter1 <- filter (event.data, event.data$y > 1.5)
maxvel <- (max(filter1$y))
maxpos <- match(maxvel, event.data$y)
}
zero_val <- function(x) x == 0
zero_index <- event.data$y[1:maxpos] %>% detect_index(zero_val, .dir = "backward")
if (zero_index==0) {starttime <- event.data$x[1]}
else {starttime <- event.data$x[zero_index]}
endvel <- which.max(event.data$y)
endtime <- event.data$x[endvel]
timediff <- paste(today(), endtime) %>% as_datetime() - paste(today(), starttime) %>% as_datetime()
sprint <- as_tibble(event.data$y[zero_index:endvel])
ms <- as_tibble(rep(0.1, count(sprint)))
time_vel <- cbind(ms, sprint)
distance <- sum(time_vel[1]*time_vel[2])
sprintselect <- as_tibble(cbind(Start_time = starttime,
Time_at_peak = endtime,
Max_velocity = round(maxvel, 2),
Time_to_peak = round(timediff, 1),
Distance_to_peak = round(distance, 1)))
values <- reactiveValues()
values$df <- x_df
addData <- observe({
if(input$Add > 0) {
newLine <- isolate(data.frame(Start_time = sprintselect$Start_time, Time_at_peak = sprintselect$Time_at_peak,
Max_velocity = sprintselect$Max_velocity,
Time_to_peak = sprintselect$Time_to_peak,
Distance_to_peak = sprintselect$Distance_to_peak,
stringsAsFactors= FALSE))
values$df <- isolate(rbind(values$df, newLine))}
})
output$testing <- renderDataTable({values$df})
})
})
))
I've managed to figure it out and thought I'd post an answer rather than delete the question - just in case someone out there is looking to do a similar thing and they are unsure how to do it.
Firstly, I removed the pre-populated table x_df from the beginning - it was no longer required.
Although I thought the code needed to sit inside the observeEvent(event_data("plotly_selected") to function correctly, it did not - thankfully, because that was at the root of the issue. Instead, I used observeEvent(input$Add, { (which is the correct code to use as opposed to if(input$Add > 0)) to anchor the event to the click of the Add button.
The values <- reactiveValues() was placed outside the observeEvent() and an IF statement was used to either add the data to the values$df data frame on it's own if it was the first selection, or bind it to the existing saved data.
Here's the new code and a GIF demonstrating.
library(shiny)
library(readr)
library(DT)
library(dplyr)
library(plotly)
library(lubridate)
library(tidyr)
library(purrr)
library(htmlwidgets)
library(shinydashboard)
library(dashboardthemes)
library(shinyjs)
runApp(shinyApp(
ui=(fluidPage(
titlePanel("Event to Table"),
mainPanel(
fileInput(
inputId = "filedata",
label = "Upload data file (.csv)",
accept = c(".csv")),
plotlyOutput('myPlot'),br(),br(),br(),br(),
DTOutput("testing"), br(), br(),
fluidRow(
valueBoxOutput("starttime", width = 2),
valueBoxOutput("endtime", width = 2),
valueBoxOutput("maxvelocity", width = 2),
valueBoxOutput("timediff", width = 3),
valueBoxOutput("distance", width = 3)
),
useShinyjs(),
fluidRow(
div(style = "text-align:center", actionButton("Add", "Add Data to Table"),
downloadButton("export", "Export Table as .CSV"))), br(),
DTOutput(outputId = "table")))
),
server = (function(input, output, session) {
values <- reactiveValues(df_data = NULL)
data<-reactive({
req(input$filedata)
read.csv(input$filedata$datapath, header = TRUE)%>%
rename(Velocity = 'Speed..m.s.',
Player = 'Player.Display.Name',
Latitude = 'Lat',
Longtitude = 'Lon',
AccelImpulse = 'Instantaneous.Acceleration.Impulse',
HeartRate = 'Heart.Rate..bpm.')
})
observe({
thedata<-data()
updateSelectInput(session, 'y', choices = names(data))
})
output$myPlot = renderPlotly({
plot_ly(data = data(), x = ~Time, y = ~Velocity, height = 450,
marker =list(color = 'rgb(132,179,202)', size = 0.1),
line = list(color = 'rgb(132,179,202)', size = 0.1, width = 0.9),
type = 'scatter', mode = 'markers+lines') %>%
layout(dragmode = "select",
showlegend = F,
title = list(text = 'Velocity Trace', font = list(size = 20)),
xaxis = list(title = list(text = "", standoff = 0), nticks = 10),
yaxis = list(title = list(text = "Velocity (m/s)"), nticks = 5, gridcolor = "#46505a"),
font = list(color = 'black'),
margin = list(t = 70))
})
observeEvent(input$Add, {
event.data <- event_data("plotly_selected")
if (max(event.data$y) < 1.5) {
maxvel <- (max(event.data$y))
maxpos <- match(maxvel, event.data$y)
}
else {
filter1 <- filter (event.data, event.data$y > 1.5)
maxvel <- (max(filter1$y))
maxpos <- match(maxvel, event.data$y)
}
zero_val <- function(x) x == 0
zero_index <- event.data$y[1:maxpos] %>% detect_index(zero_val, .dir = "backward")
if (zero_index==0) {starttime <- event.data$x[1]}
else {starttime <- event.data$x[zero_index]}
endvel <- which.max(event.data$y)
endtime <- event.data$x[endvel]
timediff <- paste(today(), endtime) %>% as_datetime() - paste(today(), starttime) %>% as_datetime()
sprint <- as_tibble(event.data$y[zero_index:endvel])
ms <- as_tibble(rep(0.1, count(sprint)))
time_vel <- cbind(ms, sprint)
distance <- sum(time_vel[1]*time_vel[2])
sprintselect <- as_tibble(cbind(Start_time = starttime,
Time_at_peak = endtime,
Max_velocity = round(maxvel, 2),
Time_to_peak = round(timediff, 1),
Distance_to_peak = round(distance, 1)))
newLine <- isolate(data.frame(Start_time = sprintselect$Start_time, Time_at_peak = sprintselect$Time_at_peak,
Max_velocity = sprintselect$Max_velocity,
Time_to_peak = sprintselect$Time_to_peak,
Distance_to_peak = sprintselect$Distance_to_peak,
stringsAsFactors= FALSE))
if (is.null(values$df)){
values$df <- newLine}
else {
values$df <- isolate(rbind(values$df, newLine))}
output$testing <- renderDataTable({values$df})
})
})
))

how to avoid flickering while refreshing valueboxes in Shiny dashboard

I use shiny modules to update a large number of value boxes.
The annoying part is the value boxes donot seem to scale above 10 or 20 as their updating is causing annoying flickers.
Even those boxes whose values are not changing on the next invalidation, flicker. Ideally if the value is not changing the box should not refresh.
A representative shiny app using shiny modules is presented to replicate the problem.
When the value of N is 4 or 5 the number of boxes are small and the updates happen instantaneously. As you increase N to 10 it gets noticeable and at N = 20 the flicker is unbearable.
### ui.R
## reprex ui.r
suppressPackageStartupMessages(library(shiny))
suppressPackageStartupMessages(library(magrittr))
suppressPackageStartupMessages(library(shinydashboard))
suppressPackageStartupMessages(library(shinydashboardPlus))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(shinyjs))
ui <- dashboardPage(
header = dashboardHeader(title = "Reprex"),
sidebar = dashboardSidebar(
sidebarMenu(id = "sidebar",
menuItem(text = "Fuel prediction",tabName = "LIVE",icon = icon("tachometer-alt"))
)
), # end of sidebarMenu
body = dashboardBody(id="body",useShinyjs(),
tabItems(
tabItem(tabName = "LIVE",h1("FUEL DISPENSATION"),
fluidRow(id = "parameters",
column(width = 2,h3("STATION")),
column(width = 2,h4("TIME UPDT")),
column(width = 2,h4("TANK LEVEL")),
column(width = 2,h4("DISPENSED")),
column(width = 2,h4("REFUELLED"))
),
uiOutput("st1"),
uiOutput("st2"),
uiOutput("st3"),
uiOutput("st4"),
uiOutput("st5"),
uiOutput("st6"),
uiOutput("st7"),
uiOutput("st8"),
uiOutput("st9"),
uiOutput("st10"),
uiOutput("st11"),
uiOutput("st12"),
uiOutput("st13"),
uiOutput("st14"),
uiOutput("st15"),
uiOutput("st16"),
uiOutput("st17"),
uiOutput("st18"),
uiOutput("st19"),
uiOutput("st20")
)
)
) # End of body
) # end of dashboard page
And this is the server.R:
## reprex server.R
suppressPackageStartupMessages(library(shiny))
suppressPackageStartupMessages(library(shinydashboard))
suppressPackageStartupMessages(library(data.table))
source("modules.R")
shinyServer(function(input, output,session) {
seqno <- reactiveVal(5)
timer <- reactiveTimer(3000)
observeEvent(timer(),{
seqno((seqno() + 1))
for(i in seq_len(N)){ ## the for loop generates all the output assignment statements using shiny module.
genrVB(i = i,output = output,s = seqno())
}
})
# This is just to stop the app when session ends. Ignore for the purposes of this reprex.
session$onSessionEnded(function() {
print("Session ended")
stopApp()
})
})
And this is the modules.R
### Shiny module reprex
library(shiny)
library(purrr)
library(maps)
# take N cities and N data.tables randomly generated to serve our input data for the shiny app
N <- 4
cities <- world.cities %>% as.data.table() %>% .$name %>% sample(N)
### Generate N simulated data.tables for the N cities.
### Notice the values of the column 2,3,4 donot change every minute.
simdata <- purrr::map(seq_len(N),
~data.table(ts = seq.POSIXt(Sys.time(),by = 60,length.out = 100),
fuel = rep(c(5000:5004),each = 2),
out = rep(c(100,110),each = 25),
fill = rep(c(100,200),each = 10)
))
fuelrowUI <- function(id,label = "Site X",n = 1){
ns <- NS(id)
fluidRow(id = ns("siteid"),
column(2,h3(cities[n])),
valueBoxOutput(ns("upd"),width = 2),
valueBoxOutput(ns("tank"),width = 2),
valueBoxOutput(ns("out"),width = 2),
valueBoxOutput(ns("fill"),width = 2)
)
}
fuelrowServer <- function(id,datarow=1,n = 1){
moduleServer(id,
function(input,output,session){
output$upd <- renderValueBox(vbtime(n,k = datarow))
output$tank <- renderValueBox(vblevel(n,k = datarow))
output$out <- renderValueBox(vbout(n,k = datarow))
output$fill <- renderValueBox(vbin(n,k = datarow))
})
}
# Function to loop through the output$.. in server.R using the two shiny modules
genrVB <- function(i,s,output = output){
stn <- paste0("st",i)
output[[stn]] <- renderUI(fuelrowUI(stn,label = "DUMMY",n = i))
fuelrowServer(stn,datarow = s,n = i)
}
##### Value box helper functions ##########
vblevel <- function(n = 1,k=1){
val <- simdata[[n]][k,round(fuel,0)]
valueBox(value = paste(val,"L"),
subtitle = tags$h4(cities[n]),
color = case_when(
val < 1000 ~ "red",
val >= 1000 ~ "green"
))
}
vbout <- function(n = 1,k=1){
val = simdata[[n]][k,out]
valueBox(value = paste(val,"L"),
subtitle = tags$h4(cities[n]),
color = case_when(
val < 100 ~ "aqua",
val >= 100 ~ "purple"
))
}
vbin <- function(n = 1,k=1){
val = simdata[[n]][k,fill]
valueBox(value = paste(val,"L"),
subtitle = tags$h4(cities[n]),
color = case_when(
val < 100 ~ "teal",
val >= 100 ~ "olive"
))
}
# Time Value box
vbtime <- function(n = 1,k = 1){
time <-simdata[[n]][k,ts]
timestr <- format(time,"%H:%M")
valueBox(value = timestr,
subtitle = "Last Updated",color = "aqua")
}
Please load the three code sections in three files: ui.R, server.R and modules.R.
Note: In the modules.R the first line has a line N <- 4. Please set it to 20 to see the annoying flicker.
If you only want to stop the flashing while recalculating all you'll have to do is adding
tags$style(".recalculating { opacity: inherit !important; }")
to your UI - taken from here.
Still I'd encourage you to simplify your app for better performance.
Here is an example for the approach I mentioned in the comments:
library(shiny)
library(shinydashboard)
library(data.table)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
tags$style(".recalculating { opacity: inherit !important; }"),
fluidPage(
sliderInput(
inputId = "nBoxesRows",
label = "rows of Boxes",
min = 1L,
max = 100L,
value = 20L
),
uiOutput("myValueBoxes")
)
)
)
server <- function(input, output, session) {
DT <- reactive({
invalidateLater(1000)
data.table(replicate(4, round(runif(input$nBoxesRows), digits = 2)))
})
output$myValueBoxes <- renderUI({
longDT <- melt(DT(), measure.vars = names(DT()))
longDT[, subtitle := paste0(variable, "_", seq_len(.N)), by = variable]
tagList(mapply(valueBox, subtitle = longDT$subtitle, value = longDT$value, MoreArgs = list(width = 3), SIMPLIFY = FALSE))
})
}
shinyApp(ui, server)

How to ensure that in pickerInput choices at least one item is selected in each group

I have not been able to find an answer to this issue on SO. The code below
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query", titleWidth=450),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
tags$head(
tags$style(HTML("
.col-sm-10 {
width: 45% !important;
}
.col-sm-2 {
width: 55% !important;
}
"))),
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
#arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1))
arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1))
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE)
dat <- reactive(d)
myfun <- function(df, var1) {
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
lapply(1:ngrp, function(i){
pickerInput(paste0("line.vars.",i),
label = paste0(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1,
`style` = "btn-primary"))
})
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
gives the following output:
It gives the option for the user to pick the order, color and shape for each of the available group value in their data. However, when users accidentally click on their selected choice again, it deselects that choice. In the image above, I have deselected order, color and shape for Drug A. It should not allow a user to deselect any group. My expectation is that if color has a choice of red and blue, they should be able to pick either color but not none.
#Stephane Laurent's answer works for the first element. I am still able to deselect order, color and shape from the second element onwards in the treatment example above. Please see the output below:
output2
Try this. The JavaScript code prevents to deselect an option if it is the unique selected option.
js <- "
$(document).ready(function(){
$('#somevalue').on('show.bs.select', function(){
$('a[role=option]').on('click', function(e){
var selections = $('#somevalue').val();
if(selections.length === 1 && $(this).hasClass('selected')){
e.stopImmediatePropagation();
};
});
}).on('hide.bs.select', function(){
$('a[role=option]').off('click');
});
});"
ui <- fluidPage(
tags$head(tags$script(HTML(js))),
pickerInput(
inputId = "somevalue",
label = "A label",
choices = c("a", "b"),
multiple = TRUE
),
verbatimTextOutput("value")
)
server <- function(input, output) {
output$value <- renderPrint(input$somevalue)
}
shinyApp(ui, server)
EDIT
I see that you are using pickerInput with groups of options. Here is the JS code for this situation:
js <- "
$(document).ready(function(){
$('#groups').on('show.bs.select', function(){
$('a[role=option]').on('click', function(e){
var classes = $(this).parent().attr('class').split(/\\s+/);
if(classes.length === 2){
var group = classes[0];
var selections = $('.' + group + '.selected');
if(selections.length === 1){
e.stopImmediatePropagation();
}
}
});
}).on('hide.bs.select', function(){
$('a[role=option]').off('click');
});
});"
ui <- fluidPage(
tags$head(tags$script(HTML(js))),
pickerInput(
inputId = "groups",
label = "Select one from each group below:",
choices = list(
Group1 = c("1", "2", "3", "4"),
Group2 = c("A", "B", "C", "D")
),
multiple = TRUE
),
verbatimTextOutput(outputId = "res_grp")
)
server <- function(input, output) {
output$res_grp <- renderPrint(input$groups)
}
shinyApp(ui, server)
EDIT
For your case:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(dplyr)
js <- "
$(document).ready(function(){
$('#shapetype').on('show.bs.select', 'select[id^=linevars]', function(){
$('a[role=option]').on('click', function(e){
var classes = $(this).parent().attr('class').split(/\\s+/);
if(classes.length === 2){
var group = classes[0];
var selections = $('.' + group + '.selected');
if(selections.length === 1){
e.stopImmediatePropagation();
}
}
});
}).on('hide.bs.select', function(){
$('a[role=option]').off('click');
});
});"
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query", titleWidth=450),
dashboardSidebar( width = 300,
sidebarMenu(id = "tabs")
),
dashboardBody(
tags$head(
tags$style(HTML("
.col-sm-10 {
width: 45% !important;
}
.col-sm-2 {
width: 55% !important;
}
")),
tags$script(HTML(js))
),
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
#arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1))
arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1))
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE)
dat <- reactive(d)
myfun <- function(df, var1) {
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
lapply(1:ngrp, function(i){
pickerInput(paste0("linevars",i),
label = paste0(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1,
`style` = "btn-primary"))
})
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
You are basically looking for a minOptions equivalent to maxOptions. Unfortunately, the underlying plugin of pickerInput (bootstrap-select) does not have this feature and it is likely that such a feature will not be implmented (see here and here for similar feature requests on GitHub).
One option would be build your own workaround via shiny. You would need to check on the server side, whether the user has chosen one option in each group, and if not, display an error message, maybe with validate/need. I attach a simple example below.
Another option would be to drop the pickerInput and use radioGroupButtons, but this could look a bit messy, given that you have several inputs.
Example: check via server side and validate / need
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query", titleWidth=450),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
tags$head(
tags$style(HTML("
.col-sm-10 {
width: 45% !important;
}
.col-sm-2 {
width: 55% !important;
}
"))),
textOutput("text"),
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
#arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1))
arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1))
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE)
dat <- reactive(d)
myfun <- function(df, var1) {
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
lapply(1:ngrp, function(i){
pickerInput(paste0("line.vars.",i),
label = paste0(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1,
`style` = "btn-primary"))
})
}
})
}
, ignoreInit = TRUE)
output$text <- renderText({
validate(
need(length(input$line.vars.1) == 4,
"Please choose one option in every category to proceed.")
)
paste(input$line.vars.1, collapse = ", ")
})
}
shinyApp(ui, server)
#TimTeaFan, that is a great idea. That was my line of thinking before seeing #Stephane Laurent's excellent javascript answer. Stephane's answer works for one group, but not multi-dimensional groups. At least I have not been able to make it work for my app. I have modified #TimTeaFan's answer slightly and adapted it to all pickerInputs. I render it with renderUI. In your code output$text is modified as shown below. Obviously, textOutput should be changed to uiOutput in ui.
output$text <- renderUI({
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
lapply(1:ngrp(), function(i){
q1 <- paste0("line.vars.",i)
uivar <- expr('$'(input,!!q1))
req(uivar)
fval <- eval_tidy(uivar)
if (length(fval) < 4) {
tagList(
p("ERROR: Please choose one option in every category to proceed.", style = "color:red")
)
}else{ return(NULL) }
})
}
})
I will go with this for now, until I can get a better solution.
update: #StephaneLaurent has updated the javascript to solve this problem and another problem listed here. I will be using both these answers as I am not sure that I will be able to use js in all my pickerInputs based on how my ShinyApp has been setup. Many many many thanks to both #StephaneLaurent and #TimTeaFan.
Update2: The final answer I used to solve this issue is javascript from #Stephane Laurent. For completeness I have attached it below.
js <- "
$(document).ready(function(){
$('div[id^=shapetype]').on('show.bs.select', 'select[id^=linevars]', function(){
$('a[role=option]').on('click', function(e){
var classes = $(this).parent().attr('class').split(/\\s+/);
if(classes.length === 2){
var group = classes[0];
var $ul = $(this).parent().parent();
var selections = $ul.find('.' + group + '.selected');
if(selections.length === 1){
e.stopImmediatePropagation();
}
}else if(classes.length === 1){
var group = classes[0];
var $ul = $(this).parent().parent();
var groupname = $ul.find('li.dropdown-header.' + group + '>span').text();
if(groupname === 'Group'){
e.stopImmediatePropagation();
}
}
});
}).on('hide.bs.select', 'select[id^=linevars]', function(){
$('a[role=option]').off('click');
});
});"
The only caveat is that all the output names should start with shapetype, and variable IDs should start with linevars or adjust the above code appropriately. All ten plots in my shiny app work as expected.

ShinyApp errors: selectInput, data-subsetting

I am creating shiny app. My goal is to visualize some data slices depending on the input.I am quite happy with the result.
However, my app has a few bugs while the app is loading. Before ploting the graph and visualizing inputs it shows some errors on screen (you can lauch the app and see the problem).
I believe, the first problem is with data filtering. I can't figure out how to deal with it and what is the problem. May I need to use other method or maybe other package? (see the output$Brand).
Error in grep(pattern, levels(vector)) : invalid 'pattern' argument
The second error comes when I am creating selectInput. I'd like to visualize all the brands of the specific category in one plot and to have an option to filter data by brand. However, my method is not working well. Any suggestions? (see the output$Brand).
Error in if (input$Brand == "All") { : argument is of length zero
Also, I enclose the code, which you can generate.
May you have any more suggestions how to simplify the code?
Thanks for the help!
library(shiny)
library(shinydashboard)
library(data.table)
library(ggplot2)
library(grid)
library(scales)
library(ggthemes)
# Header -----------------------------------------------------------
header <- dashboardHeader(title="Dashboard")
# Sidebar --------------------------------------------------------------
sm <- sidebarMenu(
menuItem(
text="Graph1",
tabName="Graph1",
icon=icon("home")
)
)
sidebar <- dashboardSidebar(sm)
# Body --------------------------------------------------
body <- dashboardBody(
# Layout --------------------------------------------
tabItems(
tabItem(
tabName="Graph1",
fluidPage(
fluidRow(
box(
title = "Inputs", status = "warning", width = 2, solidHeader = TRUE,
uiOutput("Year"),
uiOutput("Category"),
uiOutput("Brand"),
sliderInput("Finalas.Range", "Months:",
min = 1, max = 12, value = c(1,12))
),
box(
title = "Season", width = 10, status = "info", solidHeader = TRUE,
plotOutput("Graph1")
)
)
)
)
)
)
# Setup Shiny app UI components -------------------------------------------
ui <- dashboardPage(header, sidebar, body, skin="black")
# Setup Shiny app back-end components -------------------------------------
server <- function(input, output) {
# Generate data --------------------------------------
set.seed(1992)
n=99
Year <- sample(2013:2015, n, replace = TRUE, prob = NULL)
Month <- sample(1:12, n, replace = TRUE, prob = NULL)
Category <- sample(c("Car", "Bus", "Bike"), n, replace = TRUE, prob = NULL)
Brand <- sample("Brand", n, replace = TRUE, prob = NULL)
Brand <- paste0(Brand, sample(1:14, n, replace = TRUE, prob = NULL))
USD <- abs(rnorm(n))*100
df <- data.frame(Year, Month, Category, Brand, USD)
# Inputs --------------------------------------
output$Year <- renderUI({
selectInput("Year",
"Year:",
c(unique(as.character(df$Year))), selected = "2015")
})
output$Category <- renderUI({
selectInput("Category", "Choose category:",
choices = c("Car","Bus", "Bike" ))
})
output$Brand <- renderUI({
df2 <- (data.table(df))[like(df$Category,input$Category)]
selectInput("Brand",
"Brand:",
c("All", unique(as.character(df2$Brand))))
})
# Plot --------------------------------
output$Graph1 <- renderPlot({
df <- data.table(df)
if (input$Brand == "All") {
df <- df[like(df$Year, input$Year)]
df <- df[like(df$Category,input$Category)]
ggplot(df, aes(x=factor(Month,levels=1:12), y=USD, fill=Brand))+
geom_bar(stat='identity')+
scale_x_discrete('Month', breaks=factor(1:12), drop=FALSE)+
scale_fill_gdocs(guide = guide_legend(title = "Brand"))
} else {
df <- df[like(df$Year, input$Year)]
df <- df[like(df$Category,input$Category)]
df <- df[which(df$Brand == input$Brand),]
validate(
need(sum(df$USD)>0, paste(input$Brand, "was inactive in Year:",input$Year))
)
ggplot(df, aes(x=factor(Month,levels=1:12), y=USD, fill=Brand))+
geom_bar(stat='identity')+
scale_x_discrete('Month', breaks=factor(1:12), drop=FALSE)
}
})
# -----------------------------------------------------------------------------
}
# Render Shiny app --------------------------------------------------------
shinyApp(ui, server)
The following should eliminate these errors: for #1 the function like in datatable gives out the error so I changed it to %in% instead. and for #2 you have a null as a default so take care of that with an if statement
rm(list = ls())
library(shiny)
library(shinydashboard)
library(data.table)
library(ggplot2)
library(grid)
library(scales)
library(ggthemes)
# Header -----------------------------------------------------------
header <- dashboardHeader(title="Dashboard")
# Sidebar --------------------------------------------------------------
sm <- sidebarMenu(
menuItem(
text="Graph1",
tabName="Graph1",
icon=icon("home")
)
)
sidebar <- dashboardSidebar(sm)
# Body --------------------------------------------------
body <- dashboardBody(
# Layout --------------------------------------------
tabItems(
tabItem(
tabName="Graph1",
fluidPage(
fluidRow(
box(
title = "Inputs", status = "warning", width = 2, solidHeader = TRUE,
uiOutput("Year"),
uiOutput("Category"),
uiOutput("Brand"),
sliderInput("Finalas.Range", "Months:",
min = 1, max = 12, value = c(1,12))
),
box(
title = "Season", width = 10, status = "info", solidHeader = TRUE,
plotOutput("Graph1")
)
)
)
)
)
)
# Setup Shiny app UI components -------------------------------------------
ui <- dashboardPage(header, sidebar, body, skin="black")
# Setup Shiny app back-end components -------------------------------------
server <- function(input, output) {
# Generate data --------------------------------------
set.seed(1992)
n=99
Year <- sample(2013:2015, n, replace = TRUE, prob = NULL)
Month <- sample(1:12, n, replace = TRUE, prob = NULL)
Category <- sample(c("Car", "Bus", "Bike"), n, replace = TRUE, prob = NULL)
Brand <- sample("Brand", n, replace = TRUE, prob = NULL)
Brand <- paste0(Brand, sample(1:14, n, replace = TRUE, prob = NULL))
USD <- abs(rnorm(n))*100
df <- data.frame(Year, Month, Category, Brand, USD)
# Inputs --------------------------------------
output$Year <- renderUI({
selectInput("Year",
"Year:",
c(unique(as.character(df$Year))), selected = "2015")
})
output$Category <- renderUI({
selectInput("Category", "Choose category:",
choices = c("Car","Bus", "Bike" ))
})
output$Brand <- renderUI({
# first error
#df2 <- (data.table(df))[like(df$Category,input$Category)]
df2 <- df[df$Category %in% input$Category,]
selectInput("Brand",
"Brand:",
c("All", unique(as.character(df2$Brand))))
})
# Plot --------------------------------
output$Graph1 <- renderPlot({
df <- data.table(df)
if(is.null(input$Brand) || is.na(input$Brand)){return()}
else if (input$Brand == "All") {
df <- df[like(df$Year, input$Year)]
df <- df[like(df$Category,input$Category)]
ggplot(df, aes(x=factor(Month,levels=1:12), y=USD, fill=Brand))+
geom_bar(stat='identity')+
scale_x_discrete('Month', breaks=factor(1:12), drop=FALSE)+
scale_fill_gdocs(guide = guide_legend(title = "Brand"))
} else {
df <- df[like(df$Year, input$Year)]
df <- df[like(df$Category,input$Category)]
df <- df[which(df$Brand == input$Brand),]
validate(
need(sum(df$USD)>0, paste(input$Brand, "was inactive in Year:",input$Year))
)
ggplot(df, aes(x=factor(Month,levels=1:12), y=USD, fill=Brand))+
geom_bar(stat='identity')+
scale_x_discrete('Month', breaks=factor(1:12), drop=FALSE)
}
})
# -----------------------------------------------------------------------------
}
# Render Shiny app --------------------------------------------------------
shinyApp(ui, server)

Resources