In a shiny plot I am trying to highlight points matching a clicked point (based on nearPoints() and click).
It sort of works. However, the reactive parts of the shiny app are refreshed twice and the second iteration seems to clear the clicked information.
How can I avoid the second refresh of the app?
Here is the MWE:
library("Cairo")
library("ggplot2")
library("shiny")
ui <- fluidPage(
fluidRow(
titlePanel('Phenotype Plots')
),
fluidRow(
uiOutput("plotui")
),
hr(),
fluidRow(
wellPanel(
h4("Selected"),
tableOutput("info_clicked")
##dataTableOutput("info_clicked") ## overkill here
)
)
)
server <- function(input, output, session) {
selected_line <- reactive({
nearPoints(mtcars, input$plot_click,
maxpoints = 1,
addDist = TRUE)
})
output$plotui <- renderUI({
plotOutput("plot", height=600,
click = "plot_click"
)
})
output$plot <- renderPlot({
p <- ggplot(mtcars) +
facet_grid(am ~ cyl) +
theme_bw() +
geom_point(aes(x=wt, y=mpg))
sline <- selected_line()
if (nrow(sline) > 0) {
p <- p +
geom_point(aes(x=wt, y=mpg),
data=mtcars[mtcars$gear == sline$gear,],
colour="darkred",
size=1)
}
p
})
##output$info_clicked <- renderDataTable({
output$info_clicked <- renderTable({
res <- selected_line()
## datatable(res)
res
})
}
shinyApp(ui, server)
Finally(!) found a workaround for avoiding double refresh on click in Shiny: capture click to a reactiveValue(), using the observeEvent(). Seemingly works on my project, and for your MWE, too. See updated code section below.
library("Cairo")
library("ggplot2")
library("shiny")
ui <- fluidPage(
fluidRow(
titlePanel('Phenotype Plots')
),
fluidRow(
uiOutput("plotui")
),
hr(),
fluidRow(
wellPanel(
h4("Selected"),
tableOutput("info_clicked")
##dataTableOutput("info_clicked") ## overkill here
)
)
)
server <- function(input, output, session) {
## CHANGE HERE
## Set up buffert, to keep the click.
click_saved <- reactiveValues(singleclick = NULL)
## CHANGE HERE
## Save the click, once it occurs.
observeEvent(eventExpr = input$plot_click, handlerExpr = { click_saved$singleclick <- input$plot_click })
## CHANGE HERE
selected_line <- reactive({
nearPoints(mtcars, click_saved$singleclick, ## changed from "input$plot_click" to saved click.
maxpoints = 1,
addDist = TRUE)
})
output$plotui <- renderUI({
plotOutput("plot", height=600,
click = "plot_click"
)
})
output$plot <- renderPlot({
p <- ggplot(mtcars) +
facet_grid(am ~ cyl) +
theme_bw() +
geom_point(aes(x=wt, y=mpg))
sline <- selected_line()
if (nrow(sline) > 0) {
p <- p +
geom_point(aes(x=wt, y=mpg),
data=mtcars[mtcars$gear == sline$gear,],
colour="darkred",
size=1)
}
p
})
##output$info_clicked <- renderDataTable({
output$info_clicked <- renderTable({
res <- selected_line()
## datatable(res)
res
})
}
shinyApp(ui, server)
Related
I have a table where the user can change the data, and the updated data will be used for future calculations.
Here is an example of a table, and I want it so that when the table is modified, the necessary information on the main panel will be updated accordingly.
Here is my code:
library(ggplot2)
library(DT)
library(shiny)
ui <- fluidPage(
sidebarLayout(sidebarPanel(
DTOutput("mytable"),
actionButton("update", "Update")
),
mainPanel(
plotOutput("plot"),
verbatimTextOutput("text")
)
)
)
server <- function(input, output, session) {
tab <- reactiveValues(df = {data.frame(
num = 1:5,
x = LETTERS[1:5],
y = c(14,5,8,9,13)
)})
output$mytable <- renderDT({
DT::datatable(tab$df, editable = T, selection = "none")
})
observeEvent(input$update,{
output$plot <- renderPlot({
tab$df %>% ggplot(aes(x,y)) + geom_point()
})
output$text <- renderPrint({
tab$df$x
})
})
}
shinyApp(ui, server)
Try this approach with your server method.
First, add an observeEvent to detect edits/changes to your datatable. When there are, the changes are stored in your tab which is reactive.
Second, if you want an actionbutton to redo the plot and text, then would also make a second reactiveValues rv and observeEvent to store and update them when the button is pressed.
server <- function(input, output, session) {
tab <- reactiveValues(df = {data.frame(
num = 1:5,
x = LETTERS[1:5],
y = c(14,5,8,9,13)
)})
rv <- reactiveValues(
plot = NULL,
text = NULL
)
output$mytable <- renderDT({
DT::datatable(tab$df, editable = T, selection = "none")
})
observeEvent(input$mytable_cell_edit, {
row <- input$mytable_cell_edit$row
clmn <- input$mytable_cell_edit$col
tab$df[row, clmn] <- input$mytable_cell_edit$value
})
observeEvent(input$update,{
rv$text <- tab$df$x
rv$plot <- tab$df %>%
ggplot(aes(x,y)) +
geom_point()
})
output$plot <- renderPlot({
rv$plot
})
output$text <- renderPrint({
rv$text
})
}
In my app, I want plot1 to display by default, and then if an action button is clicked, have plot2 replace plot1. If it is clicked again, revert to plot1, and so on.
server <- function(input, output, session) {
plot1 <- (defined here)
plot2 <- (defined here)
which_graph <- reactive({
if (input$actionbutton == 1) return(plot1)
if (input$actionbutton == 2) return(plot2)
})
output$plot <- renderPlot({
which_graph()
})
}
You can create a reactiveValue and use an actioButton to toggle that value. For example
library(shiny)
ui <- fluidPage(
plotOutput("plot"),
actionButton("button", "Click")
)
server <- function(input, output, session) {
whichplot <- reactiveVal(TRUE)
plot1 <- ggplot(mtcars) + aes(mpg, cyl) + geom_point()
plot2 <- ggplot(mtcars) + aes(hp, disp) + geom_point()
observeEvent(input$button, {
whichplot(!whichplot())
})
which_graph <- reactive({
if (whichplot()) {
plot1
} else {
plot2
}
})
output$plot <- renderPlot({
which_graph()
})
}
shinyApp(ui, server)
Here whichplot starts off as TRUE and then evertime you press the actionButton it toggles between TRUE/FALSE. This way you are not changing the value of the actionButton; you are just updating state each time it's pressed.
If your plots need any input from the user, you can make them reactive as well
ui <- fluidPage(
selectInput("column", "Column", choices=names(mtcars)),
plotOutput("plot"),
actionButton("button", "Click")
)
server <- function(input, output, session) {
whichplot <- reactiveVal(TRUE)
plot1 <- reactive({ggplot(mtcars) + aes(mpg, .data[[input$column]]) + geom_point()})
plot2 <- reactive({ggplot(mtcars) + aes(.data[[input$column]], disp) + geom_point()})
observeEvent(input$button, {
whichplot(!whichplot())
})
which_graph <- reactive({
if (whichplot()) {
plot1()
} else {
plot2()
}
})
output$plot <- renderPlot({
which_graph()
})
}
I want to change chart type from e_line to e_bar based on a condition. What I tried to do was using some reactive expression or if else inside plot, but neither of them works. Any ideas how to tackle this?
So, I need to change dynamically e_line, I tried this:
newChartType <- reactive({
df = switch(
someCondition,
'1' = echarts4r::e_line(ColumnName2),
'2' = echarts4r::e_bar(ColumnName2)
)
})
output$plot <- echarts4r::renderEcharts4r({
dataChartStats() %>%
echarts4r::e_charts(ColumnName1) %>%
newChartType() %>%
echarts4r::e_legend(show = FALSE)
})
but it doesn't work. I'm interested in general rule on how to change dynamically building elements of plot code (can be ggplot as well etc, here I used echarts4r).
I couldn't find a way of obtaining the chart type directly from an input element, but here's one way of doing it:
library(shiny)
library(tidyverse)
ui <- fluidPage(
selectInput(
"type",
"Select a chart type:",
c("point", "line")),
plotOutput("plot")
)
server <- function(input, output) {
output$plot <- renderPlot({
if (input$type == "line") {
mtcars %>% ggplot() + geom_line(aes(x=mpg, y=disp))
} else {
mtcars %>% ggplot() + geom_point(aes(x=mpg, y=disp))
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
Next time, please provide a minimum working example.
EDIT in response to OP's request for a solution based on a button click:
library(shiny)
library(tidyverse)
ui <- fluidPage(
actionButton("go", "Click me!"),
textOutput("type"),
# selectInput(
# "type",
# "Select a chart type:",
# c("point", "line")),
plotOutput("plot")
)
server <- function(input, output) {
v <- reactiveValues(type="line")
observeEvent(input$go, {
if (v$type == "line") v$type <- "point"
else v$type <- "line"
})
output$type <- renderText({ v$type })
output$plot <- renderPlot({
if (v$type == "line") {
mtcars %>% ggplot() + geom_line(aes(x=mpg, y=disp))
} else {
mtcars %>% ggplot() + geom_point(aes(x=mpg, y=disp))
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
I am trying to update text and plot both with ActionButton click.
My Attempt-
library(shiny)
library(ggplot2)
library(shinyWidgets)
ui <- fluidPage(
actionGroupButtons(
inputIds = c("Bar", "Histogram", "Line"),
labels = list("Bar", "Histogram","Line"),
status = "danger",
fullwidth = T
),
plotOutput('plot',height = '563px'),
verbatimTextOutput('text')
)
server <- function(input, output) {
output$plot <- renderPlot({
if(req(input$Bar)!=0) {
isolate({
data <- iris
ggplot(data, aes(Species,Petal.Length)) +
geom_bar(stat="identity")
})
} else if(req(input$Histogram)>0){
isolate({
data <- iris
ggplot(data, aes(Petal.Length)) +
geom_histogram()
})
} else if(req(input$Line)>0){
isolate({
data <- iris
ggplot(data, aes(Petal.Length,Sepal.Length)) +
geom_line()
})
}
})
output$text <- renderText({
if(req(input$Bar)!=0) {
"Bar"
} else if(req(input$Histogram)>0){
"Histogram"
} else if(req(input$Line)>0){
"Line"
}
})
}
shinyApp(ui, server)
I want to change plot and text when the appropriate action button is clicked.
Here would be one way to do it.
In it's essence the approach is pointed out in the action button example no. 3 from RStudio.
library(shiny)
library(ggplot2)
library(shinyWidgets)
ui <- fluidPage(
actionGroupButtons(
inputIds = c("Bar", "Histogram", "Line"),
labels = list("Bar", "Histogram","Line"),
status = "danger",
fullwidth = T
),
plotOutput('plot',height = '563px'),
verbatimTextOutput('text')
)
server <- function(input, output) {
v <- reactiveValues(data = iris,
plot = NULL,
text = NULL)
observeEvent(input$Bar, {
v$plot <- ggplot(v$data, aes(Species,Petal.Length)) +
geom_bar(stat="identity")
v$text <- "Bar"
})
observeEvent(input$Histogram, {
data <- iris
v$plot <- ggplot(v$data, aes(Petal.Length)) +
geom_histogram()
v$text <- "Histogram"
})
observeEvent(input$Line, {
data <- iris
v$plot <- ggplot(v$data, aes(Petal.Length,Sepal.Length)) +
geom_line()
v$text <- "Line"
})
output$plot <- renderPlot({
if (is.null(v$plot)) return()
v$plot
})
output$text <- renderText({
if (is.null(v$text)) return()
v$text
})
}
shinyApp(ui, server)
Update
In case you are using Input filters on your data in a reactive, then you have to adjust the Approach above a litte:
library(shiny)
library(ggplot2)
library(shinyWidgets)
ui <- fluidPage(
selectInput(inputId = "species", label = "Select species:",
choices = unique(as.character(iris$Species)),
selected = "setosa"),
sliderInput("sepal_length", "Limit sepal length:",
round = 0,
min = range(iris$Sepal.Length)[1], max = range(iris$Sepal.Length)[2],
range(iris$Sepal.Length),
step = 0.1),
actionGroupButtons(
inputIds = c("Bar", "Histogram", "Line"),
labels = list("Bar", "Histogram","Line"),
status = "danger",
fullwidth = T
),
plotOutput('plot',height = '563px'),
verbatimTextOutput('text')
)
server <- function(input, output) {
data <- reactive({
temp <- subset(iris, Species == input$species)
subset(temp, Sepal.Length < input$sepal_length)
})
v <- reactiveValues(plot = NULL,
text = NULL)
observeEvent(input$Bar, {
v$plot <- ggplot(data(), aes(Species,Petal.Length)) +
geom_bar(stat="identity")
v$text <- "Bar"
})
observeEvent(input$Histogram, {
v$plot <- ggplot(data(), aes(Petal.Length)) +
geom_histogram()
v$text <- "Histogram"
})
observeEvent(input$Line, {
v$plot <- ggplot(data(), aes(Petal.Length,Sepal.Length)) +
geom_line()
v$text <- "Line"
})
output$plot <- renderPlot({
if (is.null(v$plot)) return()
v$plot
})
output$text <- renderText({
if (is.null(v$text)) return()
v$text
})
}
shinyApp(ui, server)
When I click one point in the chart, that point is highlighted as red.
But soon it goes back to black.
Is there any way to hold the selection?
library(shiny)
library(ggplot2)
server <- function(input, session, output) {
mtcars$cyl = as.character(mtcars$cyl)
D = reactive({
nearPoints(mtcars, input$click_1,allRows = TRUE)
})
output$plot_1 = renderPlot({
set.seed(123)
ggplot(D(),aes(x=cyl,y=mpg)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(aes(color=selected_),width=0.02,size=4)+
scale_color_manual(values = c("black","red"),guide=FALSE)
})
output$info = renderPrint({
D()
})
}
ui <- fluidPage(
plotOutput("plot_1",click = clickOpts("click_1")),
verbatimTextOutput("info")
)
shinyApp(ui = ui, server = server)
Okay, my approach is slightly different to Valter's: selected points become red, whilst you can deselect them and they turn back to black.
The key to achieve this effect (or even Valter's answer with 1 selected point) is to use reactiveValues to keep track of the selected points.
library(shiny)
library(ggplot2)
server <- function(input, session, output) {
mtcars$cyl = as.character(mtcars$cyl)
vals <- reactiveValues(clicked = numeric())
observeEvent(input$click_1, {
# Selected point/points
slt <- which(nearPoints(mtcars, input$click_1,allRows = TRUE)$selected)
# If there are nearby points selected:
# add point if it wasn't clicked
# remove point if it was clicked earlier
# Else do nothing
if(length(slt) > 0){
remove <- slt %in% vals$clicked
vals$clicked <- vals$clicked[!vals$clicked %in% slt[remove]]
vals$clicked <- c(vals$clicked, slt[!remove])
}
})
D = reactive({
# If row is selected return "Yes", else return "No"
selected <- ifelse(1:nrow(mtcars) %in% vals$clicked, "Yes", "No")
cbind(mtcars, selected)
})
output$plot_1 = renderPlot({
set.seed(123)
ggplot(D(),aes(x=cyl,y=mpg)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(aes(color=selected),width=0.02,size=4)+
scale_color_manual(values = c("black","red"),guide=FALSE)
})
output$info = renderPrint({
D()
})
}
ui <- fluidPage(
plotOutput("plot_1",click = clickOpts("click_1")),
verbatimTextOutput("info")
)
shinyApp(ui = ui, server = server)
I am not sure what is the problem but this is the first workaround I have come up to:
library(shiny)
library(ggplot2)
server <- function(input, session, output) {
mtcars$cyl = as.character(mtcars$cyl)
df <- reactiveValues(dfClikced = mtcars)
observe({
if (!is.null(input$click_1)) {
df$dfClikced <- nearPoints(mtcars, input$click_1, allRows = TRUE)
}})
output$plot_1 = renderPlot({
set.seed(123)
if (names(df$dfClikced)[NCOL(df$dfClikced)]== "selected_") {
ggplot(df$dfClikced,aes(x=cyl,y=mpg)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(aes(color=selected_),width=0.02,size=4)+
scale_color_manual(values = c("black","red"),guide=FALSE)
} else {
ggplot(df$dfClikced,aes(x=cyl,y=mpg)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(width=0.02,size=4)+
scale_color_manual(values = c("black","red"),guide=FALSE)
}
})
output$info = renderPrint({
df$dfClikced
})
}
ui <- fluidPage(
plotOutput("plot_1",click = clickOpts("click_1")),
verbatimTextOutput("info")
)
shinyApp(ui = ui, server = server)
let me know...