I've been looking into ways to update a plot within an R Shiny application without having to re-render the whole plot. I'm working with temporal data which is animated via a Slider Input (animationOptions(playButton = TRUE)). The idea is to somehow highlight the part of the plot which is selected via the Slider Input. Re-rendering the whole plot at every animation step would make the whole application uselessly slow.
The most elegant solution with ggplot2 would have been, if shiny offered a way to add layers to the ggplot (e.g. + geom line()) and integrated this layer seamlessly into the plot without re-rendering it. Sadly, this does not seem to work. A bit of a hack could include creating a second ggplot-instance with exactly the same x/y-dimensions and overlapping the two plots.
EDIT:
I've just learnt that there are more javascript oriented plotting methods than ggplot2. For example, using dygraphs and adding a layer of dyShading, the selected area gets highlighted nicely. The basic question remains the same though, since changing the start- and end values of dyShading() seems to require re-rendering the whole plot.
library(shiny)
library(dygraphs)
library(xts)
data <- data.frame(
datetime = as.POSIXct("2016-06-20 17:00:00", tz = "UTC") + 1:100*60,
y = rnorm(100)
)
data_xts <- as.xts(data[,-1], data[,1])
minDatetime <- min(data$datetime)
maxDatetime <- max(data$datetime)
minY = min(data$y)
maxY = max(data$y)
plotlimits <- lims(x = c(minDatetime, maxDatetime), y = c(minY, maxY))
ui <- fluidPage(
sliderInput("timeslider", "Time Slider",
min = minDatetime,
max = maxDatetime,
value = c(minDatetime, minDatetime+10*60),
animate = animationOptions(interval=200)
),
dygraphOutput("dyplot")
)
server <- function(input, output) {
data_fil <- reactive({
data[data$datetime <= input$timeslider[2] & data$datetime >= input$timeslider[1],]
})
output$dyplot <- renderDygraph({
dygraph(data_xts) %>%
dyShading(
from = as.character(input$timeslider[1]),
to = as.character(input$timeslider[2]),
color = "tomato")
})
}
shinyApp(ui = ui, server = server)
Related
I developed app to support my hydrological modelling teaching. In it, students can select a gauging station to work with. In order to enhance the amount of information the students get from the app, I decided to update it including a map that shows the station and its associated catchment on top of a national map, together with a more detailed map like this:
Desired output
This is the code I am using (simplified to represent just my problem):
station_data <- read.csv("station_timeseries.csv",na.strings = "NaN",fileEncoding="UTF-8-BOM")
station_data$date <- as.Date(station_data$date,"%d/%m/%Y")
station_lat <- read.csv("station_coord.csv",fileEncoding="UTF-8-BOM")
GB_boundary <- st_read("./maps/GB_boundary.shp")
catchment_boundary <- st_read("./maps/Catchments.shp")
ui <- shinyUI(fluidPage(
sidebarPanel(
# Select station
selectInput(inputId = "station", label = strong("Select station"),
choices = unique(station_data$gauge_name))
),
tabPanel("Catchment characteristics",plotOutput(outputId = "Map"))
)
)
server <- shinyServer(function(input, output) {
## Catchment and station map ----
output$Map <- renderPlot({
station <- station_lat[station_lat$gauge_name==input$Station,]$gauge_id
bbox <- st_bbox(catchment_boundary[catchment_boundary$ID==station,])
large_map <- ggplot()+
geom_sf(data=GB_boundary,size=1,color="black",fill="cyan1")+
geom_sf(data=catchment_boundary[catchment_boundary$ID==station,],size=1,color="black",fill="red")+
theme_bw()
detail_map <- ggplot()+
geom_sf(data=GB_boundary,size=1,color="black",fill="cyan1")+
geom_sf(data=catchment_boundary[catchment_boundary$ID==station,],size=1,color="black",fill="red")+
coord_sf(xlim=c(bbox[1]-20000,bbox[3]+20000),ylim = c(bbox[2]-20000,bbox[4]+20000),expand = FALSE)+
ggtitle("Catchment and station location")+
theme_bw()
grid.arrange(large_map,
detail_map,
ncol = 2,
widths = c(2,5))
})
})
shinyApp(ui = ui, server = server)
I used the ggplot2 code you can see to generate the above image. However, when running the shiny app I get this:
Actual output
It seems to me like shiny only reads the first geom_sf command and disregarding the next two plots. Does anyone have experienced something similar? Any suggestions to solve this?
Edit: I added links to the data used in this example, stations and both maps.
Cheers!
David
Sorry if this question has been asked before, but I haven't quite found exactly what I was looking for by searching around.
I'm working on a small app to bring in scores from a debate activity and have those scores plotted in a scatterplot. I've figured out how to actually make the plot, but getting the data into it is a separate story.
I have all of my inputs as a numericInput with a unique input ID, but I haven't had any luck putting that data into its own frame.
I tried making a few different sets and then compiling all of those into one frame (using name1<-c(x, y z), name2<-c(a,b,c)... and then frame<-as.matrix(c(name1,name2....)) but it tells me that I'm attempting to do something that requires a reactive context.
If anyone knows how I could make my numericInputs drop into a dataframe, that would be lovely.
EDIT: I think I'm looking to make a reactive data table, but I'm not entirely sure. Again, any suggestions will be greatly appreciated!
Given the description of your problem, which is not that clear and do not show exactly what you need, this is what I recomment:
An app which takes 6 numerics inputs, combine all in a data frame with two columns and create a scatter plot with that data.
library(shiny)
library(ggplot2)
library(dplyr)
# 1 - The UI with a sidebar layuot
ui <- fluidPage(
sidebarLayout(
# A well panell to separate the values
# for the X axis
sidebarPanel = sidebarPanel(
wellPanel(
h3("X axis"),
numericInput('numb1', 'A', value = 1),
numericInput('numb2', 'B', value = 2),
numericInput('numb3', 'C', value = 3)
),
# A well panell to separate the values
# for the Y axis
wellPanel(
h3("Y axis"),
numericInput('numb4', 'A', value = 1),
numericInput('numb5', 'B', value = 2),
numericInput('numb6', 'C', value = 3)
)),
# Main panel with the plot
mainPanel = mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output, session) {
# Here You create a reactive objece with all the values
data <- reactive({
data.frame(
a = c(input$numb1, input$numb2, input$numb3),
b = c(input$numb4, input$numb5, input$numb6)
)
})
# plot output
output$plot <- renderPlot({
data() %>%
ggplot(aes(x = a, y = b)) +
geom_point()
})
}
shinyApp(ui, server)
Let me know if this is it
I'm writing an R shiny app which should allow the user to create customisable plots of some data. The idea is that my app offers a "create new plot" button, which renders the plot and stores it in a reactive. A renderUI function "watches" this list and renders all plots in that reactive.
I found a couple of related questions r-markdown-shiny-renderplot-list-of-plots-from-lapply or shiny-r-renderplots-on-the-fly which however did not really help in my case. I hope I didn't miss a good answer somewhere (which I would assume there is because I think this is not a rare use case).
When implementing, I noticed a strange behaviour: When there is only one plot to be shown, everything works well. However, when I have n (n>1) plots, instead of rendering plot 1, plot 2, ..., plot n, the app only showed n times the plot n.
See my example app below. I simplified the problem by just letting the user choose the number of plots to be displayed. The renderUI function then has a loop creating thees plots in a variable p and then calls renderPlot(p). I assume shiny does some caching and for some reason fails to recognise that p changes in the loop?!
I found a workaround by replacing the renderPlot(p) by do.call("renderPlot", list(expr = p). This does the job but I'm still curious to learn why the direct renderPlot does not work.
Here is my example app:
library(shiny)
library(ggplot2)
# Define UI
ui <- shinyUI(fluidPage(
titlePanel("renderPlot Test"),
sidebarLayout(
sidebarPanel(
numericInput(inputId = "n", label = "Number of Plots", value = 1L, min = 1L, max = 5L, step = 1L),
checkboxInput(inputId = "use_do.call", label = "use 'do.call'", value = FALSE)
),
mainPanel(
uiOutput("show_plots")
)
)
))
# Define server logic
server <- shinyServer(function(input, output) {
output$show_plots <- renderUI({
ui <- tags$div(tags$h4("Plots"))
for( i in 1:input$n ) {
p <- ggplot() + ggtitle(paste("plot", i))
if( input$use_do.call ) { # this works
ui <- tagAppendChild(ui, do.call("renderPlot", args=list(expr=p, width = 200, height = 200)))
} else { # this doesn't ...
ui <- tagAppendChild(ui, renderPlot(p, width = 200, height = 200))
}
}
return(ui)
})
})
# Run the application
shinyApp(ui = ui, server = server)
I agree with #JonMinton, and I've had the same problem. I've found that when I reuse the same variable to save the plots and render them (such as what you do with p), the plots get overwritten by the next plot and only the final plot is copied n times like you said.
To get around this, I define a new variable for each plot, which may not be sustainable for your project, but it is a workaround.
I am struggling with my first shiny app and having problems to make it work due to, I think, I am missing out something in the server function.
I have a R script that has two variables (the two reactive values in shiny) that creates a plot (a histogram) and a csv file with two columns (time and mm).
library(ggplot2)
**Pdmm** <- 125 # numeric input in shiny
**IndiceTorrencial** <- 10 # slider between 8 and 12 in shiny
DuracionAgua <- 24
IntervaloMin <- 60
IntervaloTiempo <- IntervaloMin/60
Intervalos <- DuracionAgua/IntervaloTiempo
t <- seq(1,Intervalos,IntervaloTiempo)
DF <- data.frame(t)
DF$I <- (**Pdmm**/24)*(**IndiceTorrencial**)^(((28^0.1)-(DF$t^0.1))/((28^0.1)-1)) # equation where the reactive values are running
DF$Pacu <- DF$t*DF$I
DF$Pmm <- c(DF$Pacu[1], diff(DF$Pacu, lag = 1))
DF$mm <- c(DF$Pmm[23],DF$Pmm[21],DF$Pmm[19],DF$Pmm[17], DF$Pmm[15],DF$Pmm[13],DF$Pmm[11],DF$Pmm[9],DF$Pmm[7],DF$Pmm[5],DF$Pmm[3],DF$Pmm[1],DF$Pmm[2],DF$Pmm[4],DF$Pmm[6],DF$Pmm[8],DF$Pmm[10],DF$Pmm[12],DF$Pmm[14],DF$Pmm[16],DF$Pmm[18],DF$Pmm[20],DF$Pmm[22],DF$Pmm[24])
ggplot(DF,aes(x=t,y=mm)) + geom_bar(stat = "identity",fill = "dodgerblue",color = "black") + scale_x_continuous(name = "t(h)", breaks = seq(1,24,1)) + scale_y_continuous(name = "mm")
My shiny app has, in its UI, a slider, a numeric input and a plot. It works, I will have time to make something better.
ui <- basicPage(
sliderInput(inputId = "coefTo",
label = h3("Torrentiality Coefficient"),
value = 10, min = 8, max = 12),
numericInput(inputId = "PmmS",
label = h3("Areal Precipitation"),
value = 100),
imageOutput("plot")
)
The problem is in the server function. I think I am sure that I have to use a reactive function in order to execute the code and to yield the results (histogram and csv). Also, to plot the histogram, I have a renderPlot.
server <- function(input, output, session){
data <- reactive({
DuracionAgua <- 24
IntervaloMin <- 60
IntervaloTiempo <- IntervaloMin/60
Intervalos <- DuracionAgua/IntervaloTiempo
t <- seq(1,Intervalos,IntervaloTiempo)
DF <- data.frame(t)
DF$I <- (input$PmmS/24)*(input$coefTo)^(((28^0.1)-(DF$t^0.1))/((28^0.1)-1))
DF$Pacu <- DF$t*DF$I
DF$Pmm <- c(DF$Pacu[1], diff(DF$Pacu, lag = 1))
DF$mm <- c(DF$Pmm[23],DF$Pmm[21],DF$Pmm[19],DF$Pmm[17], DF$Pmm[15],DF$Pmm[13],DF$Pmm[11],DF$Pmm[9],DF$Pmm[7],DF$Pmm[5],DF$Pmm[3],DF$Pmm[1],DF$Pmm[2],DF$Pmm[4],DF$Pmm[6],DF$Pmm[8],DF$Pmm[10],DF$Pmm[12],DF$Pmm[14],DF$Pmm[16],DF$Pmm[18],DF$Pmm[20],DF$Pmm[22],DF$Pmm[24])
DFm <- DF$mm
return(DFm)
})
output$plot <- renderPlot({
ggplot(data(DFm),aes(x=t,y=mm)) + geom_bar(stat = "identity",fill = "dodgerblue",color = "black") + scale_x_continuous(name = "t(h)", breaks = seq(1,24,1)) + scale_y_continuous(name = "mm")
})
}
When I run this script, I get the shiny app I have the slider and the numeric input, but not the plot getting an error message saying Error: unused argument (DFm). DFm is not created, so I assume that I am placing bad the code from my original script inside the shiny app, but I cannot figure out how to do it.
I have tried several things to make the server works, but I think that the script never runs inside the shiny app. I tried to create two reactive functions, one per each reactive value. I tried to put all the code from my original script out of the shiny leaving just the reactive values inside the shiny code... I tried with observe function as well.
Another problem I have is that I am not sure about the renderPlot. I am aware that I have to call the reactive function, data, but as I am not sure if it is well made.
I think that my server function is a total disaster. I have looked for examples in the shiny gallery and in Google, but I do not see something similar to help me.
Could anyone give a tip in order to fix my server function?
Many thanks in advance.
Following the advice of our colleague, I could solve my problem by setting DF, the first data frame I created, in return() inside the reactive function. Another confusion was how to set the object from the reactive function inside the renderplot.
I'm having some trouble creating interactive plots in shiny that facet my data.
Here's some code that shows what I want, but it uses ggplot2 which is not interactive.
library(shiny)
library(data.table)
library(ggplot2)
x <- 1:10000
dt <- data.frame(a = 1:100, b = sample(x,100,replace=T), c = sample(x,100,replace=T), d = sample(x,100,replace=T))
dt.molten <- melt(dt,id.vars="a")
ui <- fluidPage(
plotOutput("gplot")
)
server = function(input, output) {
output$gplot <- renderPlot({
ggplot(y.molten,aes(x = value)) +
geom_histogram(binwidth = 100000) +
facet_wrap( ~ variable,ncol = 1)
})
}
shinyApp(ui = ui, server = server)
In my actual app, the amount of facets varies, so I can't simply hard code in 3 separate plots using highcharter, ggvis,or plotly.
Ideally I'd like it to look something like this:
require(highcharter)
x <- stl(log(AirPassengers), "per")
hchart(x)
Except with histograms instead of time-series data.
The main issue is that the data i'm plotting stretches over like -3,000,000 to +3,000,000 with a high concentration around 0. This makes it hard to see the bars at the edges, so I'd like for users to be able to zoom into certain ranges of the plot without having to select it via some ui element.
I'm open to suggestions using any plotting method in R, although i'd like to stay away from rCharts.
EDIT: I did discover that using plotly::ggplotly almost achieved what I'm looking for, however it isn't very clean. I did g <- ggplot(*plot code*) then ggplotly(g). Works decent, but it'll take quite a bit of work to clean up I think.