So far I successfully made a Shiny dashboard that makes a user select from the first dropdown and a second dropdown dependent on the first. Functionality-wise everything works well except for the UI part marked in star (*) in my script. What I want is the following two:
Make the second dropdown appear right next to the first and not below which can be accomplished by:
div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2"))
Make the second dropdown have a loading spinner once the first dropdown is selected:
uiOutput("var_dropdown2") %>% withSpinner(type = 6)
Both 1 and 2 work but when I try to combine these two it won't:
div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2") %>% withSpinner(type = 6))
How can I achieve the both?
Here is the fully working script:
#### Packages
library(dplyr)
library(shiny)
library(shinyWidgets) # pickerInput()
library(shinycssloaders) # withSpinner()
#### UI
ui <- fluidPage(
mainPanel(
### Dropdown 1
div(
style="display:inline-block; vertical-align:top; margin-left: 20px",
pickerInput(
"var_dropdown1",
"Dropdown 1:",
choices = c("Sepal", "Petal"),
multiple = T
)
),
### Dropdown 2
uiOutput("var_dropdown2") %>% withSpinner(type = 6) # Works without div()
# div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2")) # Works without withSpinner()
# div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2") %>% withSpinner(type = 6)) # Doesn't work if using both div() and withSpinner() *
)
)
#### Server
server <- function(input, output, session) {
### Dropdown 2
dropdown2 <- reactive({
if (length(input$var_dropdown1) == 1) {
if (input$var_dropdown1 == "Sepal") {
c("Sepal.Length", "Sepal.Width")
}
else {
c("Petal.Length", "Petal.Width")
}
}
else {
c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
}
})
## renderUI
output$var_dropdown2 <- renderUI({
req(input$var_dropdown1)
pickerInput(
"var_dropdown2",
"Dropdown 2:",
choices = dropdown2(),
multiple = T
)
})
}
####
shinyApp(ui=ui, server=server)
One possible solution is to use cloumn layout:
ui <- fluidPage(
mainPanel(
### Dropdown 1
fluidRow(
column(
6,
pickerInput(
"var_dropdown1",
"Dropdown 1:",
choices = c("Sepal", "Petal"),
multiple = T
)
),
column(
6,
uiOutput("var_dropdown2") %>% withSpinner(type = 6)
)
)
)
)
However, we can see the loader is not positioned in the right location as the dropdown. This is hard to achieve with shinycssloaders package. To do so, we can use some advanced loaders.
#### Packages
library(dplyr)
library(shiny)
library(shinyWidgets) # pickerInput()
library(spsComps)
#### UI
ui <- fluidPage(
mainPanel(
### Dropdown 1
div(
style="display:inline-block; vertical-align:top; margin-left: 20px",
pickerInput(
"var_dropdown1",
"Dropdown 1:",
choices = c("Sepal", "Petal"),
multiple = T
)
),
### Dropdown 2
div(
id = "dropdown2_container",
style="display:inline-block; vertical-align:top; margin-left: 20px; min-height: 50px; min-width: 250px",
uiOutput("var_dropdown2")
)
)
)
#### Server
server <- function(input, output, session) {
### Dropdown 2
dropdown2 <- reactive({
if (length(input$var_dropdown1) == 1) {
if (input$var_dropdown1 == "Sepal") {
c("Sepal.Length", "Sepal.Width")
}
else {
c("Petal.Length", "Petal.Width")
}
}
else {
c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
}
})
## renderUI
dd_loader <- spsComps::addLoader$new(target_selector = "dropdown2_container")
output$var_dropdown2 <- renderUI({
dd_loader$show()
on.exit({dd_loader$hide()})
req(input$var_dropdown1)
Sys.sleep(1)
pickerInput(
"var_dropdown2",
"Dropdown 2:",
choices = dropdown2(),
multiple = T
)
})
}
####
shinyApp(ui=ui, server=server)
Attaching loaders from spsComps takes a few more steps, but it allow you to have complete control of the loader, when to display, when to hide, change loader type, position, size and more dynamically, etc.
In this case, the loader is attached to the outside div instead of the uiOutput directly, because the height and width is subject to change when new UI is rendered inside var_dropdown2. The outside container is not usually modified by renderUI.
Related
I am creating an R Shiny app, where a user can upload their own csv, and the app generates a synthetic dataset. I am trying to add an additional function where the user can select a column they wish to anonymise to make it a shareable file. The rest of the app is currently working, however when I select the column which I want to anonomise, when I press update, the datatable isn't refreshing.
Any help or insight here would be greatly appreciated! I've tried and tried to solve it, but am stuck.
A shortened/reproducible version of the code app is below
`
library(shiny)
library(synthpop)
library(DT)
library(tidyverse)
library(data.table)
library(rsconnect)
library(fontawesome)
library(DT)
library(htmltools)
library(shinythemes)
library(RcppRoll)
library(grid)
library(reactable)
library(shinydashboard)
library(shinydashboardPlus)
library(formattable)
library(dashboardthemes)
library(deidentifyr)
library(anonymizer)
library(digest)
# User interface
ui <- fluidPage(theme = shinytheme("cosmo"),
navbarPage("Synthetic data",
# Upload data tab
tabPanel("Upload data",
sidebarLayout(
sidebarPanel(width = 3,
h4(strong("Upload original data")),
br(),
fileInput(inputId = "datafile", label = "1. Upload a csv file then press 'Update' below.
Note, the larger your dataset, the longer it will take to load", multiple = FALSE, placeholder = "No file selected",
accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv")),
actionButton(inputId = "update", label = "Update", icon = icon("fas fa-sync")),
br(),
br(),
h5(strong("2. To view and download the synthesised dataset, click on the 'Synthetic data' tab at the top"))),
mainPanel(dataTableOutput("table"), style = "font-size:80%"))),
# Synthetic data download
tabPanel("Synthetic data",
sidebarLayout(
sidebarPanel(width = 3,
h4(strong("Anonomise data?")),
br(),
uiOutput(outputId = "anon"),
br(),
actionButton(inputId = "update2", label = "Update", icon = icon("fas fa-sync"))),
mainPanel(dataTableOutput("synth"), style = "font-size:75%"))),
))
# Server function
server <- function(input, output, session) {
options(shiny.maxRequestSize=20*1024^2)
contentsrea <- reactive({
inFile <- input$datafile
if(is.null(inFile))
return(NULL)
dataset <- read_csv(inFile$datapath)
})
observeEvent(input$update, {
if(!is.null(input$datafile)){
original <- read_csv(input$datafile$datapath)
my.seed <- 17914709
synResult <- syn(original, seed = my.seed, maxfaclevels = 150)
# Synthetic data
df <- synResult$syn
# Add 'SYNTH' to column headings
colnames(df) <- paste("SYNTH", colnames(df), sep="_")
# Variable dropdown to anonomise data
output$anon <- renderUI({
selectInput(inputId = "anon",
label="1. Select the variable you'd like to anonomise (i.e., athlete name). If not necessary, leave as blank",
choices = c(" ", colnames(df)),
selected = NULL)
})
## Original
output$table <- DT::renderDataTable(original,
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: left; color: black; font-size:140%',
h3(strong("Original data"))), server = FALSE, rownames=FALSE,
options = list(bFilter=0, iDisplayLength=18,
columnDefs = list(list(className = 'dt-center', targets = '_all')),
dom = 'frtip'))
# Synthetic dataset
output$synth <- DT::renderDataTable(df,
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: left; color: black; font-size:140%',
h3(strong("Simulated synthetic data"))),
server = FALSE, rownames=FALSE, extensions = c("Buttons"),
options = list(iDisplayLength=18, bFilter=0,
columnDefs = list(list(className = 'dt-center', targets = '_all')),
dom = 'Bfrtip'))
}})
}
# Synthetic dataset with update for anon
observeEvent(input$update2, {
if(!is.null(input$datafile)){
output$synth <- DT::renderDataTable({
# Anonomise
df$ID <- sapply(input$anon, digest, algo = "crc32")
datatable(df,
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: left; color: black; font-size:140%',
h3(strong("Simulated synthetic data"))),
server = FALSE, rownames=FALSE, extensions = c("Buttons"),
options = list(iDisplayLength=18, bFilter=0,
columnDefs = list(list(className = 'dt-center', targets = '_all')),
dom = 'Bfrtip'))
})
}})
# Run the app ----
shinyApp(ui, server)
`
I have tried removing the update button ans using a reactive table, as well as other anonomise functions. I am completely stuck.
I try to observe an event in shiny. If I define a radio manually, it will be observed correctly - output: print(paste0("SELECT * FROM daten;")). I want to avoid writing several tenths of radio buttons in ui.r. Thus I wrote a loop in the server part.
But the same observeEvent() does not react on my "loop-listed" radio buttons which where correctly built in shiny app. I have no idea why.
I wrote a minimal example:
library(shiny)
shinyApp(
ui = fluidPage(
####### manually set radio #######
print("This radio 'pd1' will be observed:"),
radioButtons(inputId = "pd1", label = "value:", choices = c("?", "0", "1")),
br(), br(),
####### versus looped set set radio #######
uiOutput("scrlst"),
),
server = function(input, output) {
tablscr <- data.frame("1","question")
###################### observeEvent
##### "counter" for several items (in this case just 1 item)
rv <- reactiveValues(counter = 0)
lapply(1:dim(tablscr)[1], function(i) {
isolate({qnum <- paste0('pd', rv$counter <- rv$counter + 1)})
observeEvent(input[[qnum]], {print(paste0("SELECT * FROM daten;"))})
})
### output for tenths of items in one loop (in this case just 1 item)
output$scrlst <- renderUI({
tagList(
scr <- list(),
for (sq in 1:dim(tablscr)[1]){
scr[[sq]] = list(sq,
print("This radio 'pd1' will not be observed:"),
radioButtons(inputId = "pd1", label = "value:", choices = c("?", "0", "1")),
br(),
br()
)
},
return(scr),
)
})
}
)
Your tagList containing a loop and a return statement sounds weird. Moreover you have a duplicated id pd1. Here is a working code:
library(shiny)
shinyApp(
ui = fluidPage(
uiOutput("scrlst")
),
server = function(input, output) {
tablscr <- data.frame(c("1","2"), c("question", "hello"))
lapply(1:nrow(tablscr), function(i) {
qnum <- paste0('pd', i)
observeEvent(input[[qnum]], {print(paste0("SELECT * FROM daten;"))})
})
output$scrlst <- renderUI({
do.call(tagList, lapply(1:nrow(tablscr), function(i){
tagList(
radioButtons(paste0("pd", i), label = "value:", choices = c("?", "0", "1")),
br(), br()
)
}))
})
}
)
Is there a way to list the objects(dataframes, functions, args(funtions), inputs, outputs of a shiny app. In the below app, there are inputs likes actionButton(), numericInput() etc, and outputs like renderText(). Is there a way to list these along with there ID'd (eg action Button has "goButton") and so on.
Also, there is a function declared here called "asd" with arguments (3,4). Can we also list these information ? please guide
ui.R
source("fun.R")
pageWithSidebar(
headerPanel("actionButton test"),
sidebarPanel(
numericInput("n", "N:", min = 0, max = 100, value = 50),
br(),
actionButton("goButton", "Go!"),
p("Click the button to update the value displayed in the main panel.")
),
mainPanel(
verbatimTextOutput("nText")
)
)
server.R
function(input, output) {
# builds a reactive expression that only invalidates
# when the value of input$goButton becomes out of date
# (i.e., when the button is pressed)
ntext <- eventReactive(input$goButton, {
input$n
})
output$nText <- renderText({
asd(3,4)
})
}
fun.R
asd <- function(a,b)
{
c <- a + b
return(c)
}
You can use action buttons, show, hide from shinyjs and verbatimtextoutput to have show code for specific output in your app. Like this:
library(shiny)
library(shinyjs)
asd <- function(a,b)
{
c <- a + b
return(c)
}
ui <- pageWithSidebar(
headerPanel("actionButton test"),
sidebarPanel(
numericInput("n", "N:", min = 0, max = 100, value = 50),
br(),
actionButton("goButton", "Go!"),
p("Click the button to update the value displayed in the main panel.")
),
mainPanel(
useShinyjs(),
tags$head(tags$style("
#nText {
color: #333;
background-color: #f5f5f5;
border-radius: 4px;');
}
pre {
font-size: 90%;
color: #c7254e;
border-radius: 4px;
}")),
verbatimTextOutput("nText"),
actionButton("showtextcode", "Show Code"),
verbatimTextOutput("textoutputcode"),
)
)
server <- function(input, output) {
# builds a reactive expression that only invalidates
# when the value of input$goButton becomes out of date
# (i.e., when the button is pressed)
shinyjs::hide("textoutputcode")
ntext <- eventReactive(input$goButton, {
input$n
})
output$nText <- renderText({
asd(2,3)
})
output$textoutputcode <- renderText({
"output$nText <- renderText({
asd(2,3)
})"
})
observeEvent(input$showtextcode, {
if(input$showtextcode %% 2) {
shinyjs::show("textoutputcode")
} else {
shinyjs::hide("textoutputcode")
}
})
}
shinyApp(ui, server)
Result:
If you want to show all of your code for the app, you can create a separate tab that shows the full code placing everything in verbatimTextOutput. However, just because you can doesn't mean you should. For instance, if your shiny app spans 5000 lines then this is a bit silly!
It would be much better to simply include a github link to the source code of the app, if users are interested they can simply follow the link. Remember shiny is designed to share interactive output and not full source code.
The problem i am having is that i have to create an psychological online experiment. I won't get into all the details, however, one aspect involves displaying an image or plot for a set number of seconds. I have started learning Shiny and I am two hours into it, (as i am relatively good with R, and it seemed a good step) however i cannot find a way to do this.
I know showNotification has a "duration" attribute, but I found nothing for showPlot, or Image.
Is there a way to do this, or should I quit Shiny while I haven't lost much time?
Greetings,
George
EDIT: wow. more or less is what I want. Thank you all!
Since details were asked (and thank you for that), the experiment requires only one image. there will be two experimental conditions, and both will have one plot to show (that plot i intend to draw with ggplot2) and contains a number of 30-50 points for about 4 seconds. The subject will have to evaluate the number of points (since he can't count them) and insert it in a field, and the subject will have to then evaluate certain evaluation parameters. data that will be reused to compare him to others. that is about it!
Since it is now obviously possible to do this, i will begin with a serious approach to Shiny. Thank you all!
You will be able to do this in shiny.
You can use a reactiveTimer for example
if (interactive()) {
ui <- fluidPage(
plotOutput("plot")
)
server <- function(input, output) {
# every 2 seconds.
autoHide <- reactiveTimer(2000)
display <- TRUE
observe({
# re-execute this reactive expression every time the
# timer fires.
autoHide()
display <- if_else(display,FALSE,TRUE)
})
output$plot <- renderPlot({
autoHide()
if(display){
hist(rnorm(200))
} else {
hist(rnorm(100))
}
})
}
shinyApp(ui, server)
}
Here is a solution using the slick.js JavaScript library. You have to download the zip file here and extract it in the www subfolder of your app.
library(shiny)
# images to be displayed ####
## these images are in the www subfolder
images <- c("img1.JPG", "img2.JPG", "img3.JPG", "img4.JPG", "img5.JPG")
# ui #####
ui <- fluidPage(
tags$head(
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick-theme.css"),
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick.css"),
tags$script(type="text/javascript",
src="slick-1.8.1/slick/slick.js"),
tags$script(HTML(
"$(document).ready(function(){
$('#images').slick({
arrows: true,
dots: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 500,
infinite: false
}).on('afterChange', function(e, slick, cur){
if(cur === slick.$slides.length-1){
slick.setOption('autoplay', false, true);
}
});
});")),
tags$style(HTML(
"#images .slick-prev {
position:absolute;
top:65px;
left:-50px;
}
#images .slick-next {
position:absolute;
top:95px;
left:-50px;
}
.slick-prev:before, .slick-next:before {
color:red !important;
font-size: 30px;
}
.content {
margin: auto;
padding: 2px;
width: 90%;
}"))
),
sidebarLayout(
sidebarPanel(
# empty sidebar #
),
mainPanel(
tags$div(class="content",
do.call(function(...) tags$div(id="images", ...),
lapply(seq_along(images), function(i){
uiOutput(paste0("img",i))
})
)
)
)
)
)
# server #####
server <- function(input, output) {
lapply(seq_along(images), function(i){
output[[paste0("img",i)]] <- renderUI({
tags$img(src = images[i], width = "400px", height = "400px")
})
})
}
# Run the application ####
shinyApp(ui = ui, server = server)
Is it something like this you want ? We could try to add a "Go" button because here the autoplaying slideshow runs at the startup. Though this is not necessary: to stop the slideshow, it suffices to put the mouse cursor on the image.
EDIT
Here is the version with a "Go" button.
library(shiny)
# images to be displayed ####
## these images are in the www subfolder
images <- c("img1.JPG", "img2.JPG", "img3.JPG", "img4.JPG", "img5.JPG")
# ui #####
ui <- fluidPage(
tags$head(
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick-theme.css"),
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick.css"),
tags$script(type="text/javascript",
src="slick-1.8.1/slick/slick.js"),
tags$script(HTML(
"function runSlick(x){
$('#images').slick({
arrows: true,
dots: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 500,
infinite: false
}).on('afterChange', function(e, slick, cur){
if(cur === slick.$slides.length-1){
slick.setOption('autoplay', false, true);
}
});
};
Shiny.addCustomMessageHandler('runSlick', runSlick);")),
tags$style(HTML(
"#images .slick-prev {
position:absolute;
top:65px;
left:-50px;
}
#images .slick-next {
position:absolute;
top:95px;
left:-50px;
}
.slick-prev:before, .slick-next:before {
color:red !important;
font-size: 30px;
}
.content {
margin: auto;
padding: 2px;
width: 90%;
}"))
),
sidebarLayout(
sidebarPanel(
actionButton("go", "Go!")
),
mainPanel(
conditionalPanel(
"input.go > 0",
tags$div(class="content",
do.call(function(...) tags$div(id="images", ...),
lapply(seq_along(images), function(i){
uiOutput(paste0("img",i))
})
)
)
)
)
)
)
# server #####
server <- function(input, output, session){
lapply(seq_along(images), function(i){
output[[paste0("img",i)]] <- renderUI({
tags$img(src = images[i], width = "400px", height = "400px")
})
})
observeEvent(input[["go"]], {
session$sendCustomMessage("runSlick", "")
}, once = TRUE)
}
# Run the application ####
shinyApp(ui = ui, server = server)
I have TableTools and ColVis working together, but, as it was explained in another post (R shiny DataTables ColVis behavior), when clicking the Show/hide columns button, the list mixes up with the values in the table underneath, and I cannot make the list disappear.
In that post is mentioned that shiny atm is not compatible with the current data.table version, and I'd like to know if there's any other solution around. Here's my code:
ui.R
library(shiny)
library(shinythemes)
library(ggplot2)
addResourcePath('datatables','\\Users\\Ser\\Downloads\\DataTables-1.10.7\\DataTables-1.10.7\\media')
addResourcePath('tabletools','\\Users\\Ser\\Downloads\\TableTools-2.2.4\\TableTools-2.2.4')
shinyUI(fluidPage(theme = shinytheme("Journal"),
tags$head(
tags$style(HTML("
#import url('//fonts.googleapis.com/css?family=Lobster|Cabin:400,700');
"))
),
headerPanel(
h1("List Manager",
style = "font-family: 'Lobster', cursive;
font-weight: 500; line-height: 1.1;
color: #ad1d28;")),
sidebarLayout(
sidebarPanel(
#File Upload Manager
fileInput('file1', 'Choose file to upload'),
tagList(
singleton(tags$head(tags$script(src='//cdnjs.cloudflare.com/ajax/libs/datatables/1.10.7/js/jquery.dataTables.min.js',type='text/javascript'))),
singleton(tags$head(tags$script(src='//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/js/TableTools.min.js',type='text/javascript'))),
singleton(tags$head(tags$script(src='//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/js/ZeroClipboard.min.js',type='text/javascript'))),
singleton(tags$head(tags$link(href='//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/css/TableTools.min.css',rel='stylesheet',type='text/css'))),
singleton(tags$head(tags$script(src='//cdn.datatables.net/colvis/1.1.0/js/dataTables.colVis.min.js',type='text/javascript'))),
singleton(tags$script(HTML("if (window.innerHeight < 400) alert('Screen too small');")))
)),
mainPanel(
dataTableOutput("mytable"))
)))
server.R
shinyServer(function(input, output) {
output$mytable = renderDataTable({
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read.table(inFile$datapath, header=TRUE, sep='')
}, options = list(
"dom" = 'TC<"clear">lfrtip',
"colVis" = list(
"activate"="click",
"align"="right"),
"oTableTools" = list(
"sSwfPath" = "//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/swf/copy_csv_xls.swf",
"aButtons" = list(
"copy",
"print",
list("sExtends" = "collection",
"sButtonText" = "Save",
"aButtons" = c("csv","xls")
)
)
)
)
)
})
I also have another question: I'd like to search "<" or ">" values in the searchboxes at the bottom of the table, but I can't make it work. I don't know if I have to add anything to the code so it can be done (such as "regex" or similar).
You may try the DT package instead of hacking the JS libraries by yourself. Here is a minimal example:
library(shiny)
library(DT)
library(shinythemes)
shinyApp(
ui = fluidPage(
theme = shinytheme('journal'),
fluidRow(column(12, DT::dataTableOutput('foo')))
),
server = function(input, output) {
output$foo = DT::renderDataTable(
iris,
filter = 'bottom',
extensions = list(ColVis = list(activate= "click", align = "right")),
options = list(dom = 'C<"clear">lfrtip')
)
}
)
See http://rstudio.github.io/DT/extensions.html for more info on DataTables extensions.