Related
I want to display an animation of a simulation using two plots.
One is a "population" view of the simulation where points represent individuals. At every step in the simulation, I want to draw random points within a circle keeping those sampled in the next generation. The simulation stops when the frequency of any of the two types of individuals reaches 1 or 0.
The other one is a frequency chart with frequency on the y-axis and generations on the x-axis. Ideally, I want this graph to expand at every generation and stop when the frequency reaches 1/0.
My first problem is that I can't get reactiveTimer() to work as want it to. It does not self-update or if it does it goes back to the starting point without "remembering" previous states.
My second problem is that if I use an if statement for the condition to keep the simulation going it only iterates a single generation after pressing run. Alternatively, if I use a while loop it will just go to directly to the last generation, skipping all the middle parts of the simulation.
My third problem is that I cannot grow a data.frame within a reactive environment so that I can plot the frequencies after each generation.
Code:
library(shiny)
library(ggplot2)
# function to make a circle data.frame
# https://stackoverflow.com/questions/6862742/draw-a-circle-with-ggplot2
circleFun <- function(center=c(0,0), diameter=10, npoints=100){
r = diameter / 2
tt = seq(0,2*pi,length.out = npoints)
xx = center[1] + r * cos(tt)
yy = center[2] + r * sin(tt)
return(data.frame(x = xx, y = yy))
}
# ui
ui <- fluidPage(
titlePanel("Genetic Drift Simulator"),
sidebarLayout(
sidebarPanel(
# Input: select from menu
numericInput(inputId = "population_size",
label = "Population size:",
value = 10,
step = 10),
sliderInput(inputId = "initial_frequency",
label = "Initial frequency of allele 1:",
min = 0,
max = 1,
step = 0.1,
value = 0.5),
actionButton(inputId = "run",
label = "Run simulation"),
actionButton(inputId = "reset",
label = "Reset values")
),
mainPanel(
fluidRow(
column(4,
verbatimTextOutput("text")
)
),
fluidRow(
column(8,
plotOutput("pop_plot")
)
),
fluidRow(
column(8,
plotOutput("freq_plot")
)
)
)
)
)
server <- function(input, output, session) {
waits <- reactiveValues(timer = reactiveTimer(Inf))
returns <- reactiveValues(
z=NULL,
x=NULL,
y=NULL,
freq=NULL,
circle=NULL,
i=NULL
)
frequencies <- reactiveValues(df=NULL)
observe({
returns$z=rbinom(input$population_size, 1, input$initial_frequency)
returns$x=rnorm(input$population_size)
returns$y=rnorm(input$population_size)
returns$freq=input$initial_frequency
returns$circle=circleFun()
returns$i=0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
population <- reactive({
data.frame(x=returns$x, y=returns$y, z=returns$z)
})
grow_freq <- function(df, x, y){
rbind(df, c(x,y))
}
grow <- reactive({
frequencies$df = grow_freq(frequencies$df, returns$i, returns$freq)
})
drift <- reactive({
returns$z = sample(returns$z, replace=T)
# random locations
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
# calculate frequency
returns$freq = sum(returns$z == 1)/input$population_size
# increase to next generation
returns$i = returns$i+1
})
observeEvent(input$run, {
if (returns$freq < 1 & returns$freq > 0){
# observeEvent(reactiveTimer(200), {
drift()
grow()
# })
}
# else {
# waits$timer <- reactiveTimer(Inf)
# }
})
observeEvent(input$reset, {
timer = reactiveTimer(Inf)
returns$z = rbinom(input$population_size, 1, input$initial_frequency)
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
returns$freq = input$initial_frequency
returns$circle=circleFun(center=c(0,0), diameter=10, npoints=100)
returns$i = 0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
output$text <- renderText({
text = paste("Population size: ",input$population_size,"\n",
"Frequency allele 1: ",returns$freq,"\n",
"Generation: ",returns$i, sep="")
print(text)
})
output$pop_plot <- renderPlot({
ggplot(data=population(), aes(x, y)) +
geom_point(aes(color=factor(z)), size=5, alpha=0.7) +
geom_path(data=returns$circle, color="black", size=2) +
scale_color_brewer(type="qual", palette=1, name="allele") +
theme(axis.title=element_blank(), axis.text=element_blank()) +
theme(legend.title=element_text(size=16), legend.text=element_text(size=14))
},
height = 400, width = 450)
output$freq_plot <- renderPlot({
ggplot(data=frequencies$df, aes(x, y)) +
geom_point() +
geom_line() +
ylim(0,1)
},
height = 300, width = 500)
}
shinyApp(ui = ui, server = server)
The three problems you noted are resolved in the code below. I changed your first observe to observeEvent. Please note that you need to click on reset when the simulation ends.
# ui
ui <- fluidPage(
titlePanel("Genetic Drift Simulator"),
sidebarLayout(
sidebarPanel(
# Input: select from menu
numericInput(inputId = "population_size",
label = "Population size:",
value = 10,
step = 10),
sliderInput(inputId = "initial_frequency",
label = "Initial frequency of allele 1:",
min = 0,
max = 1,
step = 0.1,
value = 0.5),
actionButton(inputId = "run",
label = "Run simulation"),
actionButton(inputId = "reset",
label = "Reset values")
),
mainPanel(
fluidRow(
column(4,
verbatimTextOutput("text")
)
),
fluidRow(
column(8,
plotOutput("pop_plot")
)
),
fluidRow(
column(8,
plotOutput("freq_plot")
)
)
)
)
)
server <- function(input, output, session) {
# Anything that calls autoInvalidate will automatically invalidate every 2 seconds.
autoInvalidate <- reactiveTimer(2000)
#waits <- reactiveValues(timer = reactiveTimer(Inf))
returns <- reactiveValues(
z=NULL,
x=NULL,
y=NULL,
freq=NULL,
circle=NULL,
i=NULL
)
frequencies <- reactiveValues(df=NULL)
#observe({
observeEvent(list(input$population_size,input$initial_frequency), {
returns$z=rbinom(input$population_size, 1, input$initial_frequency)
returns$x=rnorm(input$population_size)
returns$y=rnorm(input$population_size)
returns$freq=input$initial_frequency
returns$circle=circleFun()
returns$i=0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
population <- reactive({
data.frame(x=returns$x, y=returns$y, z=returns$z)
})
grow_freq <- function(df, x, y){
rbind(df, c(x,y))
}
grow <- reactive({
frequencies$df <- grow_freq(frequencies$df, returns$i, returns$freq)
})
drift <- reactive({
returns$z = sample(returns$z, replace=T)
# random locations
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
# calculate frequency
returns$freq = sum(returns$z == 1)/input$population_size
# increase to next generation
returns$i = returns$i+1
})
observeEvent(list(input$run,autoInvalidate()), {
if (returns$freq < 1 & returns$freq > 0){
# observeEvent(reactiveTimer(200), {
drift()
grow()
# })
}
# else {
# waits$timer <- reactiveTimer(200)
# }
})
observeEvent(input$reset, {
#timer = reactiveTimer(Inf)
returns$z = rbinom(input$population_size, 1, input$initial_frequency)
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
returns$freq = input$initial_frequency
returns$circle=circleFun(center=c(0,0), diameter=10, npoints=100)
returns$i = 0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
output$text <- renderText({
text = paste("Population size: ",input$population_size,"\n",
"Frequency allele 1: ",returns$freq,"\n",
"Generation: ",returns$i, sep="")
print(text)
})
output$pop_plot <- renderPlot({
autoInvalidate()
ggplot(data=population(), aes(x, y)) +
geom_point(aes(color=factor(z)), size=5, alpha=0.7) +
geom_path(data=returns$circle, color="black", size=2) +
scale_color_brewer(type="qual", palette=1, name="allele") +
theme(axis.title=element_blank(), axis.text=element_blank()) +
theme(legend.title=element_text(size=16), legend.text=element_text(size=14))
},
height = 400, width = 450)
output$freq_plot <- renderPlot({
autoInvalidate()
ggplot(data=frequencies$df, aes(x, y)) +
geom_point() +
geom_line() +
ylim(0,1)
},
height = 300, width = 500)
}
shinyApp(ui = ui, server = server)
My edits to the accepted answers were rejected, so I'm posting the revised code here as an answer. The reason was that I was not addressing the OP's questions. Which is strange because the OP is me.
In short:
I removed reactiveTimer functions from plots.
The counter now starts after pressing the run button and not automatically.
Fixed a bug with initial frequency on 2nd plot which was set to 0 and not 0.5
Removed else statement on the simulation block to allow the reset button to work.
An example of the ShinyApp can be found in here.
# ui
ui <- fluidPage(
titlePanel("Genetic Drift Simulator"),
sidebarLayout(
sidebarPanel(
# Input: select from menu
numericInput(inputId = "population_size",
label = "Population size:",
value = 10,
step = 10),
sliderInput(inputId = "initial_frequency",
label = "Initial frequency of allele 1:",
min = 0,
max = 1,
step = 0.1,
value = 0.5),
actionButton(inputId = "run",
label = "Run simulation"),
actionButton(inputId = "reset",
label = "Reset values")
),
mainPanel(
fluidRow(
column(4,
verbatimTextOutput("text")
)
),
fluidRow(
column(8,
plotOutput("pop_plot")
)
),
fluidRow(
column(8,
plotOutput("freq_plot")
)
)
)
)
)
server <- function(input, output, session) {
# Anything that calls autoInvalidate will automatically invalidate.
autoInvalidate <- reactiveValues(timer=NULL)
returns <- reactiveValues(
z=NULL,
x=NULL,
y=NULL,
freq=NULL,
circle=NULL,
i=NULL
)
frequencies <- reactiveValues(df=NULL)
observeEvent(list(input$population_size,input$initial_frequency), {
returns$z=rbinom(input$population_size, 1, input$initial_frequency)
returns$x=rnorm(input$population_size)
returns$y=rnorm(input$population_size)
returns$freq=input$initial_frequency
returns$circle=circleFun()
returns$i=0
frequencies$df = data.frame(x=returns$i, y=returns$freq)
autoInvalidate$timer = reactiveTimer(Inf)
})
population <- reactive({
data.frame(x=returns$x, y=returns$y, z=returns$z)
})
grow_freq <- function(df, x, y){
rbind(df, c(x,y))
}
grow <- reactive({
frequencies$df <- grow_freq(frequencies$df, returns$i, returns$freq)
})
drift <- reactive({
returns$z = sample(returns$z, replace=T)
# random locations
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
# calculate frequency
returns$freq = sum(returns$z == 1)/input$population_size
# increase to next generation
returns$i = returns$i+1
})
observeEvent(input$run, {
autoInvalidate$timer = reactiveTimer(1000) # changed to 1 second
drift()
grow()
})
observeEvent(autoInvalidate$timer(), {
if (returns$freq < 1 & returns$freq > 0 & returns$i != 0){
autoInvalidate$timer()
drift()
grow()
}
# else if (returns$freq == 0 | returns$freq == 1) {
# autoInvalidate$timer = reactiveTimer(Inf)
# }
})
observeEvent(input$reset, {
returns$z = rbinom(input$population_size, 1, input$initial_frequency)
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
returns$freq = input$initial_frequency
returns$circle=circleFun(center=c(0,0), diameter=10, npoints=100)
returns$i = 0
frequencies$df = data.frame(x=returns$i, y=returns$freq)
autoInvalidate$timer = reactiveTimer(Inf)
})
output$text <- renderText({
#autoInvalidate$timer()
text = paste("Population size: ",input$population_size,"\n",
"Frequency allele 1: ",returns$freq,"\n",
"Generation: ",returns$i, sep="")
print(text)
})
output$pop_plot <- renderPlot({
#autoInvalidate$timer()
ggplot(data=population(), aes(x, y)) +
geom_point(aes(color=factor(z)), size=5, alpha=0.7) +
geom_path(data=returns$circle, color="black", size=2) +
scale_color_brewer(type="qual", palette=1, name="allele") +
theme(axis.title=element_blank(), axis.text=element_blank()) +
theme(legend.title=element_text(size=16), legend.text=element_text(size=14))
},
height = 400, width = 450)
output$freq_plot <- renderPlot({
#autoInvalidate$timer()
if (dim(frequencies$df)[1] == 1){
ggplot(data=frequencies$df, aes(x, y)) +
geom_hline(yintercept=0.5) +
geom_point() +
labs(x="generation",y="frequency") +
ylim(0,1)
} else {
ggplot(data=frequencies$df, aes(x, y)) +
geom_hline(yintercept=0.5) +
geom_point() +
geom_line() +
labs(x="generation",y="frequency") +
ylim(0,1)
}
},
height = 300, width = 500)
}
shinyApp(ui = ui, server = server)
I want to make an animation in R Shiny where my scatter plot is progressively updated at each iteration, here is my current plot
library(shiny)
library(plotly)
ui <- fluidPage(
titlePanel(""),
sidebarLayout(
sidebarPanel(
actionButton("launch", "Launch Simulation"),
radioButtons("display","Show every iteration", selected = 10,
choices = c(1,5,10,50),inline = FALSE),
numericInput("iter","Maximum number of iterations", value = 2000,
min = 500,max = 5000, step = 500)
),
mainPanel(
plotlyOutput('plot')
)
)
)
server <- function(input, output) {
rv <- reactiveValues(i = 0,
df = data.frame(x = -1,y = -1))
observeEvent(input$launch,{
rv$i = 0
rv$df = data.frame(x = runif(5000, min = -1,max = 1),
y = runif(5000, min = -1,max = 1))
})
observe({
isolate({
rv$i = rv$i + as.numeric(input$display)
})
if ((rv$i < input$iter)&input$launch){
invalidateLater(0)
}
})
output$plot <- renderPlotly({
df = data.frame(x = 0,y = -1)
df = rbind(df,rv$df)
plot_ly(df[1:(rv$i + 1),], x = ~x, y = ~y,
type = 'scatter', mode = 'markers',
marker = list(size = 4), hoverinfo="none") %>%
layout(showlegend = FALSE)
})
}
shinyApp(ui = ui, server = server)
The code is working fine at the beginning but after around 1000 iterations, the animation becomes very slow. I think the main problem is that because in my code, I have to re-make the plot all over again at each iteration, is there a smoother way to do what I want to do?
(Not necessarily with Plotly but it is important to me that I keep track of the number of the iterations outside of the plot (here rv$i))
I tried to fetch streaming data from mosquito test server for creating a real time line chart. I checked some examples of real time chart, but I couldn't seem to achieve the same objective. The chart is updated real time but it always refreshes.
Here is the script I edited from one example:
library(shiny)
library(magrittr)
library(mqtt)
library(jsonlite)
ui <- shinyServer(fluidPage(
plotOutput("plot")
))
server <- shinyServer(function(input, output, session){
myData <- data.frame()
# Function to get new observations
get_new_data <- function(){
d <- character()
mqtt::topic_subscribe(host = "test.mosquitto.org", port = 1883L, client_id = "dcR", topic = "IoTDemoData",
message_callback =
function(id, topic, payload, qos, retain) {
if (topic == "IoTDemoData") {
d <<- readBin(payload, "character")
# print(received_payload)
# received_payload <- fromJSON(received_payload)
# print(d)
return("quit")
}
}
)
d <- fromJSON(d)
d <- as.data.frame(d)
return(d)
# data <- rnorm(5) %>% rbind %>% data.frame
# return(data)
}
# Initialize my_data
myData <- get_new_data()
# Function to update my_data
update_data <- function(){
myData <<- rbind(get_new_data(), myData)
}
# Plot the 30 most recent values
output$plot <- renderPlot({
invalidateLater(1000, session)
update_data()
print(myData)
plot(temperature ~ 1, data=myData[1:30,], ylim=c(-20, -10), las=1, type="l")
})
})
shinyApp(ui=ui,server=server)
I have been struggling with creating real time chart for days. If anyone can point out the problem why the line chart is always refreshed and the solution, it will be highly appreciated!
Below are the revised working script based on Florian's answer:
library(shiny)
library(mqtt)
library(jsonlite)
library(ggplot2)
ui <- shinyServer(fluidPage(
plotOutput("mqttData")
))
server <- shinyServer(function(input, output, session){
myData <- reactiveVal()
get_new_data <- function(){
d <- character()
mqtt::topic_subscribe(host = "localhost", port = 1883L, client_id = "dcR", topic = "IoTDemoData",
message_callback =
function(id, topic, payload, qos, retain) {
if (topic == "IoTDemoData") {
d <<- readBin(payload, "character")
return("quit")
}
}
)
d <- fromJSON(d)
d <- as.data.frame(d)
return(d)
}
observe({
invalidateLater(1000, session)
isolate({
# fetch the new data
new_data <- get_new_data()
# If myData is empty, we initialize it with just the new data.
if(is.null(myData()))
myData(new_data)
else # row bind the new data to the existing data, and set that as the new value.
myData(rbind(myData(),new_data))
})
})
output$mqttData <- renderPlot({
ggplot(mapping = aes(x = c(1:nrow(myData())), y = myData()$temperature)) +
geom_line() +
labs(x = "Second", y = "Celsius")
})
})
shinyApp(ui=ui,server=server)
However, after adding a second plot, the flickering began. When I commented out one of the plots, the plot works great without the need to refresh.
library(shiny)
library(mqtt)
library(jsonlite)
library(ggplot2)
ui <- shinyServer(fluidPage(
plotOutput("mqttData"),
plotOutput("mqttData_RH")
))
server <- shinyServer(function(input, output, session){
myData <- reactiveVal()
get_new_data <- function(){
d <- character()
mqtt::topic_subscribe(host = "test.mosquitto.org", port = 1883L, client_id = "dcR", topic = "IoTDemoData",
# mqtt::topic_subscribe(host = "localhost", port = 1883L, client_id = "dcR", topic = "IoTDemoData",
message_callback =
function(id, topic, payload, qos, retain) {
if (topic == "IoTDemoData") {
d <<- readBin(payload, "character")
return("quit")
}
}
)
d <- fromJSON(d)
d <- as.data.frame(d)
d$RH <- as.numeric(as.character( d$RH))
return(d)
}
observe({
invalidateLater(10000, session)
isolate({
# fetch the new data
new_data <- get_new_data()
# If myData is empty, we initialize it with just the new data.
if(is.null(myData()))
myData(new_data)
else # row bind the new data to the existing data, and set that as the new value.
myData(rbind(myData(),new_data))
})
})
output$mqttData <- renderPlot({
ggplot(mapping = aes(x = c(1:nrow(myData())), y = myData()$temperature)) +
geom_line() +
labs(x = "Second", y = "Celsius")
})
output$mqttData_RH <- renderPlot({
ggplot(mapping = aes(x = c(1:nrow(myData())), y = myData()$RH)) +
geom_line() +
labs(x = "Second", y = "RH %")
})
})
shinyApp(ui=ui,server=server)
One solution I found plot the charts in one renderPlot object. The flickering reduces.
output$mqttData <- renderPlot({
myData() %>%
gather('Var', 'Val', c(temperature, RH)) %>%
ggplot(aes(timestamp,Val, group = 1))+geom_line()+facet_grid(Var ~ ., scales="free_y")
})
However, I wonder if there is way to plot the charts separately without flickering / refreshing.
I found one github example put data to ggplot2 using pipe %>% (https://github.com/mokjpn/R_IoT) and modified it to plot separated charts.
library(shiny)
library(ggplot2)
library(tidyr)
# Dashboard-like layout
ui <- shinyServer(fluidPage(
fluidRow(
column(
6,
plotOutput("streaming_data_1")
),
column(
6,
plotOutput("streaming_data_2")
)
),
fluidRow(
column(
6,
plotOutput("streaming_data_3")
),
column(
6,
plotOutput("streaming_data_4")
)
)
))
server <- shinyServer(function(input, output, session){
myData <- reactiveVal()
# show the first and last timestamp in the streaming charts
realtime_graph_x_labels <- reactiveValues(first = "",last ="")
get_new_data <- function(){
epochTimeStamp <- as.character(as.integer(Sys.time()))
sensor_1 <- -runif(1,min = 10, max = 30)
sensor_2 <- runif(1,min = 0,max = 100)
sensor_3 <- runif(1,min = 0,max = 100000)
sensor_4 <- runif(1,min = 0,max = 10)
newData <- data.frame(ts = epochTimeStamp, val_1 = sensor_1, val_2 = sensor_2, val_3 = sensor_3, val_4 = sensor_4)
return(newData)
}
observe({
invalidateLater(1000, session)
isolate({
# fetch the new data
new_data <- get_new_data()
# If myData is empty, we initialize it with just the new data.
if(is.null(myData()))
{
myData(new_data)
realtime_graph_x_labels$first <- as.character(head(myData()$ts,1))
}
else # row bind the new data to the existing data, and set that as the new value.
myData(rbind(myData(),new_data))
realtime_graph_x_labels$last <- as.character(tail(myData()$ts,1))
})
})
# When displaying two charts, there is no flickering / refreshing, which is desired
output$streaming_data_1 <- renderPlot({
myData() %>%
ggplot(aes(ts,val_1, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 1") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
output$streaming_data_2<- renderPlot({
myData() %>%
ggplot(aes(ts,val_2, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 2") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
# When adding the 3rd chart, every charts start to flicker / refresh when ploting new value
output$streaming_data_3<- renderPlot({
myData() %>%
ggplot(aes(ts,val_3, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 3") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
output$streaming_data_4<- renderPlot({
myData() %>%
ggplot(aes(ts,val_4, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 4") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
})
shinyApp(ui=ui,server=server)
The solution works when there are only two charts and starts flickering / refreshing when adding the 3rd.
One possible cause may be that 1000ms is too short for the data to finish processing. Try invalidateLater(10000, session) for example, and see what happens.
I was unable to install mqtt with my R version, so I am unable to reproduce your behavior. However, I looked at your code and I think there is something you could do different to improve your code: Writing data to the global environment with <<- is usually not a good idea. What might be better suited is a reactiveVal, in which you can store data, and on which other functions take a dependency. So in the example below, I have created a reactiveVal and a corresponding observer that updates the reactiveVal every 1000ms.
Below is a working example, where I replaced the contents of your function with a simple one-liner for illustration purposes.
Hope this helps!
set.seed(1)
library(shiny)
ui <- fluidPage(
plotOutput("plotx")
)
server <- function(input, output, session){
# A reactiveVal that holds our data
myData <- reactiveVal()
# Our function to get new data
get_new_data <- function(){
data.frame(a=sample(seq(20),1),b=sample(seq(20),1))
}
# Observer that updates the data every 1000ms.
observe({
# invalidate every 1000ms
invalidateLater(1000, session)
isolate({
# fetch the new data
new_data <- get_new_data()
# If myData is empty, we initialize it with just the new data.
if(is.null(myData()))
myData(new_data)
else # row bind the new data to the existing data, and set that as the new value.
myData(rbind(myData(),new_data))
})
})
# Plot a histrogram
output$plotx <- renderPlot({
hist(myData()$a)
})
}
shinyApp(ui=ui,server=server)
EDIT based on new reproducible example. Seems like it just takes some time to create all the plots. You can add
tags$style(type="text/css", ".recalculating {opacity: 1.0;}")
to your app to prevent them from flickering. Working example:
library(shiny)
library(ggplot2)
library(tidyr)
# Dashboard-like layout
ui <- shinyServer(fluidPage(
tags$style(type="text/css", ".recalculating {opacity: 1.0;}"),
fluidRow(
column(
6,
plotOutput("streaming_data_1")
),
column(
6,
plotOutput("streaming_data_2")
)
),
fluidRow(
column(
6,
plotOutput("streaming_data_3")
),
column(
6,
plotOutput("streaming_data_4")
)
)
))
server <- shinyServer(function(input, output, session){
myData <- reactiveVal()
# show the first and last timestamp in the streaming charts
realtime_graph_x_labels <- reactiveValues(first = "",last ="")
get_new_data <- function(){
epochTimeStamp <- as.character(as.integer(Sys.time()))
sensor_1 <- -runif(1,min = 10, max = 30)
sensor_2 <- runif(1,min = 0,max = 100)
sensor_3 <- runif(1,min = 0,max = 100000)
sensor_4 <- runif(1,min = 0,max = 10)
newData <- data.frame(ts = epochTimeStamp, val_1 = sensor_1, val_2 = sensor_2, val_3 = sensor_3, val_4 = sensor_4)
return(newData)
}
observe({
invalidateLater(1000, session)
isolate({
# fetch the new data
new_data <- get_new_data()
# If myData is empty, we initialize it with just the new data.
if(is.null(myData()))
{
myData(new_data)
realtime_graph_x_labels$first <- as.character(head(myData()$ts,1))
}
else # row bind the new data to the existing data, and set that as the new value.
myData(rbind(myData(),new_data))
realtime_graph_x_labels$last <- as.character(tail(myData()$ts,1))
})
})
# When displaying two charts, there is no flickering / refreshing, which is desired
output$streaming_data_1 <- renderPlot({
myData() %>%
ggplot(aes(ts,val_1, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 1") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
output$streaming_data_2<- renderPlot({
myData() %>%
ggplot(aes(ts,val_2, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 2") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
# When adding the 3rd chart, every charts start to flicker / refresh when ploting new value
output$streaming_data_3<- renderPlot({
myData() %>%
ggplot(aes(ts,val_3, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 3") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
output$streaming_data_4<- renderPlot({
myData() %>%
ggplot(aes(ts,val_4, group = 1))+geom_line() +
scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
labs(title ="Sensor 4") +
theme(plot.margin = unit(c(1,4,1,1),"lines"))
})
})
shinyApp(ui=ui,server=server)
I would like to preselect some points in a ggiraph::renderggiraph() output.
I can make the following shiny app which allows me to select points and then use those selected points elsewhere like so:
dat <- data.table(x = 1:6, y = 1:6 %% 3, id = 1:6, status = rep(c('on','off'),3))
ui <- fluidPage( ggiraphOutput("plot"),
verbatimTextOutput("choices"))
server <- function(input, output, session){
output$plot <- renderggiraph({
p <- ggplot(dat ) +
geom_point_interactive(aes(x = x, y = y, data_id = id), size = 5) +
scale_color_manual(limits = c('on','off'),values = c('red','black'))
ggiraph(code = print(p),
hover_css = "fill:red;cursor:pointer;",
selection_type = "multiple",
selected_css = "fill:red;")
})
output$choices <- renderPrint({
input$plot_selected
})
}
shinyApp(ui = ui, server = server)
But sometimes I want to have certain points selected before I initialize the app.
For example, if the points 1, 3, and 5 are already "on" orginally, I would like the user to be able to turn them "off".
So my question is, is it possible to achieve something like this?
Yes, by using session$sendCustomMessage in session$onFlushed:
library(shiny)
library(ggiraph)
library(data.table)
library(magrittr)
dat <- data.table(x = 1:6, y = 1:6 %% 3, id = 1:6, status = rep(c('on','off'),3))
ui <- fluidPage( fluidRow(
column(width = 7,
ggiraphOutput("ggobj") ),
column(width = 5, verbatimTextOutput("choices"))
) )
server <- function(input, output, session){
output$ggobj <- renderggiraph({
p <- ggplot(dat ) +
geom_point_interactive(aes(x = x, y = y, data_id = id), size = 5) +
scale_color_manual(limits = c('on','off'),values = c('red','black'))
ggiraph(code = print(p),
hover_css = "fill:red;cursor:pointer;",
selection_type = "multiple",
selected_css = "fill:red;")
})
session$onFlushed(function(){
session$sendCustomMessage(type = 'ggobj_set', message = 1:3)
})
output$choices <- renderPrint({
input$ggobj_selected
})
}
shinyApp(ui = ui, server = server)
I would really appreciate some help with the following code:
library(shiny)
library(rhandsontable)
library(tidyr)
dataa <- as.data.frame(cbind(rnorm(100, sd=2), rchisq(100, df = 0, ncp = 2.), rnorm(100)))
ldataa <- gather(dataa, key="variable", value = "value")
thresholds <- as.data.frame(cbind(1,1,1))
ui <- fluidPage(fluidRow(
plotOutput(outputId = "plot", click="plot_click")),
fluidRow(rHandsontableOutput("hot"))
)
server <- function(input, output) {
values <- reactiveValues(
df=thresholds
)
observeEvent(input$plot_click, {
values$trsh <- input$plot_click$x
})
observeEvent(input$hot_select, {
values$trsh <- 1
})
output$hot = renderRHandsontable({
rhandsontable(values$df, readOnly = F, selectCallback = TRUE)
})
output$plot <- renderPlot({
if (!is.null(input$hot_select)) {
x_val = colnames(dataa)[input$hot_select$select$c]
dens.plot <- ggplot(ldataa) +
geom_density(data=subset(ldataa,variable==x_val), aes(x=value), adjust=0.8) +
geom_rug(data=subset(ldataa,variable==x_val), aes(x=value)) +
geom_vline(xintercept = 1, linetype="longdash", alpha=0.3) +
geom_vline(xintercept = values$trsh)
dens.plot
}
})
}
shinyApp(ui = ui, server = server)
I have a plot and a handsontable object in the app.
Clicking on whichever cell loads a corresponding plot, with a threshold value. Clicking the plot changes the position of one of the vertical lines.
I would like to get the x value from clicking the plot into the corresponding cell, and I would like to be able to set the position of the vertical line by typing in a value in the cell too.
I'm currently a bit stuck with how I should feed back values into a reactiveValue dataframe.
Many thanks in advance.
This works as I imagined:
(The trick was to fill right columns of "df" with input$plot_click$x by indexing them with values$df[,input$hot_select$select$c].)
library(shiny)
library(rhandsontable)
library(tidyr)
dataa <- as.data.frame(cbind(rnorm(100, sd=2), rchisq(100, df = 0, ncp = 2.), rnorm(100)))
ldataa <- gather(dataa, key="variable", value = "value")
thresholds <- as.data.frame(cbind(1,1,1))
ui <- fluidPage(fluidRow(
plotOutput(outputId = "plot", click="plot_click")),
fluidRow(rHandsontableOutput("hot"))
)
server <- function(input, output) {
values <- reactiveValues(
df=thresholds
)
observeEvent(input$plot_click, {
values$df[,input$hot_select$select$c] <- input$plot_click$x
})
output$hot = renderRHandsontable({
rhandsontable(values$df, readOnly = F, selectCallback = TRUE)
})
output$plot <- renderPlot({
if (!is.null(input$hot_select)) {
x_val = colnames(dataa)[input$hot_select$select$c]
dens.plot <- ggplot(ldataa) +
geom_density(data=subset(ldataa,variable==x_val), aes(x=value), adjust=0.8) +
geom_rug(data=subset(ldataa,variable==x_val), aes(x=value)) +
geom_vline(xintercept = 1, linetype="longdash", alpha=0.3) +
geom_vline(xintercept = values$df[,input$hot_select$select$c])
dens.plot
}
})
}
shinyApp(ui = ui, server = server)
Update your reactiveValue dataframe from inside of an observeEvent, where you are watching for whichever event is useful, i.e. a click or something.
observeEvent(input$someInput{
values$df <- SOMECODE})