I am using a brushed histogram to query samples in a shiny app. In my full application, I overlay a new histogram that highlights the selected region and update a DT data table showing properties of the filtered samples.
I've noticed that a reactive that depends on the brush gets called twice each time I move it. For example, the table_data reactive below gets called twice each time I brush the histogram.
app.R
library(ggplot2)
library(shiny)
df <- data.frame(x = rnorm(1000))
base_histogram <- ggplot(df, aes(x)) +
geom_histogram(bins = 30)
# Define UI for application that draws a histogram
ui <- fluidPage(
column(
plotOutput("histogram", brush = brushOpts(direction = "x", id = "brush", delay=500, delayType = "debounce")),
width = 6
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$histogram <- renderPlot({
p <- base_histogram
current <- table_data()
if (nrow(current) > 0) {
p <- p + geom_histogram(data = current, fill = "red", bins = 30)
}
p
})
table_data <- reactive({
print("called")
brushedPoints(df, input$brush)
})
}
# Run the application
shinyApp(ui = ui, server = server)
In this toy example, it's barely noticeable. But in my full app, a heavy calculation has to be done within the table_data reactive, and this the double call is unnecessarily slowing everything down.
Is there any way to structure the app so that the reactive only executes once whenever a brush is ended?
Here is a GIF that shows that the table_data is being executed twice per brush.
try this, only trigger once on each brush movement.
library(ggplot2)
library(shiny)
df <- data.frame(x = rnorm(1000))
base_histogram <- ggplot(df, aes(x)) +
geom_histogram(bins = 30)
# Define UI for application that draws a histogram
ui <- fluidPage(
column(
plotOutput("histogram", brush = brushOpts(direction = "x", id = "brush", delay=500, delayType = "debounce")),
width = 6
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$histogram <- renderPlot({
p <- base_histogram
if(!is.null(table_data())) {
p <- p + geom_histogram(data = table_data(), fill = "red", bins = 30)
}
p
})
table_data <- reactive({
if(is.null(input$brush)) return()
print("called")
brushedPoints(df, input$brush)
})
}
shinyApp(ui, server)
Related
For the first time I really can't find this answer here already, so I hope you all can help me, I'm sure there is a pretty easy fix.
I am making a Shiny volcano plot with clickable points to give me a table with the data about that point. If I use a trans function (that I found here, thank you helpful stranger) within scale_y_continuous() in my plot, points in the scaled region are no longer clickable. How can I scale the axis this way and still be able to have the clickable points?
My code, with some fake data that has the same problem:
## Read in necessary libraries, function, and data
library(shiny)
library(ggplot2)
library(dplyr)
library(scales)
reverselog_trans <- function(base = exp(1)) {
trans <- function(x) -log(x, base)
inv <- function(x) base^(-x)
trans_new(paste0("reverselog-", format(base)), trans, inv,
log_breaks(base = base),
domain = c(1e-100, Inf))
}
pretend_data <- tibble(data=1:5, estimate = runif(5, min = -1, max = 2), plot = c(1e-50, 2e-35, 5e-1, 1, 50))
# Define UI for application that draws a volcano plot
ui <- fluidPage(
# Application title
titlePanel("Pretend Plot"),
plotOutput("plot", click = "plot_click"),
tableOutput("data")
)
# Define server logic required to draw a volcano plot
server <- function(input, output, session) {
output$plot <- renderPlot({
ggplot(data = pretend_data, aes(x=estimate, y=plot)) +
geom_vline(xintercept=c(-1, 1), linetype=3) +
geom_hline(yintercept=0.01, linetype=3) +
geom_point() +
scale_y_continuous(trans = reverselog_trans(10))
}, res = 96)
output$data <- renderTable({
req(input$plot_click)
nearPoints(pretend_data, input$plot_click)
})
}
# Run the application
shinyApp(ui = ui, server = server)
The problem is that input$plot_click returns the coordinates on the transformed scale. nearPoints tries then to match those to the original scale which does not work.
You have a couple of options though:
Transform the data yourself and adapt y axis ticks via scale_y_continuous
Adapt pretend_data in the nearPoints call.
Option 1
This requires that you control y axis tick marks yourself and would need some more fiddling to get the exact same reuslts as in your example.
pretend_data_traf <- pretend_data %>%
mutate(plot = reverselog_trans(10)$transform(plot))
# Define UI for application that draws a volcano plot
ui <- fluidPage(
# Application title
titlePanel("Pretend Plot"),
plotOutput("plot", click = "plot_click"),
tableOutput("data")
)
# Define server logic required to draw a volcano plot
server <- function(input, output, session) {
output$plot <- renderPlot({
ggplot(data = pretend_data_traf, aes(x=estimate, y=plot)) +
geom_vline(xintercept=c(-1, 1), linetype=3) +
geom_hline(yintercept=0.01, linetype=3) +
geom_point() +
## would need to define breaks = to get same tick mark positions
scale_y_continuous(labels = reverselog_trans(10)$inverse)
}, res = 96)
output$data <- renderTable({
req(input$plot_click)
nearPoints(pretend_data_traf, input$plot_click)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Option 2
pretend_data_traf <- pretend_data %>%
mutate(plot = reverselog_trans(10)$transform(plot))
# Define UI for application that draws a volcano plot
ui <- fluidPage(
# Application title
titlePanel("Pretend Plot"),
plotOutput("plot", click = "plot_click"),
tableOutput("data")
)
# Define server logic required to draw a volcano plot
server <- function(input, output, session) {
output$plot <- renderPlot({
ggplot(data = pretend_data, aes(x=estimate, y=plot)) +
geom_vline(xintercept=c(-1, 1), linetype=3) +
geom_hline(yintercept=0.01, linetype=3) +
geom_point() +
scale_y_continuous(trans = reverselog_trans(10))
}, res = 96)
output$data <- renderTable({
req(input$plot_click)
nearPoints(pretend_data_traf, input$plot_click) %>%
mutate(plot = reverselog_trans(10)$inverse(plot))
})
}
# Run the application
shinyApp(ui = ui, server = server)
What I am trying to achieve is to handle dynamically generated UI elements with names based on a counter that is triggered on a button click. This works fine, but I cannot compose the names of these output elements using assign(). Here is a simple example that demonstrates the problem:
library(shiny)
ui <- fluidPage(
actionButton("run_btn", "Run"),
plotOutput('Plot1'),
plotOutput('Plot2'),
plotOutput('Plot3')
)
server <- function(input, output, clientData, session) {
observeEvent(input$run_btn, {
myplot <- renderPlot({
boxplot(1:100)
})
assign(paste('output$Plot', sep = "", input$run_btn), myplot) # DOES NOT WORK!
# output$Plot1 <- myplot # THIS WORKS!
})
}
# Run the application
shinyApp(ui = ui, server = server)
I'm inferring that you want to stack new plots in some fashion, additive, potentially with some cleanup?
Actions:
Press the Run button, it creates a plot of mtcars with a random car highlighted.
Repeat this multiple times, each time a new plot is added, stacked before/above all other plots.
Press the Trim button, and all plots except the most-recent are removed from the UI completely.
library(shiny)
ui <- fluidPage(
actionButton("trim_btn", "Trim"),
actionButton("run_btn", "Run")
)
someplot <- function(nm) {
rand <- sample(nrow(mtcars), size = 1)
plot(disp ~ mpg, data = mtcars, main = paste(nm, "-", rownames(mtcars)[rand]), pch = 16, cex = 1)
points(disp ~ mpg, data = mtcars[rand,,drop=FALSE], pch = 16, cex = 2, col = "red")
}
server <- function(input, output, session) {
idcount <- reactiveVal(0)
observeEvent(input$run_btn, {
thisid <- idcount() + 1
idcount(thisid)
thisid <- paste0("plot", thisid)
insertUI(selector = "#run_btn", where = "afterEnd",
ui = plotOutput(thisid))
output[[thisid]] <- renderPlot({ someplot(thisid) })
})
observeEvent(input$trim_btn, {
curid <- idcount() - 1
if (curid > 0) {
selectors <- paste0("#plot", seq_len(curid))
# this could be improved to only remove existing selectors
for (sel in selectors) removeUI(selector = sel)
}
})
}
# # Run the application
shinyApp(ui = ui, server = server)
I am setting up a small shiny app where I do not want the plot to change unless the action button is clicked. In the example below, when I first run the app, there is no plot until I click the action button. However, if I then change my menu option in the drop-down from Histogram to Scatter, the scatter plot is automatically displayed even though the value for input$show_plot has not changed because the action button has not been clicked.
Is there a way that I can change my menu selection from Histogram to Scatter, but NOT have the plot change until I click the action button? I've read through several different posts and articles and can't seem to get this worked out.
Thanks for any input!
ui.R
library(shiny)
fluidPage(
tabsetPanel(
tabPanel("Main",
headerPanel(""),
sidebarPanel(
selectInput('plot_type', 'Select plot type', c('Histogram','Scatter'), width = "250px"),
actionButton('show_plot',"Plot", width = "125px"),
width = 2
),
mainPanel(
conditionalPanel(
"input.plot_type == 'Histogram'",
plotOutput('plot_histogram')
),
conditionalPanel(
"input.plot_type == 'Scatter'",
plotOutput('plot_scatter')
)
))
)
)
server.R
library(shiny)
library(ggplot2)
set.seed(10)
function(input, output, session) {
### GENERATE SOME DATA ###
source_data <- reactive({
mydata1 = as.data.frame(rnorm(n = 100))
mydata2 = as.data.frame(rnorm(n = 100))
mydata = cbind(mydata1, mydata2)
colnames(mydata) <- c("value1","value2")
return(mydata)
})
# get a subset of the data for the histogram
hist_data <- reactive({
data_sub = as.data.frame(source_data()[sample(1:nrow(source_data()), 75), "value1"])
colnames(data_sub) <- "value1"
return(data_sub)
})
# get a subset of the data for the scatter plot
scatter_data <- reactive({
data_sub = as.data.frame(source_data()[sample(1:nrow(source_data()), 75),])
return(data_sub)
})
### MAKE SOME PLOTS ###
observeEvent(input$show_plot,{
output$plot_histogram <- renderPlot({
isolate({
plot_data = hist_data()
print(head(plot_data))
p = ggplot(plot_data, aes(x = value1, y = ..count..)) + geom_histogram()
return(p)
})
})
})
observeEvent(input$show_plot,{
output$plot_scatter <- renderPlot({
isolate({
plot_data = scatter_data()
print(head(plot_data))
p = ggplot(plot_data, aes(x = value1, y = value2)) + geom_point()
return(p)
})
})
})
}
Based on your desired behavior I don't see a need for actionButton() at all. If you want to change plots based on user input then the combo of selectinput() and conditionPanel() already does that for you.
On another note, it is not good practice to have output bindings inside any reactives. Here's an improved version of your server code. I think you are good enough to see notice the changes but comment if you have any questions. -
function(input, output, session) {
### GENERATE SOME DATA ###
source_data <- data.frame(value1 = rnorm(n = 100), value2 = rnorm(n = 100))
# get a subset of the data for the histogram
hist_data <- reactive({
# reactive is not needed if no user input is used for creating this data
source_data[sample(1:nrow(source_data), 75), "value1", drop = F]
})
# get a subset of the data for the histogram
scatter_data <- reactive({
# reactive is not needed if no user input is used for creating this data
source_data[sample(1:nrow(source_data), 75), , drop = F]
})
### MAKE SOME PLOTS ###
output$plot_histogram <- renderPlot({
req(hist_data())
print(head(hist_data()))
p = ggplot(hist_data(), aes(x = value1, y = ..count..)) + geom_histogram()
return(p)
})
output$plot_scatter <- renderPlot({
req(scatter_data())
print(head(scatter_data()))
p = ggplot(scatter_data(), aes(x = value1, y = value2)) + geom_point()
return(p)
})
}
I have a Shiny app in which I have a network graph rendered using ggraph, something similar to the app below:
library(ggraph)
library(igraph)
library(shiny)
ui <- fluidPage(
plotOutput("plot", brush = brushOpts(id = "plot_brush"))
)
server <- function(input, output) {
graph <- graph_from_data_frame(highschool)
output$plot <- renderPlot({
ggraph(graph) +
geom_edge_link(aes(colour = factor(year))) +
geom_node_point()
})
observe(print(
brushedPoints(as_data_frame(graph, what = "vertices"), input$plot_brush)
)
)
}
shinyApp(ui, server)
What I'm looking to do is when you click and drag in a chart so that some nodes are captured, I can examine more information about those specific points captured. For now, I'm just using observe({print()}) so that I can see in the console what gets captured.
My problem, whenever I select an area in the app, I get 0 rows returned in the console, no matter how many nodes are included in the area selected. How do I make it return the nodes included in the area selected?
This response showed me the way:
library(ggraph)
library(igraph)
library(shiny)
library(dplyr)
ui <- fluidPage(
plotOutput("plot", brush = brushOpts(id = "plot_brush"))
)
server <- function(input, output) {
graph2 <- graph_from_data_frame(highschool)
set.seed(2017)
p <- ggraph(graph2, layout = "nicely") +
geom_edge_link() +
geom_node_point()
plot_df <- ggplot_build(p)
coords <- plot_df$data[[2]]
output$plot <- renderPlot(p)
coords_filt <- reactive({
if (is.null(input$plot_brush$xmin)){
coords
} else {
filter(coords, x >= input$plot_brush$xmin,
x <= input$plot_brush$xmax,
y >= input$plot_brush$ymin,
y <= input$plot_brush$ymax)
}
})
observe(print(
coords_filt()
)
)
}
shinyApp(ui, server)
Below is functioning code for a basic shiny app that allows the user to pick a column and then plots a ggplot::histogram() of the selected column:
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
titlePanel("ggplot"),
sidebarLayout(
sidebarPanel(
uiOutput("column_select")
),
mainPanel(plotOutput("plot"))
)
)
# Define server logic required to draw a histogram
server <- function(input, output){
dat <- reactive({iris})
output$column_select <- renderUI({selectInput("col", label = "column", choices = as.list(names(iris)), selected = "Sepal.Length")})
output$plot <- renderPlot({ggplot(dat(), aes_string(x = input$col)) +
geom_histogram()})
p <- ggplot(dat(), aes_string(x = input$col)) +
geom_histogram()
renderPlot
}
# Run the application
shinyApp(ui = ui, server = server)
I am not sure, however, why I am unable to remove the ggplot() function from within renderPlot() and still get the same result. I have tried:
p <- reactive({ggplot(dat(), aes_string(x = input$col)) +
geom_histogram()})
outputPlot <- renderPlot({p})
But this results in no plot being drawn.
I assume there is a simple fix to this, but thus far it escapes me.