Shiny - Updating global variable and seeing the result in current session - shiny

I am working with global variables that update after time X. This issue I am coming across is it updates the global variable but the current session doesn't update accordingly, however, any new session open uses the updated global variable.
Question: how do I get the current session to use the updated global variable? I thought wrapping it in a reactive would work but it doesn't.
Code:
library(shiny)
library(shinydashboard)
####/GLOBAL/####
num <- 4
####/UI/####
header <- dashboardHeader()
sidebar <- dashboardSidebar()
body <- dashboardBody(
verbatimTextOutput("test")
)
ui <- dashboardPage(header, sidebar, body)
####/SERVER/####
server <- function(input, output, session) {
data <- reactive({num})
output$test <- renderText({ data() })
observe({
invalidateLater(0.5*60*1000,session)
num <<- sample(1:1000,1,replace=T)
})
}
shinyApp(ui, server)
If you wait 30+ seconds and then open up a new session you will see that the number has changed from 4 but the original session still shows 4. They should be showing the same number.

Solved! Realized I needed to wrap it in a reactiveValues versus reactive. I also made the updating a value a dataframe versus a single number because that fits my real dashboard's problem.
library(shiny)
library(shinydashboard)
####/GLOBAL/####
dataset <- data.frame(ColA = c("dogs", "cats", "birds"), ColB = c(10, 2, 2), stringsAsFactors = FALSE)
####/UI/####
header <- dashboardHeader()
sidebar <- dashboardSidebar()
body <- dashboardBody(
box(width = 3, tableOutput("test"))
)
ui <- dashboardPage(header, sidebar, body)
####/SERVER/####
server <- function(input, output, session) {
values <- reactiveValues(n = dataset)
data <- reactive({values$n})
output$test <- renderTable({ data() })
observe({
invalidateLater(0.5*60*1000,session)
new1 <- sample(1:10,1,replace=T)
new2 <- sample(1:10,1,replace=T)
new3 <- sample(1:10,1,replace=T)
print(new1)
print(new2)
print(new3)
dat <- data.frame(ColA = c("dogs", "cats", "birds"), ColB = c(new1, new2, new3), stringsAsFactors = FALSE)
values$n <- dat
dataset <<- dat
})
}
shinyApp(ui, server)

Related

R Shiny: Updating proxy table column headers in ObserveEvent

I would like to update column headers in an R Shiny proxy table. The app should:
Launch with original column header names (e.g. "Do","Re","Mi","Fa","So")
Change those column headers in the proxy table to something else when the user clicks an action button (e.g. "y1","y2","y3","y4","y5")
Shiny has a convenient updateCaption() method that allows for a similar behavior for proxy table captions. I'd like to do something similar with table column headers for proxy tables. Here's my attempt.
library(shiny)
library(DT)
ui <- fluidPage(
fluidRow(
actionButton(
"updatebutton",
label = "Update Table",
style = "margin-right: 5px;"
),
DT::dataTableOutput("myplot")
),
)
server <- function(input, output) {
mycolumnnames <-c("Do","Re","Mi","Fa","So")
myothercolumnnames <- c("y1","y2","y3","y4","y5")
output$myplot <- DT::renderDataTable({
DF <- data.frame(replicate(5, sample(rnorm(5), 10, rep = TRUE)))
datatable(DF, colnames = mycolumnnames,
caption="Original caption")
})
proxy <- DT::dataTableProxy("myplot")
observeEvent(input$updatebutton, {
updateCaption(proxy, caption="Look, I am a NEW caption!")
DF <- data.frame(replicate(5, sample(rnorm(5), 10, rep = TRUE)))
# names(DF) <- myothercolumnnames # This doesn't work
proxy %>% replaceData(DF)
})
}
shinyApp(ui = ui, server = server)
Edit1: Now uses dataTableProxy()
I took away all the things related to color background so I could focus on your problem.
First, I declare some values outside shiny: your data.frame and two vectors for the column names. Then I assign the column names as the first vector.
Inside the app, I retrieve the data as a reactiveVal(), and update its colnames whenever the button is pressed
library(shiny)
library(DT)
mycolumnnames <-c("Do","Re","Mi","Fa","So")
myothercolumnnames <- c("y1","y2","y3","y4","y5")
DF <- data.frame(replicate(5, sample(rnorm(5), 10, rep = TRUE)))
colnames(DF) <- mycolumnnames
ui <- fluidPage(
fluidRow(
actionButton(
"updatebutton",
label = "Update Table",
style = "margin-right: 5px;"
),
DT::dataTableOutput("myplot")
),
)
server <- function(input, output) {
df <- reactiveVal(DF)
output$myplot <- DT::renderDataTable({
datatable(df(), caption="Original caption")
})
observeEvent(input$updatebutton, {
new_data <- data.frame(replicate(5, sample(rnorm(5), 10, rep = TRUE)))
if(!input$updatebutton %% 2 == 0 ){
colnames(new_data) <- myothercolumnnames
} else {
colnames(new_data) <- mycolumnnames
}
df(new_data)
proxy1 <- DT::dataTableProxy("myplot")
updateCaption(proxy1, caption="Look, I am a NEW caption!")
replaceData(proxy1, df())
})
}
shinyApp(ui = ui, server = server)
So whenever you press the button, the colnames are changed between the two vectors.

User defined function output in Shiny not in scope

I would like to use a user defined function in Shiny to perform a simple calculation with output two variables. The function I wrote works when it is not part of a shiny app. However when part of a Shiny, the returned object (dfr) is ‘not in scope’. What am I missing?
library(shiny)
# Function ----------------------------------------------------------------
convert <- function(coef_1, coef_2, vec) {
part_1 <- coef_1/(2*sin((vec/2)*pi/180))
part_2 <- 2*(180/pi)*(asin(coef_2/(2*part_1)))
dfr <- data.frame(part_1, part_2)
return(dfr)
}
# End Function ------------------------------------------------------------
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("num", h3("Enter number to convert:"), value = NULL)
),
mainPanel(
verbatimTextOutput("text1", placeholder = TRUE),
verbatimTextOutput("text2", placeholder = TRUE)
)
)
)
server <- function(input, output) {
nums_str <- as.character(input$num)
nums_vector <- as.numeric(unlist(strsplit(nums_str, split = ",")))
convert(1.5, 1.1, nums_vector)
output$text1 <- renderText({
req(input$num)
dfr$part_1
})
output$text2 <- renderText({
req(input$num)
dfr$part_2
})
}
shinyApp(ui = ui, server = server)
When you use inputs, you need to do it in reactive environment, such as reactive(), renderDataTable(), etc.
Here, you need to run your function in a reactive() and then call it with dfr() in the outputs.
server <- function(input, output) {
dfr <- reactive({
convert(1.5, 1.1, as.numeric(input$num))
})
output$text1 <- renderText({
req(input$num)
dfr()$part_1
})
output$text2 <- renderText({
req(input$num)
dfr()$part_2
})
}
Since this is quite basic stuff with R Shiny, checking some tutorials might be very useful.

RStudio-Shiny code works line-by-line (Ctrl+Enter), but not with the "Run App" button

in RStudio the below Shiny code works fine if I run it using Ctrl+Enter, line-by-line. However, if I run the whole code using the "Run App" button it generates this error:
Error in ts(x) : 'ts' object must have one or more observations
I think it is due to "lambda" parameter but I cannot see why. Any help is appreciated.
The link for "data.csv" is https://www.dropbox.com/s/p1bhacdg8j1qx42/data.csv?dl=0
====================================
library(shiny)
library(shinydashboard)
library(plotly)
library(forecast)
df <- read.csv("data.csv")
demand <- ts(df$demand, start = c(1995, 1), frequency = 12)
lbd <- BoxCox.lambda(demand, lower=-5, upper=5)
m <- ar(BoxCox(demand,lambda=lbd))
fit_BC <- forecast(m, h=12, lambda=lbd)
ui <- dashboardPage(
dashboardHeader(title = "Plot"),
dashboardSidebar(disable = TRUE),
dashboardBody(fluidRow(column(width = 12, box(plotlyOutput("forecast_plots"),width = NULL))))
)
server <- function(input, output) {
output$forecast_plots <- renderPlotly({
autoplot(fit_BC)
})
}
shinyApp(ui, server)
==================================
autoplot() returns ggplot object. But your output$forecast_plots requires plotly object(with plotlyOutput() function).
Working code is like the following:
ui <- dashboardPage(
dashboardHeader(title = "Plot"),
dashboardSidebar(disable = TRUE),
dashboardBody(fluidRow(column(width = 12, box(plotOutput("forecast_plots"),width = NULL))))
)
server <- function(input, output) {
output$forecast_plots <- renderPlot({
autoplot(fit_BC)
})
}
ggplot objects can be easily converted with ggplotly function, but unfortunately converted plotly autoplot graph loses the forecasting region. You can verify it like:
ui <- dashboardPage(
dashboardHeader(title = "Plot"),
dashboardSidebar(disable = TRUE),
dashboardBody(fluidRow(column(width = 12, box(plotlyOutput("forecast_plots"),width = NULL))))
)
server <- function(input, output) {
output$forecast_plots <- renderPlotly({
ggplotly(autoplot(fit_BC))
})
}
Add
I found autoplotly library.https://terrytangyuan.github.io/2018/02/12/autoplotly-intro/
autoplotly() function can convert autoplot object to plotly object which is roughly correct.
library(shiny)
library(shinydashboard)
library(plotly)
library(forecast)
library(autoplotly)
df <- read.csv("c:/Users/010170283/Downloads/data.csv")
demand <- ts(df$demand, start = c(1995, 1), frequency = 12)
lbd <- BoxCox.lambda(demand, lower=-5, upper=5)
m <- ar(BoxCox(demand,lambda=lbd))
fit_BC <- forecast(m, h=12, lambda=lbd)
ui <- dashboardPage(
dashboardHeader(title = "Plot"),
dashboardSidebar(disable = TRUE),
dashboardBody(fluidRow(column(width = 12, box(plotlyOutput("forecast_plots"),width = NULL))))
)
server <- function(input, output) {
output$forecast_plots <- renderPlotly({
autoplotly(autoplot(fit_BC))
})
}
shinyApp(ui, server)
The forecast region can be seen with it, and hi/lo 80 % edge values are presented with mouse hover event.

Shiny: calculate cumsum based on dygraphs' RangeSelector

I'm building a shiny app where I want to plot a dataset with one of the variables being a cumulative sum of another variable. The latter needs to be re-calculated every time the start date of dygraphs' dyRangeSelector changes. Below is a basic code without cumsum calculations. Commented out code is what I tried, with no success.
library(shinydashboard)
library(stringr)
library(zoo)
library(dplyr)
library(dygraphs)
ui <-dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
uiOutput("Ui1")
)
)
server <- function(input, output, session) {
output$Ui1 <- renderUI({
# date range observer
# values <- reactiveValues()
#
# observeEvent(input$plot1_date_window, {
# from <- as.Date(str_sub(input$plot1_date_window[[1]], 1, 10))
# })
## dygraphs plot
output$plot1 <- renderDygraph({
m_df <- data.frame(date=as.Date(zoo::as.yearmon(time(mdeaths))), Y=as.matrix(mdeaths))
# input_data <- m_df %>%
# filter(date >= values$from) %>%
# mutate(cumY = cumsum(Y))
input_xts <- xts(select(m_df, -date),
order.by = m_df$date)
#select(input_data, -date),
#order.by = input_data$date)
p <- dygraph(input_xts) %>%
dyRangeSelector()
p
})
## outputs
dygraphOutput('plot1')
})
}
shinyApp(ui, server)
UPDATE
I modified #Pork Chop's answer to be able to plot the cumulative values with other metrics on one graph, but I'm not even able to display the plot now:
library(xts)
library(shiny)
library(shinydashboard)
library(dygraphs)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
dygraphOutput('plot1'),
textOutput("cumsum1")
)
)
server <- function(input, output, session) {
m_df <- data.frame(date=as.Date(zoo::as.yearmon(time(mdeaths))), Y=as.matrix(mdeaths))
subdata <- reactive({
cumsum(m_df$Y[m_df$date >= as.Date(input$plot1_date_window[1]) & m_df$date <= as.Date(input$plot1_date_window[2])])
})
output$plot1 <- renderDygraph({
req(input$plot1_date_window)
input_xts <- xts(select(m_df, -date), order.by = m_df$date)
subdata_xts <- xts(select(subdata(), - date), order.by = subdata()$date)
final_xts <- cbind(input_xts, subdata_xts)
dygraph(final_xts) %>%
dyRangeSelector()
})
output$cumsum1 <- renderText({
req(input$plot1_date_window)
subdata <- cumsum(m_df$Y[m_df$date >= as.Date(input$plot1_date_window[1]) & m_df$date <= as.Date(input$plot1_date_window[2])])
subdata
})
}
shinyApp(ui, server)
The problem with your updated code is, that you didn't keep the date information. Also once you start rendering a plot based on a change of the plot itself (recursion) it gets a little tricky. You have to make sure that re-rendering the plot doesn't trigger the rendering again or you'll end up in a loop. That's why I set retainDateWindow = TRUE. Besides that you don't want the plot to re-render right away after the first change of the slider that's why I debounced the subdata.
Nevertheless, using dygraphs you still have the problem, that when you add cumsum as a series your plot for dyRangeSelector is changed (y maximum of all series). Please see the following code:
library(xts)
library(shiny)
library(shinydashboard)
library(dygraphs)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
dygraphOutput('plot1')
)
)
server <- function(input, output, session) {
m_df <- data.frame(date=as.Date(zoo::as.yearmon(time(mdeaths))), Y=as.matrix(mdeaths))
subdata <- reactive({
if(!is.null(input$plot1_date_window)){
subdata <- m_df[m_df$date >= as.Date(input$plot1_date_window[1]) & m_df$date <= as.Date(input$plot1_date_window[2]), ]
subdata$cumsum <- cumsum(subdata$Y)
subdata$Y <- NULL
} else {
subdata <- NULL
}
return(subdata)
})
subdata_d <- subdata %>% debounce(100)
output$plot1 <- renderDygraph({
input_xts <- xts(select(m_df, -date), order.by = m_df$date)
if(is.null(subdata_d())){
final_xts <- input_xts
} else {
subdata_xts <- xts(select(subdata_d(), - date), order.by = subdata_d()$date)
final_xts <- cbind(input_xts, subdata_xts)
}
p <- dygraph(final_xts) %>% dySeries(name="Y") %>%
dyRangeSelector(retainDateWindow = TRUE)
if("cumsum" %in% names(final_xts)){
p <- dySeries(p, name="cumsum", axis = "y2")
}
p
})
}
shinyApp(ui, server)
Just as #PorkChop mentioned I'd recommend multiple outputs for this scenario. Furthermore, I'd suggest to have a look at library(plotly) and it's event_data().
This should do the job, I think it is cleaner to have separate outputs for your dashboard
library(xts)
library(shiny)
library(shinydashboard)
library(dygraphs)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
dygraphOutput('plot1'),
textOutput("cumsum1")
)
)
server <- function(input, output, session) {
m_df <- data.frame(date=as.Date(zoo::as.yearmon(time(mdeaths))), Y=as.matrix(mdeaths))
output$plot1 <- renderDygraph({
input_xts <- xts(select(m_df, -date), order.by = m_df$date)
dygraph(input_xts) %>%
dyRangeSelector()
})
output$cumsum1 <- renderText({
req(input$plot1_date_window)
subdata <- cumsum(m_df$Y[m_df$date >= as.Date(input$plot1_date_window[1]) & m_df$date <= as.Date(input$plot1_date_window[2])])
subdata
})
}
shinyApp(ui, server)

Conditional reactive/eventReactive

I would like to add a checkbox (input$autorefresh) in my shiny application to control where my data input is auto updated (=reactive()) at every change, or whether it is only updated when a button (=input$refresh) is pushed. The idea is described in the following code, which I however didn't expect to work. I could use reactive() together with a conditional isolate(), but since I have many inputs, that is not very elegant. Any ideas?
if (input$autorefresh==TRUE){
dataInput <- reactive({
dosomething
})
} else {
dataInput <- eventReactive(input$refresh,{
dosomething
})
}
Are you looking for something like this?
library(shiny)
ui <- fluidPage(
checkboxInput("autorefresh","autorefresh", F),
actionButton("refresh","refresh"),
mainPanel(plotOutput("plot"))
)
autoInvalidate <- reactiveTimer(1000)
server <- function(input, output, session) {
data <- reactive({
input$refresh
data <- plot(rnorm(100),type="l",col="red")
if(input$autorefresh){
autoInvalidate()
return(data)
}
return(data)
})
output$plot <- renderPlot({
data()
})
}
runApp(shinyApp(ui = ui, server = server))