This is a simplified version of a larger script I have made. Essentially I have 3 graphs, the first uses a user Input percentage of the total number of rows from a dataset and plots it. The second graph uses the rows that were not plotted in the first graph, applies a different percent selector, and plots that. And the third does the same thing but with respect to the second graph.
My issue is the second graph is able to return a custom error message because the dataset is available but because shiny is 'lazy' I can't query the 3rd graphs data because it doesn't exist yet. I want to do this in order to change the error message to a more useful error message for the user.
When the code below is run, the second graph has the custom error but the 3rd one says "Error: argument is of length zero"
but I would like it to show the custom error. I know I can just use
validate(
need(nrow(Graph1_data_inverse()) > 0, 'There is no data available for this graph')
)
validate(
need(nrow(Graph2_data_inverse()) > 0, 'There is no data available for this graph')
)
But I would like a more elegant solution if possible.
library(shiny)
library(datasets)
library(shinyFeedback)
library(plotly)
data <- iris
ui <- fluidPage(
shinyFeedback::useShinyFeedback(),
column(4,
numericInput("Graph1_percent", tags$span(style="color: #000000; font-size: 13px;", "Percent (%)"), value = 100, min = 0, max = 100),
plotlyOutput("Graph1", height = 400)
),
column(4,
numericInput("Graph2_percent", tags$span(style="color: #000000; font-size: 13px;", "Percent (%)"), value = 100, min = 0, max = 100),
plotlyOutput("Graph2", height = 400)
),
column(4,
numericInput("Graph3_percent", tags$span(style="color: #000000; font-size: 13px;", "Percent (%)"), value = 100, min = 0, max = 100),
plotlyOutput("Graph3", height = 400)
)
)
server <- function(input, output, session) {
Graph1_data <- reactive({
n <- nrow(data)
if(input$Graph1_percent == 0 | n == 0) {
} else {
Graph1_data_ <- data[1:round(n*(input$Graph1_percent /100)), ]
}
})
Graph1_data_inverse <- reactive({
n <- nrow(data)
if(n == 0 | input$Graph1_percent == 100) {
} else {
Graph1_data_inv <- data[(round(n*(input$Graph1_percent /100))+1):n, ]
}
})
output$Graph1 <- renderPlotly({
ggplotly(Graph1_data() %>%
ggplot(aes(x = Sepal.Length, fill = Species))+
geom_bar()
)
})
###Graph 2
Graph2_data <- reactive({
validate(
need(nrow(Graph1_data_inverse()) > 0, 'There is no data available for this graph.')
)
n <- nrow(Graph1_data_inverse())
if(input$Graph2_percent == 0 | n == 0) {
} else {
Graph2_data_ <- Graph1_data_inverse()[1:round(n*(input$Graph2_percent /100)), ]
}
})
Graph2_data_inverse <- reactive({
n <- nrow(Graph1_data_inverse())
if(n == 0 | input$Graph2_percent == 100) {
} else {
Graph2_data_inv <- Graph1_data_inverse()[(round(n*(input$Graph2_percent /100))+1):n, ]
}
})
output$Graph2 <- renderPlotly({
req(Graph2_data())
ggplotly(Graph2_data() %>%
ggplot(aes(x = Sepal.Length, fill = Species))+
geom_bar()
)
})
###Graph 3
Graph3_data <- reactive({
###This section doesn't work
exists <- exists("Graph2_data_inverse", where = -1, mode='function')
if(exists != TRUE){
paste("There is no data available for this graph")
}
req(exists, cancelOutput = TRUE)
validate(
need(nrow(Graph2_data_inverse()) > 0, 'There is no data available for this graph')
)
n <- nrow(Graph2_data_inverse())
if(input$Graph3_percent == 0 | n == 0) {
} else {
Graph3_data_ <- Graph2_data_inverse()[1:round(n*(input$Graph3_percent /100)), ]
}
})
###this inverse dataset is not used but I'm including it here so the graphs retain the same structure
Graph3_data_inverse <- reactive({
n <- nrow(Graph2_data_inverse())
if(n == 0 | input$Graph3_percent == 100) {
} else {
Graph3_data_inv <- Graph2_data_inverse()[(round(n*(input$Graph3_percent /100))+1):n, ]
}
})
output$Graph3 <- renderPlotly({
req(Graph3_data())
ggplotly(Graph3_data() %>%
ggplot(aes(x = Sepal.Length, fill = Species))+
geom_bar()
)
})
}
# Run the application
shinyApp(ui = ui, server = server)
When you check the trace log, you should notice that the error does not really originate from Graph3_data()
but it's the Graph2_data_inverse()
that fails to evaluate with Error in if :
Warning: Error in if: argument is of length zero
150: <reactive:Graph2_data_inverse> [#43]
134: Graph2_data_inverse
126: <reactive:Graph3_data> [#68]
110: Graph3_data
103: renderPlotly [#92]
102: func
99: shinyRenderWidget
98: func
85: renderFunc
84: output$Graph3
3: runApp
2: print.shiny.appobj
1: <Anonymous>
Which in turn is a result of having potential NULL
-values and elementwise OR, |
, in if
conditions.
E.g. if you have a following reactive:
Graph2_data_inverse <- reactive({
n <- nrow(Graph1_data_inverse())
if(n == 0 | input$Graph2_percent == 100) {
} else {
Graph2_data_inv <- Graph1_data_inverse()[(round(n*(input$Graph2_percent /100))+1):n, ]
}
})
and Graph1_data_inverse()
happens to return NULL
, you get something like:
(n <- nrow(NULL))
#> NULL
if (n == 0 | FALSE ) {}
#> Error in if (n == 0 | FALSE) {: argument is of length zero
In if
conditions you most likely want to use ||
, &&
instead of |
, &
. If there's a chance that NULL
-values could sneak in and turn whole expression to 0-length / non-TRUE/FALSE
, shiny::isTruthy()
can be handy, but as it's also true for 0
, a minimal update might look like this :
Graph2_data_inverse <- reactive({
n <- nrow(Graph1_data_inverse())
if(!isTruthy(n) || n == 0 || input$Graph2_percent == 100) {
} else {
...
}
})
This applies to all conditionals, not just for the one in Graph2_data_inverse()
.