I am working on a Shiny application in which there are two slider inputs. These inputted values subset a data frame differently, and the subset of that data frame is then plotted into a scatterplot.
I am trying to prevent the scatterplot from being replotted unless the user clicks a "Go!" button. To try to achieve this, I am using the isolate() function on the slider input values, so that the data frame and plot are only updated when the "Go!" button is changed.
This seems to be working okay, but I am also trying to allow the user to use the selection tool in Plotly and see the data frame rows that correspond to that selection. However, when I attempt to do so, I receive an error ("Error: object 'datInput' not found"). This line is commented in the example below:
library(shiny)
library(plotly)
ui <- shinyUI(pageWithSidebar(
headerPanel("Click the button"),
sidebarPanel(
sliderInput("val1", "Value 1:", min = 0, max = 1, value=0.5, step=0.1),
sliderInput("val2", "Value 2:", min = 0, max = 1, value=0.5, step=0.1),
actionButton("goButton", "Go!")
),
mainPanel(
plotlyOutput("plot1"),
verbatimTextOutput("click")
)
))
server <- shinyServer(function(input, output) {
set.seed(1)
dat <- data.frame(Case = paste0("case",1:15), val1=runif(15,0,1), val2=runif(15,0,1))
dat$Case <- as.character(dat$Case)
xMax = max(dat$val1)
xMin = min(dat$val1)
yMax = max(dat$val2)
yMin = min(dat$val2)
maxTemp = max(abs(xMax), abs(xMin))
observeEvent(input$goButton,
output$plot1 <- renderPlotly({
# Use isolate() to avoid dependency on input$val1 and input$val2
datInput <- isolate(subset(dat, val1 > input$val1 & val2 > input$val2))
p <- qplot(datInput$val1, datInput$val2) +xlim(0, ceiling(maxTemp)) +ylim(0,1)
ggplotly(p)
})
)
d <- reactive(event_data("plotly_selected"))
output$click <- renderPrint({
if (is.null(d())){
"Click on a state to view event data"
}
else{
#str(d()$pointNumber) #Seems to be working
datInput[d()$pointNumber,] #Error: object 'datInput' not found
}
})
})
shinyApp(ui, server)
Any ideas for a workaround for this issue would be appreciated!
EDIT:
Here is the solution as per #mlegge. I simply added the output after the user selects certain points:
library(shiny)
library(plotly)
ui <- shinyUI(pageWithSidebar(
headerPanel("Click the button"),
sidebarPanel(
sliderInput("val1", "Value 1:", min = 0, max = 1, value=0.5, step=0.1),
sliderInput("val2", "Value 2:", min = 0, max = 1, value=0.5, step=0.1),
actionButton("goButton", "Go!")
),
mainPanel(
plotlyOutput("plot1"),
verbatimTextOutput("click")
)
))
set.seed(1)
dat <- data.frame(Case = paste0("case",1:15), val1=runif(15,0,1), val2=runif(15,0,1))
dat$Case <- as.character(dat$Case)
xMax = max(dat$val1)
xMin = min(dat$val1)
yMax = max(dat$val2)
yMin = min(dat$val2)
maxTemp = max(abs(xMax), abs(xMin))
server <- shinyServer(function(input, output) {
# datInput only validated once the go button is clicked
datInput <- eventReactive(input$goButton, {
subset(dat, val1 > input$val1 & val2 > input$val2)
})
output$plot1 <- renderPlotly({
# will wait to render until datInput is validated
plot_dat <- datInput()
p <- qplot(plot_dat$val1, plot_dat$val2) + xlim(0, ceiling(maxTemp)) +ylim(0,1)
ggplotly(p)
})
d <- reactive(event_data("plotly_selected"))
output$click <- renderPrint({
if (is.null(d())){
"Click on a state to view event data"
}
else{
#str(d()$pointNumber)
datInput()[d()$pointNumber+1,] #Working now
}
})
})
shinyApp(ui, server)
You are not using isolate properly, a better solution is an eventReactive:
library(shiny)
library(plotly)
ui <- shinyUI(pageWithSidebar(
headerPanel("Click the button"),
sidebarPanel(
sliderInput("val1", "Value 1:", min = 0, max = 1, value=0.5, step=0.1),
sliderInput("val2", "Value 2:", min = 0, max = 1, value=0.5, step=0.1),
actionButton("goButton", "Go!")
),
mainPanel(
plotlyOutput("plot1")
)
))
set.seed(1)
dat <- data.frame(Case = paste0("case",1:15), val1=runif(15,0,1), val2=runif(15,0,1))
dat$Case <- as.character(dat$Case)
xMax = max(dat$val1)
xMin = min(dat$val1)
yMax = max(dat$val2)
yMin = min(dat$val2)
maxTemp = max(abs(xMax), abs(xMin))
server <- shinyServer(function(input, output) {
# datInput only validated once the go button is clicked
datInput <- eventReactive(input$goButton, {
subset(dat, val1 > input$val1 & val2 > input$val2)
})
output$plot1 <- renderPlotly({
# will wait to render until datInput is validated
plot_dat <- datInput()
p <- qplot(plot_dat$val1, plot_dat$val2) + xlim(0, ceiling(maxTemp)) +ylim(0,1)
ggplotly(p)
})
})
shinyApp(ui, server)
You'll notice that your data generation has been moved outside the server, this is because it only needs to be run once.
You also should never wrap an output object in an observer, instead control the input data with reactives.
Related
I have an app with two numericinput. Both presented values between 0 and 1. What I wanted to do is the following: As the sum of the two weights must equal 1, so when I select the first weight, for example, 0.2, the second will be 0.8. Got the idea?
Executable code below
library(shiny)
ui <- fluidPage(
numericInput("weight1", label = h4("Weight 1"),
min = 0, max = 1, value = 0.5),
numericInput("weight2", label = h4("Weight 2"),
min = 0, max = 1, value = 0.5),
helpText("The sum of weights should be equal to 1"),
hr(),
fluidRow(column(3, verbatimTextOutput("value1"))),
fluidRow(column(3, verbatimTextOutput("value2")))
)
server <- function(input, output,session) {
output$value1 <- renderPrint({ input$weight1 })
output$value2 <- renderPrint({ input$weight2 })
}
shinyApp(ui = ui, server = server)
You can do it by using observeEvent and updateNumericInput.
Here's what the code will look like:
server <- function(input, output,session) {
observeEvent(input$weight1, {
updateNumericInput(session, 'weight2',
value = 1 - input$weight1)
})
output$value1 <- renderPrint({ input$weight1 })
output$value2 <- renderPrint({ input$weight2 })
}
Note: You don't need updateNumericInput if you are dealing with only two numbers and every time you need the sum to be equal to 1.
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)
I was wondering if I can get rows data using nearPoints() from an interactive graph with slider input. My app.R file looks like:
library('shiny')
library('ggplot2')
dt <-read.csv('file.csv')
ui <- fluidPage(
plotOutput("plot1", height = 550, click = "plot1_click"),
fluidRow(
column(3,
sliderInput("Obs", "Number of Books", min = 1, max = nrow(up), value = 50)
),
column(3, offset = 3,
h4("Legends"),
verbatimTextOutput("selected")
)
)
)
server <- function(input, output) {
mydata <- reactive({
dt[1:as.numeric(input$Obs),]
})
output$plot1 <- renderPlot({
test <- mydata()
ggplot(data = test, aes(x = test[,2], y = test[,1])) + geom_point()
})
output$selected <- renderPrint({
file <- mydata()
nearPoints(file, input$plot1_click, threshold = 10, maxpoints = 1,
addDist = FALSE)
})
}
shinyApp(ui = ui, server = server)
Shiny nearPoints() is working perfectly without this slider input. When I used slider input, I can't get the row data until max. Is there any approach to work with the slider input? Any help is appreciated.
The following code works for me. It seems nearPoints is not able to tell which columns of your dataset are displayed because of the aes(x = test[,2], y = test[,1]) statement. Another possible fix sould be to set the parameters xvar and yvar in nearPoints.
library('shiny')
library('ggplot2')
dt <-mtcars
ui <- fluidPage(
plotOutput("plot1", height = 550, click = "plot1_click"),
fluidRow(
column(3,
sliderInput("Obs", "Number of Cars", min = 1, max = nrow(dt), value = 50)
),
column(3, offset = 3,
h4("Legends"),
verbatimTextOutput("selected")
)
)
)
server <- function(input, output) {
mydata <- reactive({
dt[1:as.numeric(input$Obs),]
})
output$plot1 <- renderPlot({
test <- mydata()
ggplot(data = test, aes(mpg, wt)) + geom_point()
})
output$selected <- renderPrint({
file <- mydata()
nearPoints(file, input$plot1_click, threshold = 100, maxpoints = 1,
addDist = FALSE)
})
}
shinyApp(ui = ui, server = server)
Quick note: Please try to make the code in your question reproducible by using one of the default datasets in R. You can get a list of all available datasets by calling data().
I've got a problem using the shiny package in R. I'm trying to insert income. debt and bonus entered in the slider that I've built into the original data set so the algorithm can scale i.e. normalise the data to make the calculation. Right now I can only make the calculation as-is i.e. when you enter the data it calculates it however, as I indicated, I want the data entered to go back into the original data set so I can scale it before making the calculation. I have the below code for the shiny package.
library(shiny)
shinyUI(fluidPage(
headerPanel("Calculating fwbs"),
sidebarPanel(
sliderInput("Income", "Please Select Income: ", min=0, max=5000,
value=2500, step=100),
sliderInput("Debt", "Please Select Debt: ", min = 0, max=2500,
value=1250, step = 100),
sliderInput("Bonus", "Have you received any bonus: ", min=0, max= 1000,
value=500, step =100)
),
mainPanel(
textOutput("fwbi")
)
))
shinyServer(function(input, output, session){
output$fwbi <- renderText({
income2<- input$Income
debt2<- input$Debt
bonus2<- input$Bonus
paste("Your fwbi is: ", income2 + debt2 + bonus2)
})
})
Many thanks
If you want to be able to add an arbitrary number of rows to the original data, you can use a reactiveValues list:
Dframe <- data.frame(income = rnorm(5),
debt = rnorm(5),
bonus = rnorm(5))
library(shiny)
shinyApp(
ui =
shinyUI(
fluidPage(
headerPanel("Calculating fwbs"),
sidebarPanel(
sliderInput("Income", "Please Select Income: ", min=0, max=5000,
value=2500, step=100),
sliderInput("Debt", "Please Select Debt: ", min = 0, max=2500,
value=1250, step = 100),
sliderInput("Bonus", "Have you received any bonus: ", min=0, max= 1000,
value=500, step =100),
actionButton(inputId = "btn_add_to_data",
label = "Add To Data")
),
mainPanel(
textOutput("fwbi"),
tableOutput("data")
)
)
),
server =
shinyServer(function(input, output, session){
# Store the original data at start up.
Data <- reactiveValues(
Source = Dframe
)
# Every time you click "Add Data", it will add the user-input to Data$Source
observeEvent(
input$btn_add_to_data,
{
new_row <- data.frame(income = input$Income,
debt = input$Debt,
bonus = input$Bonus)
Data$Source <- rbind(Data$Source,
new_row)
}
)
output$fwbi <- renderText({
income2<- input$Income
debt2<- input$Debt
bonus2<- input$Bonus
paste("Your fwbi is: ",
scale(Data$Source$income) +
scale(Data$Source$debt) +
scale(Data$Source$bonus))
})
output$data <- renderTable({
Data$Source
})
})
)
If you want to limit the user to only adding one row, you can use an eventReactive
Dframe <- data.frame(income = rnorm(5),
debt = rnorm(5),
bonus = rnorm(5))
library(shiny)
shinyApp(
ui =
shinyUI(
fluidPage(
headerPanel("Calculating fwbs"),
sidebarPanel(
sliderInput("Income", "Please Select Income: ", min=0, max=5000,
value=2500, step=100),
sliderInput("Debt", "Please Select Debt: ", min = 0, max=2500,
value=1250, step = 100),
sliderInput("Bonus", "Have you received any bonus: ", min=0, max= 1000,
value=500, step =100),
actionButton(inputId = "btn_add_to_data",
label = "Add To Data")
),
mainPanel(
textOutput("fwbi"),
tableOutput("data")
)
)
),
server =
shinyServer(function(input, output, session){
NewData <- eventReactive(
input$btn_add_to_data,
{
new_row <- data.frame(income = input$Income,
debt = input$Debt,
bonus = input$Bonus)
rbind(Dframe,
new_row)
}
)
output$fwbi <- renderText({
income2<- input$Income
debt2<- input$Debt
bonus2<- input$Bonus
paste("Your fwbi is: ",
scale(NewData()$income) +
scale(NewData()$debt) +
scale(NewData()$bonus))
})
output$data <- renderTable({
NewData()
})
})
)
We can change the codes to
library(shiny)
library(DT
-create a function
fscale <- function(data = NULL) {
ui <- fluidPage(
headerPanel(title = "Calculating fwbs"),
sidebarPanel(
sliderInput("Income", "Please Select Income: ", min=0, max=5000,
value=2500, step=100),
sliderInput("Debt", "Please Select Debt: ", min = 0, max=2500,
value=1250, step = 100),
sliderInput("Bonus", "Have you received any bonus: ", min=0, max= 1000,
value=500, step =100),
numericInput("n", "Number of rows to scale:", 10, min = 1, max = 100)
),
mainPanel(
textOutput("fwbi"),
dataTableOutput("data"))
)
server <- function(input, output, session){
dat_scale <- reactive({
n1 <- seq_len(input$n)
data[["Income"]][n1] <- (data$Income[n1] - input$Income)/data$Income[n1]
data[["Debt"]][n1] <- (data$Debt[n1] - input$Debt)/data$Debt[n1]
data[["Bonus"]][n1] <- (data$Bonus[n1] - input$Bonus)/data$Bonus[n1]
data
})
output$data <- renderDataTable({
dat_scale()
})
output$fwbi <- renderText({
income2<- input$Income
debt2<- input$Debt
bonus2<- input$Bonus
paste("Your fwbi is: ", income2 + debt2 + bonus2)
})
}
shinyApp(ui= ui, server = server)
}
-run the function
fscale(df1)
-output
data
set.seed(24)
df1 <- data.frame(Income = sample(0:5000, 25, replace = TRUE),
Debt = sample(0:2500, 25, replace = TRUE),
Bonus = sample(0:1000, 25, replace = TRUE))
The community helped me in developing this code
library(shiny)
server <- shinyServer(function(input, output, session) {
values <- reactiveValues()
values$df <- data.frame(N = numeric(0), A = numeric(0), C = numeric(0))
newEntry <- observeEvent(input$update,{
gr <- 10 + input$n
newLine <- isolate(c(input$n, input$nr1, gr))
values$df[nrow(values$df) + 1,] <- c(input$n, input$nr1, gr)
})
output$table1 <- renderTable({values$df})
})
ui <- shinyUI(fluidPage(
titlePanel("submitButton example"),
fluidRow(
column(3, wellPanel(
sliderInput("nr1", "Give a number: ", min = 0, max = 100, value = 0, step = 2),
sliderInput("n", "N:", min = 10, max = 1000, value = 200, step = 10),
actionButton("update", "Update Table"))),
column(6, tableOutput("table1"))
)
))
shinyApp(ui, server)
And I come to a new problem with that.
Now, I'd like that the app do like this: on click on the update, the variable A should always add a 5 i.e. if I have starting value of 5 than on the next click it should be 10 than 15, 20 etc?
Now when I click update the same number appears continously
Is this what you want? Also you dont need the isolate in there.
rm(list = ls())
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("submitButton example"),
fluidRow(
column(3, wellPanel(
sliderInput("nr1", "Give a number:", min = 1, max = 100, value = 1, step = 1),
sliderInput("n", "N:", min = 10, max = 1000, value = 200, step = 10),
actionButton("update", "Update Table"))),
column(6, tableOutput("table1"))
)
))
server <- shinyServer(function(input, output, session) {
values <- reactiveValues()
values$df <- data.frame(N = numeric(0), A = numeric(0), C = numeric(0))
newEntry <- observeEvent(input$update,{
if(!is.null(input$nr1) & is.null(values$A)){
values$A <- input$nr1
}
if(nrow(values$df) > 0){
values$A <- values$A + 5
}
gr <- 10 + input$n
values$df[nrow(values$df) + 1,] <- c(input$n, values$A, gr)
})
output$table1 <- renderTable({values$df})
})
shinyApp(ui, server)