Shiny apps - embedding reactive expression in if-then statement - r

I'm having difficult using reactive expression in Shiny Apps. I'm creating a pie chart from slider input. This all works fine, however, the labels overlap. To avoid this I would like the label "" when the input is zero. The difficulty is that I'm unable to embed a reactive expression within an if-then statement.
Here is an MWE...
File "ui.R"...
library(shiny)
shinyUI(fluidPage(
titlePanel("Exposure to English calculator"),
sidebarLayout(
sidebarPanel(
h3("Sleeping"),
sliderInput("sleep", "How long does your child sleep every day?",
min=0, max=15, value=0, step = 0.5),
h3("School"),
sliderInput("school", "How long does your child spend in school?",
min = 0, max = 50, value=0, step = 0.5)
),
mainPanel(
plotOutput("plot")
)
)
))
File server.R...
library(shiny)
values <- c(1,2)
# Define server logic for slider examples
shinyServer(function(input, output) {
total_sleep <- reactive({7*input$sleep})
pieLabels<-c("sleep", "school", "other")
if(total_sleep() == 0) {
pieLabels[1] <- ""
}
output$plot <- renderPlot({
pie(c(total_sleep(), input$school, 50),
labels = pieLabels,
col = c("deepskyblue", "orange"),
height = 1500
)
})
})
The compilation crashes due to the "if" expression even though I've put the brackets after it, to show that it is derived from a reactive expression.
Thanks in advance.

You can just put all your total_sleep code and conditional statement inside the renderPlot function:
library(shiny)
app <- shinyApp(
ui = shinyUI(fluidPage(
titlePanel("Exposure to English calculator"),
sidebarLayout(
sidebarPanel(
h3("Sleeping"),
sliderInput("sleep", "How long does your child sleep every day?",
min=0, max=15, value=0, step = 0.5),
h3("School"),
sliderInput("school", "How long does your child spend in school?",
min = 0, max = 50, value=0, step = 0.5)
),
mainPanel(
plotOutput("plot")
)
)
)),
server = shinyServer(function(input, output) {
output$plot <- renderPlot({
total_sleep <- 7*input$sleep
pieLabels<-c("sleep", "school", "other")
if(total_sleep == 0) {
pieLabels[1] <- ""
}
pie(c(total_sleep, input$school, 50),
labels = pieLabels,
col = c("deepskyblue", "orange")
)
})
}))
runApp(app)
Note that I removed the height from pie because that argument doesn't exist and that gives out a warning.

Related

R Shiny app seems to hang when generating data

Colleagues,
I'm creating a Shiny app that can generate a data set with user-defined properties. The intended data-generation function can take some time, so I've substituted a very simple one.
My problem is that the app seems to just hang, or nothing happens at all, when I hit the GO button.
DEBUG in Rstudio shows nothing, and reactlog also gives no information.
Similar questions on this stackoverflow forum are more than 8 years old, and suggestions don't seem to work either.
I'm sure the solution is head-slapping simple but, right now, I'm lost.
Any suggestions from those more knowledgeable than this Shiny newbie?
## generate data set with user-defined parameters
## load libraries
library(shiny)
library(ggplot2)
library(DT)
##
options(shiny.reactlog = TRUE)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Synthesise data"),
# Sidebar
sidebarLayout(
sidebarPanel(
## Sample size
numericInput("sample_n", "Sample size", 30,
min = 10, max = 300, step = 1
),
h4("Desired scale moments"),
numericInput("target_mean", "Target mean", 4),
numericInput("target_sd", "Target st dev", 1),
hr(style = "border-top: 1px solid #000000;"),
actionButton("goButton", "Go!"),
actionButton("goChart", "show chart"),
),
mainPanel(
h4("generated data"),
# table of generated data
DT::dataTableOutput("mytable"),
# Show a plot of the generated distribution
plotOutput("resultPlot")
)
)
)
# Define server logic
server <- function(input, output) {
mytable <- reactive(input$goButton, {
## substituting data-gen function that can take some time
mydata <- rnorm(sample_n, target_mean, target_sd) |>
data.frame()
colnames(mydata) <- "scale"
# saveRDS(mydata, file = "generatedData.RDS")
output$mytable <- DT::renderDataTable(DT::datatable({
mydata
}))
})
myplot <- eventReactive(input$goChart, {
output$resultPlot <- renderPlot({
ggplot(mydata, aes(x = scale)) +
geom_density()
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Few code errors here :
forgot input$ when using sample_n, target_mean and target_sd in server
put some output definition inside eventReactive or reactive is a terrible habit
reactive is not used like you did. EventReactive is what you needed here.
Here is a corrected version of you code
library(shiny)
library(ggplot2)
library(DT)
ui <- fluidPage(
titlePanel("Synthesise data"),
sidebarLayout(
sidebarPanel(
numericInput("sample_n", "Sample size", 30,
min = 10, max = 300, step = 1
),
h4("Desired scale moments"),
numericInput("target_mean", "Target mean", 4),
numericInput("target_sd", "Target st dev", 1),
hr(style = "border-top: 1px solid #000000;"),
actionButton("goButton", "Go!"),
actionButton("goChart", "show chart"),
),
mainPanel(
h4("generated data"),
DT::dataTableOutput("mytable"),
plotOutput("resultPlot")
)
)
)
server <- function(input, output) {
mydata <- eventReactive(input$goButton, {
mydata <- data.frame(scale = rnorm(input$sample_n, input$target_mean, input$target_sd))
return(mydata)
})
output$mytable <- DT::renderDataTable(DT::datatable(
mydata()
))
output$resultPlot <- renderPlot({
input$goChart
isolate(ggplot(mydata(), aes(x = scale)) +
geom_density())
})
}
shinyApp(ui = ui, server = server)

How to reset one sliderInput when changing a second without triggering reactive in Shiny

I apologize if this question has a trivial answer and my limited knowledge of Shiny has led me down the wrong path during my extensive search for an answer.
I am trying to solve the following issue. I have an output that depends on two sliderInputs to create a plot. The sliders in turn are dependent on each other in the sense that the state of second slider should be reset each time the value for the first slider changes. My current attempt on implementing this looks as follows:
library(shiny)
library(plotly)
library(ggplot2)
ui <- fluidPage(
titlePanel("Test"),
sidebarLayout(
sidebarPanel(
sliderInput("slider1", "Slider1:", min = 0, max = 100, value = 0, step= 0.1),
sliderInput("slider2", "Slider2:", min = 0, max = 100, value = 0, step= 0.1)
),
mainPanel(
plotlyOutput('plot', height = 600)
)
)
)
server <- function(input, output, session) {
#temporary state storage.
slider1.state <- reactiveVal(-1)
counter <- reactiveVal(0)
output$plot <- renderPlotly({
print(paste("Function Call Number ", isolate(counter()) ))
counter(isolate(counter())+1)
#Only reset Slider2 if Slider1 has been changed
if (isolate(slider1.state()) != input$slider1) {
#this triggers a redraw
updateSliderInput(session, "slider2", value=0 )
}
ylim_max = input$slider2
#set the new values of the sliders
slider1.state(input$slider1)
ggplot(data.frame()) + geom_point() + xlim(0, input$slider1) + ylim(0, ylim_max)
})
}
shinyApp(ui, server)
I am using reactive values to store the state of slider1, and resetting slider2 using updateSliderInput only when slider1 has changed. The problem that I am facing however this that the call to updateSliderInput triggers the renderPlotly function a second time, hence unnecessarily computing and redrawing the plot of a second time.
I have tried to find a solution that would allow me to somehow update the sliderInput without triggering an event, but to no avail. Is there an elegant way of obtaining this behavior? Ideally, I am looking for a solution that could be applied to arbitrary inputs.
Any help in this matter would be greatly appreciated. Thank you!
You could use debounce() to avoid unnecessary updates:
library(shiny)
library(plotly)
library(ggplot2)
ui <- fluidPage(
titlePanel("Test"),
sidebarLayout(
sidebarPanel(
sliderInput("slider1", "Slider1:", min = 0, max = 100, value = 0, step= 0.1),
sliderInput("slider2", "Slider2:", min = 0, max = 100, value = 0, step= 0.1)
),
mainPanel(
plotlyOutput('plot', height = 600)
)
)
)
server <- function(input, output, session) {
observeEvent(input$slider1, {
updateSliderInput(session, "slider2", value=0 )
})
plot_limits <- reactive({
list(xlim_max = input$slider1, ylim_max = input$slider2)
})
plot_limits_d <- plot_limits %>% debounce(500)
counter <- reactiveVal(0)
output$plot <- renderPlotly({
print(paste("Function Call Number ", isolate(counter()) ))
counter(isolate(counter())+1)
ggplot(data.frame()) + geom_point() + xlim(0, plot_limits_d()$xlim_max) + ylim(0, plot_limits_d()$ylim_max)
})
}
shinyApp(ui, server)

user defined tickmark labels in inputSlider in shiny

sliderInput("myslider", "Slider:", min=1, max=100, value=6)
returns a slider with tickmark labels at 1, 11, 21, 31,...,91 and 100.
I would love the heuristic that is determining these tickmark labels to return more reasonable values of 1, 10, 20, 30,, ...90, 100.
I imagine this comes up a lot, as a slider from 1 to 100 is a very common one. (If you set min=0, it does show the desired tickmark labels, but in many apps, you don't want the input to be 0.
Currently, there is no way to supply user-defined tickmark labels to sliderInput. Is there a workaround just for the labels?
A similar question is posted here, but it talks about creating user-defined tick marks, not the labeling.
In https://groups.google.com/forum/#!topic/shiny-discuss/AeAzR4p2h1g is a solution of this problem:
ui <- pageWithSidebar(
headerPanel("Slider labels"),
sidebarPanel(
uiOutput("slider")
),
mainPanel()
)
server <- function(input, output) {
output$slider <- renderUI({
args <- list(inputId="foo", label="slider :", ticks=c(90,95,99,99.9), value=c(2,3))
args$min <- 1
args$max <- length(args$ticks)
if (sessionInfo()$otherPkgs$shiny$Version>="0.11") {
# this part works with shiny 1.5.0
ticks <- paste0(args$ticks, collapse=',')
args$ticks <- T
html <- do.call('sliderInput', args)
html$children[[2]]$attribs[['data-values']] <- ticks;
} else {
html <- do.call('sliderInput', args)
}
html
})
}
runApp(list(ui = ui, server = server))
By now this can be done using htmltools::tagQuery:
library(shiny)
library(htmltools)
ui <- basicPage(h1("Custom sliderInput ticks"),
{
customTicks <- seq_len(15)
customSlider <- sliderInput(
inputId = "sliderinput",
label = "sliderInput",
min = 1,
max = max(customTicks),
value = 7,
step = 1,
ticks = TRUE
)
tagQuery(customSlider)$find("input")$addAttrs("data-values" = paste0(customTicks, collapse = ", "))$allTags()
})
server <- function(input, output, session) {}
shinyApp(ui = ui, server = server)

Shiny in R: How can I fade-out my plotOutput from renderPlot if certain conditions are not met?

the question is straight forward. First, I tried an if-else condition within the render plot. Something like
if (input$Next > 0) {
plot(...)
}
else {
return()
}
This didn't work. The grey area at which the plot would be placed later was shown even though the condition wasn't met yet.
In a next step, I tried to use validate (see here). I basically copied the code from the given example. However, it still shows the grey area when the condition is actually not met. My current attempt looks as follows:
ui.R
shinyUI(fluidPage(
sidebarPanel(
plotOutput("test"),
actionButton("Next", "Next")
))
server.R
shinyServer(function(input, output, session) {
function(input, output) {
output$test <- renderPlot({
validate(
need(input$Next > 0)
)
pt <- plot(input$Next,2)
print(pt)
})
}
})
The plot function is just for illustration. Mine looks different. Any help is highly appreciated!
First possibility - conditionalPanel
We want to show the plotOutput if the actionButton was pressed. More specifically, if input.Next > 0. This condition is evaluated in javaScript, hence we have a slightly different syntax - instead of $ we use . after input and we use the parentheses.
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test")
)
However, it is quite strange that we multiply input.Next by one. It is necessary because input.Next, expect a number, returns also attributes. It seems that JavaScript doesn't know how to deal with this...but the multiplication does the trick.
[1] 0
attr(,"class")
[1] "integer" "shinyActionButtonValue"
In this example the plotOutput appears immediately...definitely too fast.
library(shiny)
ui1 <- shinyUI(fluidPage(
sidebarPanel(
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test")
),
actionButton("Next", "Next")
)
))
server1 <- shinyServer(function(input, output, session) {
output$test <- renderPlot({
pt <- plot(input$Next, 2)
print(input$Next)
print(pt)
})
})
shinyApp(ui1, server1)
"Slowing down the train"
In this example, we're going to "slow down" the speeding plotOutput. To do so we need the package shinyjs.
First, we're going to wrap the conditionalPanel into a div with an id, say, animation
div(id = "animation",
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test")
)
)
Then, on the server side, we're going to define the animation in the following way: conditional on the input$next the div should show up with the slide animation.
observe({
toggle(id = "animation", anim = TRUE, animType = "slide",
time = 0.5, condition = input$Next > 0)
})
Full example:
ui2 <- shinyUI(fluidPage(
# we need to include this function in order to use shinyjs functions
useShinyjs(),
sidebarPanel(
actionButton("Next", "Next"),
div(id = "animation",
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test"),
sliderInput("manipulate", "slider", min = 0, max = 1, value = 1)
)
)
)
))
server2 <- shinyServer(function(input, output, session) {
# Introduce gently the div with an id = "animation" and its all content.
observe({
toggle(id = "animation", anim = TRUE, animType = "slide",
time = 0.5, condition = input$Next > 0)
})
# We could animate only the plotOutput with "toogle(id = test")"
# - it would work as well, but for the first time the plot is shown
# way we would get an errors with margins.
output$test <- renderPlot({
#plot(input$Next, 2)
ggplot(iris, aes(x = Species)) + geom_bar(alpha = input$manipulate)
})
})
shinyApp(ui2, server2)
renderUI
As you pointed out, the another possibility is to use the function renderUI. If you want to render more than one element at once, you have to wrap them into a list as in the example below:
library(shiny)
library(ggplot2)
ui3 <- shinyUI(fluidPage(
sidebarPanel(
uiOutput("dynamic"),
actionButton("Next", "Next")
)
))
server3 <- shinyServer(function(input, output, session) {
output$dynamic <- renderUI({
if (input$Next > 0) {
# if we want to render more element, we need the list
list(
plotOutput("test"),
sliderInput("manipulate", "slider", min = 0, max = 1, value = 1)
)
}
})
output$test <- renderPlot({
#plot(input$Next, 2)
ggplot(iris, aes(x = Species)) + geom_bar(alpha = input$manipulate)
})
})
shinyApp(ui3, server3)
Use a conditional panel like so:
library(shiny)
ui =fluidPage(
sidebarPanel(
conditionalPanel(condition="input.Next>0",
plotOutput("test")),
actionButton("Next", "Next")
))
server=shinyServer(function(input, output, session) {
output$test <- renderPlot({
req(input$Next > 0)
pt <- plot(input$Next,2)
print(pt)
})
})
shinyApp(ui=ui,server=server)

How to avoid renderPlot to run more than once when updating multiple sliderInputs?

I'm wondering if it's possible to avoid rendering the plot function below more than once when pressing the reset button, while allowing the plot to render when any of the sliders change.
So far the sliders update correctly. Whenever a slider value changes, the plot is rendered as it's supposed to. I would like to avoid the plot to render twice (one per updateSliderInput call) when I press the reset button. This is a simple example and perhaps the delay is not as noticeable, but this problem is particularly nagging when many more inputs affecting the plot are updated programmatically and the plot takes a significant time to render.
Reproducible example:
server.r
library(shiny)
shinyServer(function(input, output, clientid, session) {
x <- reactive({
input$slider1
})
y <- reactive({
input$slider2
})
output$distPlot <- renderPlot({
plot(x(), y())
})
observe({
if(input$resetButton != 0) {
updateSliderInput(session, "slider1", value=30)
updateSliderInput(session, "slider2", value=30)
}
})
})
ui.r
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Example"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("slider1",
"X:",
min = 1,
max = 50,
value = 30),
sliderInput("slider2",
"y:",
min = 1,
max = 50,
value = 30),
actionButton("resetButton", "Reset!")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))

Resources