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)
})
}
Related
I want to create a clickable histogram in shiny but I don't know if it is possible.
Some months ago I saw a clickable volcano plot which gives you a table of what you click.
Source: https://2-bitbio.com/2017/12/clickable-volcano-plots-in-shiny.html
The closest post that I found about creating clickable histograms is this one Click to get coordinates from multiple histogram in shiny
However, I don't want to get the coordinates. I want the rownames of the dataframe.
Having this dataframe, can I get the rownames everytime I click a bar from the histogram?
mtcars <- mtcars %>%
select("hp")
mtcars <- as.matrix(mtcars)
One example (but not clickable) in shiny:
library(shiny)
library(ggplot2)
library(scales)
library(dplyr)
ui <- fluidPage(
titlePanel("Histogram"),
sidebarLayout(
sidebarPanel(
),
mainPanel(
plotOutput("hist"),
)
)
)
mtcars <- mtcars %>%
select("hp")
mtcars <- as.matrix(mtcars)
server <- function(input, output) {
output$hist <- renderPlot({
pp <- qplot(mtcars, geom = "histogram", bins = 10, xlab="values",
ylab="Frequency", main="Histogram",
fill=I("red"), col=I("black"), alpha=I(0.4))
pp + scale_x_continuous(breaks=pretty(mtcars, n=10))
})
}
shinyApp(ui = ui, server = server)
Does anyone know how to do it?
Thanks very much in advance!
Regards
This is a great question, and what makes it challenging is that the qplot/ggplot charts are static images. The below app.r is an example of how I would do it. I'd love to see other approaches.
In essence:
Create a sequence of numbers that will be used both as the breaks in your histogram and as intervals in your dataframe. I based these on user inputs, but you could hardcode them.
Assign a "bin" value to each row in the dataframe based on the interval in which the value falls.
Record the x-coordinate from the user's click event and assign that a "bin" value based on the same set of intervals.
Subset your dataframe and retain only those records where the "bin" value of the data matches the "bin" value of the x-coordinate from the user's click event.
Otherwise, if you're willing to go the d3 route, you could explore something like this posted by R Views.
#Load libraries ----------------------------------------------------
library(shiny)
library(ggplot2)
library(scales)
library(dplyr)
# Prepare data -----------------------------------------------------
df <- mtcars
df <- cbind(model = rownames(df), data.frame(df, row.names = NULL)) # setting the rownames as the first column
dm <- df$hp %>% as.matrix()
# UI function ------------------------------------------------------
ui <- fluidPage(
titlePanel("Histogram"),
sidebarLayout(
sidebarPanel(
tags$h5("I added the below text output only to demonstrate shiny's way for tracking user interaction on static plots. You can click, double-click, or click & drag (i.e. brushing). These functions are AWESOME when exploring scatterplots."),
tags$h3("Chart click and brushing"),
verbatimTextOutput("info"),
tags$h5("Now I'm applying the below UI inputs to the `vec` and `breaks` arguments in `findInterval()` and `qplot()` respectively; I'm using `findInterval()` to bin the values in the dataframe AND to bin the x-value of the user's click event input on the chart. Then we can return the dataframe rows with the same bin values as the x-value of the click input."),
sliderInput("seq_from_to"
, label = h3("Sequence 'From' and 'To'")
, min = 0
, max = 500
, value = c(50, 350)
),
sliderInput("seq_by"
, label = h3("Sequence 'By'")
, min = 25
, max = 200
, value = 50
, step = 5)
),
mainPanel(
plotOutput("hist",
click = "plot_click",
dblclick = "plot_dblclick",
hover = "plot_hover",
brush = "plot_brush"),
dataTableOutput("table")
)
)
)
# Server function --------------------------------------------------
server <- function(input, output) {
# Render Histogram Plot
output$hist <- renderPlot({
# Using the same `qplot` function but inserting the user inputs to set the breaks values in the plot
pp <- qplot(dm
, geom = "histogram"
, breaks = seq(from = input$seq_from_to[1], to = input$seq_from_to[2], by = input$seq_by)
, xlab = "values"
, ylab = "Frequency"
, main = "Histogram"
, fill = I("red")
, col = I("black")
, alpha = I(0.4)
)
# Also using the user inputs to set the breaks values for the x-axis
pp + scale_x_continuous(breaks = seq(from = input$seq_from_to[1], to = input$seq_from_to[2], by = input$seq_by))
})
# This is purely explanatory to help show how shiny can read user interaction on qplot/ggplot objects
# It's taken from the Shiny docs here: https://shiny.rstudio.com/articles/plot-interaction.html
output$info <- renderText({
# Retain the x and y coords of the user click event data
xy_str <- function(e) {
if(is.null(e)) return("NULL\n")
paste0("x=", round(e$x, 1), " y=", round(e$y, 1), "\n")
}
# Retain the x and y range coords of click & drag (brush) data
xy_range_str <- function(e) {
if(is.null(e)) return("NULL\n")
paste0("xmin=", round(e$xmin, 1), " xmax=", round(e$xmax, 1),
" ymin=", round(e$ymin, 1), " ymax=", round(e$ymax, 1))
}
# Paste this together so we can read it in the UI function for demo purposes
paste0(
"click: ", xy_str(input$plot_click),
"dblclick: ", xy_str(input$plot_dblclick),
"hover: ", xy_str(input$plot_hover),
"brush: ", xy_range_str(input$plot_brush)
)
})
# Back to the story. Set a listener to trigger when one of the following is updated:
toListen <- reactive({list(
input$plot_click # user clicks on the plot
, input$seq_from_to # user updates the range slider
, input$seq_by # user updates the number input
)
})
# When one of those events are triggered, update the datatable output
observeEvent(toListen(), {
# Save the user click event data
click_data <- input$plot_click
print(click_data) # during your app preview, you can watch the R Console to see what click data is accessible
# Assign bin values to each row using the intervals that are set by the user input
df$bin <- findInterval(dm, vec = seq(from = input$seq_from_to[1], to = input$seq_from_to[2], by = input$seq_by))
# Similarly assign a bin value to the click event based on what interval the x values falls within
click_data$x_bin <- findInterval(click_data$x, vec = seq(from = input$seq_from_to[1], to = input$seq_from_to[2], by = input$seq_by))
# Lastly, subset the df to only those records within the same interval as the click event x-value
df_results <- subset(df, bin == click_data$x_bin)
# Select what values to view in the table
df_results <- df_results %>% select(model, hp)
# And push these back out to the UI
output$table <- renderDataTable(df_results,
options = list(
pageLength = 5
)
)
})
}
shinyApp(ui = ui, server = server)
Well, someone answered. Since I took the time to put it together, here is another potential solution.
library(shiny)
library(ggplot2)
library(scales)
library(dplyr)
library(DescTools) # added for Closest()
ui <- fluidPage(
titlePanel("Histogram"),
sidebarLayout(
sidebarPanel(
),
mainPanel(
plotOutput("hist", click = 'plot_click'), # added plot_click
verbatimTextOutput("x_value"), # added queues for interactivity
verbatimTextOutput("selected_rows") # added table for bin values
)
)
)
# this can be a dataframe or matrix for qplot or ggplot
# (not sure if there was another reason you had this code?)
# mtcars <- mtcars %>%
# select("hp") # if you only want hp
# mtcars <- as.matrix(mtcars) # I suggest making row names a column
# to keep 2 columns
pp <- ggplot(mtcars) +
geom_histogram(aes(x = hp),
bins = 10,
fill = "red",
color = "black",
alpha = .4) +
labs(x = "values",
y = "Frequency",
title = "Histogram")
# extract data from plot to find where each value falls within the histogram bins
# I kept the pkg name, function in more than one library
bd <- ggplot_build(ggplot2::last_plot())$data[[1]]
# add the assigned bin number to the mtcars frame; used for filtering matches
mtcars$bins <- lapply(mtcars$hp,
function(y) {
which(bd$x == Closest(bd$x, y))
}) %>% unlist()
server <- function(input, output) {
output$hist <- renderPlot({
# moved the plot outside of server, so that global variables could be created
# pp <- qplot(mtcars[,"hp"], geom = "histogram", bins = 10, xlab="values",
# ylab = "Frequency", main = "Histogram",
# fill = I("red"), col = I("black"), alpha = I(0.4))
# scale_x_continuous(breaks=pretty(mtcars, n=10)) # can't use this
pp
})
# # Print the name of the x value # added all that's below with server()
output$x_value <- renderPrint({
if (is.null(input$plot_click$x)) return()
# find the closest bin center to show where the user clicked on the histogram
cBin <- which(bd$x == Closest(bd$x, input$plot_click$x))
paste0("You selected bin ", cBin) # print out selected value based on bin center
})
# Print the rows of the data frame which match the x value
output$selected_rows <- renderPrint({
if (is.null(input$plot_click$x)) return()
# find the closest bin center to show where the user clicked on the histogram
cBin <- which(bd$x == Closest(bd$x, input$plot_click$x))
mtcars %>% filter(bins == cBin)
# mtcars
})
}
shinyApp(ui = ui, server = server)
Just in case someone ends in this post looking a way to include brushedPoints... inspired on this post, I found a way to do it!
Code:
#Load libraries ----------------------------------------------------
library(shiny)
library(ggplot2)
library(scales)
library(dplyr)
# Prepare data -----------------------------------------------------
df <- mtcars
df <- cbind(model = rownames(df), data.frame(df, row.names = NULL)) # setting the rownames as the first column
breaks_data = pretty(mtcars$hp, n=10)
my_breaks = seq(min(breaks_data), to=max(breaks_data), by=30)
# UI function ------------------------------------------------------
ui <- fluidPage(
titlePanel("Histogram"),
sidebarLayout(
sidebarPanel(
actionButton("draw_plot", "Draw the plot")
),
mainPanel(
plotOutput("hist",
brush = brushOpts("plot_brush", resetOnNew = T, direction = "x")),
dataTableOutput("table"),
)
)
)
# Server function --------------------------------------------------
server <- function(input, output) {
observeEvent(input$plot_brush, {
info_plot <- brushedPoints(df, input$plot_brush)
output$table <- renderDataTable(info_plot)
})
# If the user didn't choose to see the plot, it won't appear.
output$hist <- renderPlot({
df %>% ggplot(aes(hp)) +
geom_histogram(alpha=I(0.4), col = I("black"), fill = I("red"), bins=10) +
labs(x = "values",
y = "Frequency",
title = "Histogram") +
scale_x_continuous(breaks = my_breaks)
})
}
shinyApp(ui = ui, server = server)
How to do a scatterplot with hover
library(shiny)
library(tidyverse)
ui <- fluidPage(
titlePanel("hover tooltips demo"),
mainPanel(
plotOutput("plot1", hover = hoverOpts(id = "plot_hover", delay = 100, delayType = "debounce")),
uiOutput("hover_info") # , style = "pointer-events: none")
)
)
server <- function(input, output) {
output$plot1 <- renderPlot({
mtcars %>%
ggplot(aes(mpg, hp)) +
geom_point()
})
output$hover_info <- renderUI({
hover <- input$plot_hover
point <- shiny::nearPoints(mtcars,
coordinfo = hover,
xvar = 'mpg',
yvar = 'hp',
threshold = 20,
maxpoints = 1,
addDist = TRUE)
if (nrow(point) == 0) return(NULL)
style <- paste0("position:absolute; z-index:100; background-color: #3c8dbc; color: #ffffff;",
"font-weight: normal; font-size: 11pt;",
"left:", hover$coords_css$x + 5, "px;",
"top:", hover$coords_css$y + 5, "px;")
wellPanel(
style = style,
p(HTML(paste0("Some info about car: <br/>MPG ", point$mpg, "<br/>HP ", point$hp)))
)
})
}
shinyApp(ui = ui, server = server)
I am teaching myself r and shiny and trying to make an interactive bar chart where the user can change the chart based on columns. I keep getting errors with this code. Any help would be appreciated! My data has four columns: v, one, two, three. The first column is characters and the last three are numbers. I want to change the bar chart based on the y axis (columns: one, two and three). Right now, the error I am getting is: missing value where TRUE/FALSE needed.
library(shiny)
library(readr)
library(ggplot2)
data <- read.csv('scoring.csv')
data$v <- as.character(data$v)
ui <- fluidPage(
titlePanel("Scoring"),
sidebarPanel(
selectInput(inputId = "scoring", label = "Select a score:", c("Scoring Method 1", "Scoring Method 2", "Scoring Method 3"))),
mainPanel(
plotOutput(outputId = "bar")
)
)
#browser()
server <- function(input, output) {
new_data <- reactive({
selected_score = as.numeric(input$"scoring")
if (selected_score == "Scoring Method 1"){(data[data$one])}
if (selected_score == "Scoring Method 2"){(data[data$two])}
if (selected_score == "Scoring Method 3"){(data[data$three])}
})
#browser()
output$bar <- renderPlot({
newdata <- new_data()
p <- ggplot(newdata, aes(x=reorder(v, -selected_score), selected_score, y = selected_score, fill=v)) +
geom_bar(stat = 'identity', fill="darkblue") +
theme_minimal() +
ggtitle("Sports")
barplot(p, height = 400, width = 200)
})
}
Run the application
shinyApp(ui = ui, server = server)
You have a few errors in your code. In the server part, please use input$scoring, instead of input$"scoring".
First, in ui selectInput could be defined as
selectInput(inputId = "scoring", label = "Select a score:", c("Scoring Method 1"="one",
"Scoring Method 2"="two",
"Scoring Method 3"="three")))
Second, your reactive dataframe new_data() could be defined as shown below:
new_data <- reactive({
d <- data %>% mutate(selected_score = input$scoring)
d
})
Third, ggplot could be defined as
output$bar <- renderPlot({
newdata <- new_data()
p <- ggplot(newdata, aes(x=v, y = newdata[[as.name(selected_score)]], fill=v)) +
geom_bar(stat = 'identity', position = "dodge", fill="blue") +
theme_bw() +
#scale_fill_manual(values=c("blue", "green", "red")) +
scale_y_continuous(limits=c(0,10)) +
ggtitle("Sports")
p
})
Please note that you had an extra selected_score variable within aes. My suggestion would be to play with it to reorder x, and review some online or youtube videos on R Shiny.
When I create a group of linked boxplots( selecting points in one boxplot highlights the corresponding points in all boxplots), the boxplots keep updating themselves for a uncertain amount of times (sometimes only once but sometimes up to 20 times).
Please run the following sample code.
I believe the source of problem is the geom_jitter(). Is there any way to stop the boxplots from updating themselves? Thanks.
library(shiny)
library(ggplot2)
server <- function(input, session, output) {
X = data.frame(x1 = rnorm(1000),
x2 = rnorm(1000),
week = sample(LETTERS[1:10],1000,replace = TRUE)
)
D = reactive({
brushedPoints(X,input$brush_1, allRows = TRUE)
})
output$p1 = renderPlot({
set.seed(123)
ggplot(D(),aes(x=week,y=x1))+
geom_boxplot() +
geom_jitter(aes(color=selected_))+
scale_color_manual(values = c("black","red"),guide=FALSE)
})
output$p2 = renderPlot({
set.seed(123)
ggplot(D(),aes(x=week,y=x2))+
geom_boxplot() +
geom_jitter(aes(color=selected_))+
scale_color_manual(values = c("black","red"),guide=FALSE)
})
}
ui <- fluidPage(
splitLayout(
plotOutput("p1",brush = "brush_1"),
plotOutput("p2",brush = "brush_1")
)
)
shinyApp(ui = ui, server = server)
Update: 2016-9-16
I tried replacing geom_jitter with geom_point, but the charts still keep updating themselves.
So geom_jitter may not be the suspect.
So what is the source of problem on earth?
library(shiny)
library(ggplot2)
server <- function(input, session, output) {
X = data.frame(x1 = rnorm(1000),
x2 = rnorm(1000),
week = sample(LETTERS[1:10],1000,replace = TRUE)
)
vals <- reactiveValues(
keeprows = rep(TRUE,nrow(X))
)
D = reactive({
R=cbind(X,vals$keeprows)
#print(sum(R[,"vals$keeprows"]==TRUE))
R
})
output$p1 = renderPlot({
set.seed(123)
ggplot(D(),aes(x=week,y=x1))+
geom_boxplot() +
geom_jitter(aes(colour=vals$keeprows))+
scale_color_manual(values = c("black","red"),guide=FALSE)
})
output$p2 = renderPlot({
set.seed(123)
ggplot(D(),aes(x=week,y=x2))+
geom_boxplot() +
geom_jitter(aes(color=vals$keeprows))+
scale_color_manual(values = c("black","red"),guide=FALSE)
})
observeEvent(input$brush_1,{
Res=brushedPoints(X,input$brush_1,allRows = TRUE)
vals$keeprows = Res$selected_
})
observeEvent(input$brush_2,{
Res=brushedPoints(X,input$brush_2,allRows = TRUE)
vals$keeprows = Res$selected_
})
observeEvent(input$exclude_reset,{
vals$keeprows = rep(TRUE,nrow(X))
})
}
ui <- fluidPage(
actionButton("exclude_reset","Reset"),
splitLayout(
plotOutput("p1",brush = brushOpts("brush_1",resetOnNew = TRUE)),
plotOutput("p2",brush = brushOpts("brush_2",resetOnNew = TRUE))
)
)
shinyApp(ui = ui, server = server)
guess the reason is source table always keep updating when you brush one of plots. cause all of your plots use the same brush id, can not identify which input$brush_1 is the real "brush" action. One chart has been brushed,input$brush_1 changed and reactive table D will be updated as well. Another plot based on the new reactive table plot again and make the input$brush_1 changed again...
From above thinking, based on your code, made a new one to distinguish the input brush action from different plots. the problem that boxplots keep updating themselves for a uncertain amount of times seems be solved. pls try below code:
I am trying to use Shiny to select variables I want to plot in a multi-line chart rendered using Plotly. I have many variables so I want to select using Shiny instead of using Plotly's interactive legend "click" selection mechanism.
Example Data:
library(plotly)
# Example dataframe
foo <-data.frame( mon = c("Jan", "Feb", "Mar"),
var_1 = c(100, 200, 300),
var_b = c(80, 250, 280),
var_three = c(150, 120,201)
)
When using Plotly directly I can manually add traces using code like this:
p <- plot_ly(x = foo$mon, y = foo$var_1, line = list(shape="linear"))
p <- add_trace(p, x = foo$mon, y = foo$var_b)
p <- add_trace(p, x = foo$mon, y = foo$var_three)
print(p)
Now I want to use a Shiny checkbox to select the variables I wish to see on the plot. The selection is captured in input$show_vars , but how do I loop through and plot this changing list of variables? Here is my app.R code that manually plots one of the variables. Suggestions appreciated!
#------------------------------------------------------------------------------
# UI
#------------------------------------------------------------------------------
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxGroupInput('show_vars', 'Columns in the dataset', names(foo),
selected = c('mon', 'var_1')),
helpText('Select the variables to show in the graph.')
),
mainPanel(
plotlyOutput("myPlot")
)
)
)
#------------------------------------------------------------------------------
# SERVER
# Need to loop through input$show_vars to show a trace for each one?
#------------------------------------------------------------------------------
server <- function(input, output) {
# a large table, reative to input$show_vars
output$uteTable = renderDataTable({
library(ggplot2)
ute[, input$show_vars, drop = FALSE]
})
output$myPlot = renderPlotly({
plot_ly(x=foo$mon, y=foo$var_1, line = list(shape="linear"))
## How to add the other traces selected in input$show_vars??
})
}
shinyApp(ui = ui, server = server)
UPDATE: I realize now that I need the script to avoid hard-coding the first plot to use foo$var_1. The plot should use any one of the possible selections in the checkboxes (minus $mon, which I have removed from the select list). When I try to make the first plot statement conditional I get the message "Error: The last plot does not exist." ie, this does not work:
output$myPlot = renderPlotly({
# p <- plot_ly(x=foo$mon, y=foo$var_1, line = list(shape="linear"))
for (item in input$show_vars) {
if (item == 1){
p <- plot_ly(x=foo$mon, y=foo[[item]], line = list(shape="linear"))
}
if(item > 1){
p <- add_trace(p, x = foo$mon, y = foo[[item]], evaluate = TRUE)
}
}
print(p)
See if this is what you want. Also you probably want to remove the first two items in the checkboxGroup so that they are not removable (depending on what you want).
output$myPlot = renderPlotly({
p <- plot_ly(x=foo$mon, y=foo$var_1, line = list(shape="linear"))
## How to add the other traces selected in input$show_vars??
for (item in input$show_vars) {
p <- add_trace(p, x = foo$mon, y = foo[[item]], evaluate = TRUE)
}
print(p)
})
I am using shiny to query an SQL database. From the data I produce a tile map. I would like the user to be able to click a tile, to select data, after which the tile changes colour. I have got it working slightly but the tile changes back to the original colour almost immediately. Here is an example:
Server.R
library(data.table)
# Create example data
Row <- 1:4
Col <- 1:4
Batch <- c("A","B")
dd <- expand.grid(Row,Col, Batch)
colnames(dd) <- c("Row","Col","Batch")
#Write to memory
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "dd", dd)
query <- function(...) dbGetQuery(con, ...)
shinyServer(function(input, output, session){
id <- eventReactive(input$do, {input$batch})
# Search by batch: either A or B. Create column "selected" to represent which tile has been clicked lower down i.e. 0 = not selected, 1=selected
wid <- reactive({
if(input$do==0) return ( )
quer <- paste("Select Row, Col, '0' as selected from dd where Batch='",id(),"'", sep="")
data.frame(query(quer))
})
# Output of clicked tile
output$plot_clicked_points <- renderDataTable({
dat <- wid()
res <- nearPoints(dat, input$plot_click,
threshold = 100, maxpoints = 1)
data.table(res)
})
#Update dataframe by changing "selected" tile to 1
update <- reactive({
dat <- wid()
res <- nearPoints(dat, input$plot_click,
threshold = 100, maxpoints = 1)
DT <- data.table(dat)
DT[(Row==res$Row & Col==res$Col), selected:=1]
})
# Produce tile map with colour of tile based on whether it is the most recently clicked i.e. "selected" should now be = 1
output$map <- renderPlot({
ggplot(update(), aes(Row,Col, fill=factor(selected))) + geom_tile(colour="white")
})
})
ui.R
library(shiny)
library(ggplot2)
library(RMySQL)
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
textInput("batch", label = "Batch ID", value=""),
actionButton("do", label = "Search")
),
mainPanel(
dataTableOutput(outputId="plot_clicked_points"),
plotOutput("map", click = "plot_click")
))))
So my question is, how do I get the colour change to stick? Maybe I need to create reactiveValues? Maybe I need a different approach altogether? Thanks
The issue is that when update is changed, the ggplot is redrawn which sets the selected points to an empty data frame. This removes all the selected points from your data frame and reverts the coloring.
You could try changing the data frame only when there is at least one selected point, I stored the data frame in a reactive value, you can access using values$data:
values <- reactiveValues()
observe({
if(input$do==0) return ( )
quer <- paste("Select Row, Col, '0' as selected from dd where Batch='",id(),"'", sep="")
print(data.frame(query(quer)))
values$data = data.frame(query(quer))
})
#Update dataframe by changing "selected" tile to 1
observe({
res <- nearPoints(values$data, input$plot_click,
threshold = 100, maxpoints = 1)
if(!is.null(res)) {
if(nrow(res)>=1){
selected <- rep(0,nrow(values$data))
selected[which(values$data$Row==res$Row & values$data$Col==res$Col)] <- 1
values$data$selected <- selected
}
}
})
# Produce tile map with colour of tile based on whether it is the most recently clicked i.e. "selected" should now be = 1
output$map <- renderPlot({
ggplot(values$data, aes(Row,Col, fill=factor(selected))) + geom_tile(colour="white")
})
Another in my opinion slightly more simple solution would be to use a reactiveVal to monitor selection. Example app.R:
library(shiny)
library(ggplot2)
library(dplyr)
dataset = expand.grid(time=paste("m_", 1:10), op=paste("om_", 1:20)) %>% mutate(wip=row_number())
server <- function(input, output) {
tileSelect <- reactiveVal(data_frame())
output$wip_map <- renderPlot({
p = ggplot(fakewip, aes(time, op, fill = wip)) + geom_tile()
if (nrow(tileSelect()) > 0) {
p + geom_tile(color="red", size=2, fill=NA, data=tileSelect())
}else{
p
}
})
observeEvent(input$plot_click, {
tileSelect(nearPoints(dataset, input$plot_click, threshold = 100, maxpoints=1))
})
# reset selection with double click
observeEvent(input$plot_dblclick, {
tileSelect(data_frame())
})
}
ui <- fluidPage(
title = "Heatmap Select",
plotOutput("wip_map", click = "plot_click", dblclick = "plot_dblclick")
)
shinyApp(ui = ui, server = server)