let's say a generic code of validate function of shiny R :
## Only run examples in interactive R sessions
if (interactive()) {
ui <- fluidPage(
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
selectizeInput('in2', 'Select a state', choices = state.name),
plotOutput('plot')
)
server <- function(input, output) {
output$plot <- renderPlot({
validate(
need(input$in1, 'Check at least one letter!'),
need(input$in2 != '', 'Please choose a state.')
)
plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
})
}
shinyApp(ui, server)
}
Could you think it's possible to replace mesage error in character ('Check at least one letter!') by an image (png, jpeg format) ? I tried with renderImage function or , with the help of package imager, and don't manage to do it.
many thanks to you,
echoes
Thanks you for your quick answer, it could be a great solution for my shiny application, and it works !
in :
tags$style(HTML("
.shiny-output-error-validation {
background-image: URL(https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.svg);
background-repeat: no-repeat;
}
"))
It would be nice to display a random or personalized image, who depends on user action. shinipsum package offer a random_image function, which could be quoted, something as :
tags$style(HTML("
.shiny-output-error-validation {
background-image: plotOutput("random_image");
background-repeat: no-repeat;
}
"))
and in server part :
output$random_image <- renderImage({
random_image()
},deleteFile=FALSE)
Do you think it's possible to use output of ui part in tags$style or tagsList parts ?
many thanks, echoes
A random ggplot:
library(shiny)
library(shinipsum)
ui <- fluidPage(
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
plotOutput('plot')
)
server <- function(input, output) {
output$plot <- renderPlot({
test <- need(input$in1, "")
if(!is.null(test)) {
random_ggplot()
} else {
plot(1:10, main = input$in1)
}
})
}
shinyApp(ui, server)
A random image:
library(shiny)
library(shinipsum)
library(imager)
ui <- fluidPage(
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
plotOutput('plot')
)
server <- function(input, output) {
output$plot <- renderPlot({
test <- need(input$in1, "")
if(!is.null(test)) {
img <- load.image(random_image()$src)
plot(img)
} else {
plot(1:10, main = input$in1)
}
})
}
shinyApp(ui, server)
Ase they say in this tutorial you can add css style to validation messages. Just target with the selector .shiny-output-error-validation.
In this case I rendernder stackoverflow logo as background to the validation html element.
library(shiny)
ui <- fluidPage(
tags$head(
tags$style(HTML("
.shiny-output-error-validation {
background-image: URL(https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.svg);
background-repeat: no-repeat;
}
"))
),
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
selectizeInput('in2', 'Select a state', choices = state.name),
plotOutput('plot')
)
server <- function(input, output) {
output$plot <- renderPlot({
validate(
need(input$in1, " ")
)
plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
})
}
shinyApp(ui, server)
I manage to do something with css tags in two parts, part one here :
css_content1 <- "
.shiny-output-error-validation {
background-image:"
writeLines(text = css_content1, con = "styles1.css")
css1 <- readLines(con = "styles1.css") %>% paste(collapse = "\n")
part two :
css_content2 <- "
background-repeat: no-repeat;
}
"
writeLines(text = css_content2, con = "styles2.css")
css2 <- readLines(con = "styles2.css") %>% paste(collapse = "\n")
I modify server file as below, using renderUi function :
server <- function(input, output) {
output$plot <- renderPlot({
validate(
need(input$in1, " ")
)
plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
})
output$css_style <- renderUI({
tags$head( tags$style(HTML(paste0(css1,"URL(test",sample(1:4,1),".png);",css2,collapse="\n"))
))
})
output$css_style_text <- renderText({
HTML(paste0(css1,"test",sample(1:4,1),".png",");",css2,collapse="\n"))
})
}
with test1.png, test2.png etc ... in a www directory.
I add in UI file part :
ui <- fluidPage(
uiOutput("css_style"),
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
selectizeInput('in2', 'Select a state', choices = state.name),
plotOutput('plot')
)
I didn't manage to use a random_image function from shinipsum package.
thanks for your help
Related
I have this shiny app. The main aim is to upload excel sheet with data and plot some graphs in tabs. User is able to select a sheet to make the graph. The seet will render to observe the selected data. This works well.
But I am struggling to manipulate with input data to make the graph.
I tried to use reactive value named data and then make the graph from that. I am quite new with shiny apps.
library(shiny)
library(readxl)
library(dplyr)
library(tidyverse)
library(lubridate)
ui <- fluidPage(
titlePanel("OTD project update"),
sidebarPanel(
fileInput('file1', 'Insert File', accept = c(".xlsx")),
textInput('file1sheet','Name of Sheet (Case-Sensitive)')),
mainPanel(tabsetPanel(
type = "tabs",
tabPanel("Data", tableOutput("value")),
tabPanel("OTD", plotOutput("OTD"))
)
)
)
server <- function(input, output) {
sheets_name <- reactive({
if (!is.null(input$file1)) {
return(excel_sheets(path = input$file1$datapath))
} else {
return(NULL)
}
})
output$value <- renderTable({
if (!is.null(input$file1) &&
(input$file1sheet %in% sheets_name())) {
return(read_excel(input$file1$datapath,
sheet = input$file1sheet))
} else {
return(NULL)
}
})
data <- reactive({
if (!is.null(input$file1) &&
(input$file1sheet %in% sheets_name())) {
return(read_excel(input$datapath,
sheet = input$file1sheet))
} else {
return(NULL)
}
})
}
shinyApp(ui, server)
It may be better to use the sheet names in radio buttons to pick instead of typing it. Also, there was a typo. Try this
library(shiny)
library(readxl)
library(dplyr)
library(tidyverse)
library(lubridate)
library(DT)
ui <- fluidPage(
titlePanel("OTD project update"),
sidebarPanel(
fileInput('file1', 'Insert File', accept = c(".xlsx")),
#textInput('file1sheet','Name of Sheet (Case-Sensitive)')
uiOutput("sheet")
),
mainPanel(tabsetPanel(
type = "tabs",
tabPanel("Data", DTOutput("table")),
tabPanel("OTD", plotOutput("plot"))
)
)
)
server <- function(input, output) {
sheets_name <- reactive({
if (!is.null(input$file1)) {
return(excel_sheets(path = input$file1$datapath))
} else {
return(NULL)
}
})
data <- reactive({
req(sheets_name())
if (!is.null(input$file1)) {
return(read_excel(input$file1$datapath, sheet = input$mysheet))
} else {
return(NULL)
}
})
output$sheet <- renderUI({
req(sheets_name())
radioButtons("mysheet", "Select a Sheet", choices = sheets_name())
})
output$table <- renderDT(data())
output$plot <- renderPlot({plot(cars)})
}
shinyApp(ui, server)
Goal:
In my app, the user is supposed to upload a .csv file. If a file other than .csv is uploaded, a warning message (showFeedbackWarning()) should appear around the the fileInput() widget. If the user corrects his input and uploads a .csv file, the message should disappear again (hideFeedback()).
All this already works in the app. But now I would like to change the colour of the progress bar in fileInput() to another colour for example red (like in this example). However, the warning message should still appear in its default colour orange.
Problem:
shinyFeedback overwrites my custom CSS and the colour of the progress bar is not changed. I can of course use !important, but then the colour of the bar in the warning message also turns red and I don't want this.
Do any of you know how to solve this problem?
Reprex:
library(shiny)
library(shinyFeedback)
ui <- fluidPage(
useShinyFeedback(),
fileInput(
inputId = "upload",
label = "Upload file:",
accept = ".csv"
),
tags$style(".progress-bar {
background-color: red;
}"),
verbatimTextOutput("text")
)
server <- function(input, output, session) {
data_in <- reactive({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
if (ext == "csv") {
hideFeedback("upload")
read.delim(
input$upload$datapath,
sep = ";"
)
} else {
showFeedbackWarning(
inputId = "upload"
)
}
})
output$text <- renderPrint({
class(data_in())
})
}
shinyApp(ui, server)
We can use shinyjs to change the color dynamically:
library(shiny)
library(shinyjs)
library(shinyFeedback)
ui <- fluidPage(
useShinyFeedback(),
useShinyjs(),
fileInput(
inputId = "upload",
label = "Upload file:",
accept = ".csv"
),
# tags$style(".progress-bar {
# background-color: blue;
# }"),
verbatimTextOutput("text")
)
server <- function(input, output, session) {
data_in <- reactive({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
if (ext == "csv") {
hideFeedback("upload")
runjs('document.querySelector("#upload_progress > div").style.setProperty("background-color", "green", "important");')
read.delim(
input$upload$datapath,
sep = ";"
)
} else {
showFeedbackWarning(
inputId = "upload",
color = "red"
)
}
})
output$text <- renderPrint({
class(data_in())
})
}
shinyApp(ui, server)
I am trying to build a shiny app where with a click of a button 6 codes gets executed. since the processing time is 5-10 mins, to keep the users aware of the process, I want to have a textbox/verbatim box that will change basis which code is run.
"error in evaluating the argument 'x' in selecting a method for function 'head': object 'x' not found"
Edit : Have changed the code. However the first instance of text is not displayed "data loading".
TIA.
library(shinyjs)
library(shiny)
ui <- fluidPage(
titlePanel("Testing Textupdate Multiple Times"),
sidebarLayout(
sidebarPanel(
useShinyjs(),
actionButton("button1","Click"),
textInput("text1", label = "", value = ""),
dataTableOutput("table1")
),
mainPanel(
)
))
server = function(input, output,session) {
x<-data.frame()
observeEvent(input$button1, {
updateTextInput(session,"text1",value = "Data Loading")
withProgress(message = 'Data Loading',
detail = 'This may take a while...', value = 0, {
for (i in 1:10) {
incProgress(1/10)
Sys.sleep(0.25)
}
})
x<-mtcars
updateTextInput(session,"text1",value = "Data Loaded")})
output$table1 <- renderDataTable({
head(x)})
}
shinyApp(ui, server)
The updateTextInput will not be implemented until the end of the observeEvent, so the "Data Loading" message will not be seen. However, you can try a sendCustomMessage and add javascript to show the text instead. Here is a working example that uses verbatimTextOutput instead of a textInput. Please let me know if this works for you - I hope it is helpful.
library(shiny)
ui <- fluidPage(
tags$script('
Shiny.addCustomMessageHandler("status_text", function(text) {
document.getElementById("text1").innerHTML = text;
})
'),
titlePanel("Testing Textupdate Multiple Times"),
sidebarLayout(
sidebarPanel(
useShinyjs(),
actionButton("button1", "Click"),
verbatimTextOutput("text1")
),
mainPanel(
dataTableOutput("table1")
)
)
)
server = function(input, output, session) {
x <- mtcars
observeEvent(input$button1, {
session$sendCustomMessage("status_text", "Data loading...")
withProgress(message = 'Data Loading',
detail = 'This may take a while...', value = 0, {
for (i in 1:10) {
incProgress(1/10)
Sys.sleep(0.25)
}
})
session$sendCustomMessage("status_text", "Data loaded")
})
output$table1 <- renderDataTable({
head(x)
})
}
shinyApp(ui, server)
If you want the verbatimTextOutput to be present initially (but without text) you can add this to server:
output$text1 <- renderText({
" "
})
I wonder if it is possible to change the color of text in validate() in a shiny app since there is not an id like for example when you use textOutput().
library(shiny)
ui <- fluidPage(
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
selectizeInput('in2', 'Select a state', choices = state.name),
plotOutput('plot')
)
server <- function(input, output) {
output$plot <- renderPlot({
validate(
need(input$in1, 'Check at least one letter!'),
need(input$in2 != '', 'Please choose a state.')
)
plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
})
}
shinyApp(ui, server)
You can provide a CSS style as described here: https://shiny.rstudio.com/articles/validation.html
library(shiny)
ui <- fluidPage(
tags$head(
tags$style(HTML("
.shiny-output-error-validation {
color: #ff0000;
font-weight: bold;
}
"))
),
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
selectizeInput('in2', 'Select a state', choices = state.name),
plotOutput('plot')
)
server <- function(input, output) {
output$plot <- renderPlot({
validate(
need(input$in1, 'Check at least one letter!'),
need(input$in2 != '', 'Please choose a state.')
)
plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
})
}
shinyApp(ui, server)
Result
Solution for creating links between tabs a have found here R shiny build links between tabs is really nice, but it's not working with DT package (for me..).
Can anybody tell me, what am I doing wrong in my example code using DT library in compare to the solution without DT package?
library(shiny)
library(DT)
server <- function(input, output) {
output$iris_type <- DT::renderDataTable({
datatable(data.frame(Species=paste0("<a href='#filtered_data'>", unique(iris$Species), "</a>")),
escape = FALSE,
options = list(initComplete = JS(
'function(table) {
table.on("click.dt", "tr", function() {
Shiny.onInputChange("rows", table.row( this ).index());
tabs = $(".tabbable .nav.nav-tabs li a");
$(tabs[1]).click();
});
}')))
})
output$filtered_data <- DT::renderDataTable({
if(is.null(input$rows)){
iris
}else{
iris[iris$Species %in% unique(iris$Species)[as.integer(input$rows)+1], ]
}
})
}
ui <- shinyUI(fluidPage(
mainPanel(
tabsetPanel(
tabPanel("Iris Type", DT::dataTableOutput("iris_type")),
tabPanel("Filtered Data", DT::dataTableOutput("filtered_data"))
)
)
))
shinyApp(ui = ui, server = server)
You could try the code below. I changed the function switching the tabs to the callback (which has table as an argument) and in your output$filtered_data, replaced iris by datable(iris) since you are rendering with DT::renderDataTable
library(shiny)
library(DT)
server <- function(input, output) {
output$iris_type <- DT::renderDataTable({
datatable(data.frame(Species=paste0("<a href='#filtered_data'>", unique(iris$Species), "</a>")),
escape = FALSE,
callback = JS(
'table.on("click.dt", "tr", function() {
tabs = $(".tabbable .nav.nav-tabs li a");
$(tabs[1]).click();})'))
})
output$filtered_data <- DT::renderDataTable({
selected <- input$iris_type_rows_selected
if(is.null(selected)){
datatable(iris)
} else {
datatable(iris[iris$Species %in% unique(iris$Species)[selected], ])
}
})
}
ui <- shinyUI(fluidPage(
mainPanel(
tabsetPanel(
tabPanel("Iris Type", DT::dataTableOutput("iris_type")),
tabPanel("Filtered Data", DT::dataTableOutput("filtered_data"))
)
)
))
shinyApp(ui = ui, server = server)
Please note this requires DT >= 0.0.62.
In the end I used a little hack with onclick event on . Which way do you think is more clear? (NicE's or this one?)
library(shiny)
library(DT)
server <- function(input, output) {
output$iris_type <- DT::renderDataTable({
datatable(data.frame(Species=paste0("<a href='#filtered_data'",
"alt='",unique(iris$Species),"'",
"onclick=\"",
"tabs = $('.tabbable .nav.nav-tabs li');",
"tabs.each(function() {",
"$(this).removeClass('active')",
"});",
"$(tabs[1]).addClass('active');",
"tabsContents = $('.tabbable .tab-content .tab-pane');",
"tabsContents.each(function() {",
"$(this).removeClass('active')",
"});",
"$(tabsContents[1]).addClass('active');",
"$('#filtered_data').trigger('change').trigger('shown');",
"Shiny.onInputChange('species', getAttribute('alt'));",
"\">",
unique(iris$Species),
"</a>")),
escape = FALSE)
})
output$filtered_data <- DT::renderDataTable({
if(is.null(input$species)){
datatable(iris)
}else{
datatable(iris[iris$Species %in% input$species, ])
}
})
}
ui <- shinyUI(fluidPage(
mainPanel(
tabsetPanel(
tabPanel("Iris Type", DT::dataTableOutput("iris_type")),
tabPanel("Filtered Data", DT::dataTableOutput("filtered_data"))
)
)
))
shinyApp(ui = ui, server = server)