Dynamically add and remove trace in the presence of reactive values - r

My application contains a surface plot made with R plotly and the user can use a sliderInput to specify a level where a contour line (actually a 3D scatterplot) is dynamically drawn. So, when the user clicks on the app's button, the current contour line is deleted and a new one is generated and placed on the plot. My issue; however, is that the use of plotlyProxy and plotlyProxyInvoke do not matter - the surface plot is redrawn and the angle of view is reset, which is exactly what I am trying to avoid. Here is my minimal reproducible code:
library(shiny)
library(plotly)
library(isoband)
ui <- fluidPage(
h1("My simple app"),
sliderInput(
inputId = "slider", label = "Select contour level", value = 1, min = 1, max = 40),
actionButton(inputId = "btn", "OK"),
plotlyOutput(outputId = "plot")
)
server <- function(input, output, session){
rv <- reactiveValues()
x <- y <- 0:100
z <- outer(X = x, Y = y, function(x, y) x^0.2 * y^0.6)
observeEvent(input$btn, ignoreInit = TRUE, {
rv$iso <- isolines(x = x, y = y, z = z, levels = isolate({input$slider}))
plotlyProxy("plot", session) %>%
plotlyProxyInvoke(method = "deleteTraces", list(1)) %>%
plotlyProxyInvoke(
method = "addTraces",
list(
type = "scatter3d",
x = rv$iso[[1]]$x,
y = rv$iso[[1]]$y,
z = isolate({input$slider})
)
)
})
output$plot <- renderPlotly({
rv$iso <- isolines(x = x, y = y, z = z, levels = isolate({input$slider}))
plot_ly(
type = "surface",
x = x,
y = y,
z = z
) %>%
add_trace(
type = "scatter3d",
x = rv$iso[[1]]$x,
y = rv$iso[[1]]$y,
z = isolate({input$slider})
)
})
}
shinyApp(ui = ui, server = server)

The solution was provided by #nirgrahamuk of the RStudio Community:
library(shiny)
library(plotly)
library(isoband)
ui <- fluidPage(
h1("My simple app"),
sliderInput(
inputId = "slider",
label = "Select contour level",
value = 1,
min = 1,
max = 40
),
actionButton(inputId = "btn", "OK"),
plotlyOutput(outputId = "plot")
)
server <- function(input, output, session) {
x <- y <- 0:100
z <- outer(X = x, Y = y, function(x, y) x^0.2 * y^0.6)
# precompute iso levels
iso <- isolines(x = x, y = y, z = z, levels = 1:40)
observeEvent(input$btn,
ignoreInit = TRUE,
{
lvl <- input$slider
mytrace <- list(
type = "scatter3d",
mode = "markers",
x = iso[[lvl]]$x,
y = iso[[lvl]]$y,
z = rep(lvl, length(iso[[lvl]]$id))
)
p1 <- plotlyProxy("plot", session)
plotlyProxyInvoke(p1,
method = "deleteTraces",
list(-1)
)
plotlyProxyInvoke(p1,
method = "addTraces",
list(mytrace)
)
}
)
output$plot <- renderPlotly({
isolate({
lvl <- input$slider
plot_ly(
type = "surface",
x = x,
y = y,
z = z
) %>%
add_trace(
type = "scatter3d",
mode = "markers",
x = iso[[lvl]]$x,
y = iso[[lvl]]$y,
z = lvl
)
})
})
}
shinyApp(ui, server)

Related

Problems with reactiveValue() in Plotly draggable graph

Thanks for your help in advance as this one is really driving me mad. I am trying to create a plotly scatterplot where I can change the location of single plots by dragging them, thus changing the regression line. Importantly, I would like to filter the data through a pickerInput, to only run the analysis for a subset of the data.
Most things are working, however I am coming unstuck with my use of reactiveValues(). More, specifically, I believe reactiveValues() can't take a reactive dataframe...in this case a filtered version of mtcars. I have tried all sorts of things and am now getting a little desperate. Below is the code. I have also attached code of a simplified version of the code, which works just fine however doesn't have the all important filtering capability.
Please help!
library(plotly)
library(purrr)
library(shiny)
ui = navbarPage(windowTitle="Draggable Plot",
tabPanel(title = "Draggable Plot",
sidebarPanel(width = 2,
pickerInput("Cylinders","Select Cylinders",
choices = unique(mtcars$cyl), options = list(`actions-box` = TRUE),multiple = FALSE, selected = unique(mtcars$cyl))),
mainPanel(
plotlyOutput("p", height = "500px", width = "1000px"),verbatimTextOutput("summary"))))
server <- function(input, output, session) {
data = reactive({
data = mtcars
data <- data[data$cyl %in% input$Cylinders,]
return(data)
})
rv <- reactiveValues(
data = data()
x = data$mpg,
y = data$wt
)
grid <- reactive({
data.frame(x = seq(min(rv$x), max(rv$x), length = 10))
})
model <- reactive({
d <- data.frame(x = rv$x, y = rv$y)
lm(y ~ x, d)
})
output$p <- renderPlotly({
# creates a list of circle shapes from x/y data
circles <- map2(rv$x, rv$y,
~list(
type = "circle",
# anchor circles at (mpg, wt)
xanchor = .x,
yanchor = .y,
# give each circle a 2 pixel diameter
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
# other visual properties
fillcolor = "blue",
line = list(color = "transparent")
)
)
# plot the shapes and fitted line
plot_ly() %>%
add_lines(x = grid()$x, y = predict(model(), grid()), color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$summary <- renderPrint({a
summary(model())
})
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
rv$x[row_index] <- pts[1]
rv$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
Just to add insult to injury, this version of the code without filtering works just fine.
library(plotly)
library(purrr)
library(shiny)
ui = navbarPage(windowTitle="Draggable Plot",
tabPanel(title = "Draggable Plot",
mainPanel(
plotlyOutput("p", height = "500px", width = "1000px"))))
server <- function(input, output, session) {
rv <- reactiveValues(
x = mtcars$mpg,
y = mtcars$wt
)
grid <- reactive({
data.frame(x = seq(min(rv$x), max(rv$x), length = 10))
})
model <- reactive({
d <- data.frame(x = rv$x, y = rv$y)
lm(y ~ x, d)
})
output$p <- renderPlotly({
# creates a list of circle shapes from x/y data
circles <- map2(rv$x, rv$y,
~list(
type = "circle",
# anchor circles at (mpg, wt)
xanchor = .x,
yanchor = .y,
# give each circle a 2 pixel diameter
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
# other visual properties
fillcolor = "blue",
line = list(color = "transparent")
)
)
# plot the shapes and fitted line
plot_ly() %>%
add_lines(x = grid()$x, y = predict(model(), grid()), color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$summary <- renderPrint({a
summary(model())
})
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
rv$x[row_index] <- pts[1]
rv$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
The following should address your concerns.
rv <- reactiveValues()
observe({
rv$data = data()
rv$x = data()$mpg
rv$y = data()$wt
})

Why is this simple R Shiny output not plotting with ggplot?

In running the below code, I'm not sure why it's not plotting. In other, more involved versions of this code it does plot; I've done line-by-line comparisons and can't see why it doesn't plot in this case. I've played with req(), if(isTruthy()...)) statements, with no luck. I tested the interpol() custom function in the console, and it works fine as shown in the image at the bottom of this post.
library(ggplot2)
library(shiny)
library(shinyMatrix)
interpol <- function(a, b) { # a = periods, b = matrix inputs
c <- rep(NA, a)
c[1] <- b[1]
c[a] <- b[2]
c <- approx(seq_along(c)[!is.na(c)], c[!is.na(c)], seq_along(c))$y # << interpolates
return(c)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput('periods', 'Modeled periods (X variable):', min=1, max=10, value=10),
matrixInput("matrix1",
label = "Matrix 1:",
value = matrix(c(5), ncol = 1, dimnames = list("Base rate",NULL)),
cols = list(names = FALSE),
class = "numeric"),
matrixInput("matrix2",
label = "Matrix 2 (will link to Matrix 1):",
value = matrix(c(10,5), ncol = 2, dimnames = list(NULL,c("X","Y"))),
rows = list(extend = TRUE, delete = TRUE),
class = "numeric"),
),
mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output, session){
plotData <- reactive({
req(input$periods,input$matrix2) # << this doesn't help
tryCatch(
tibble(
X = seq_len(input$periods),
Y = interpol(input$periods,input$matrix2, drop = FALSE)
),
error = function(e) NULL
)
})
output$plot <- renderPlot({
req(plotData())
plotData() %>% ggplot() +
geom_line(aes(x = X, y = Y, colour = as.factor(Scenario))) +
theme(legend.title=element_blank())
})
}
shinyApp(ui, server)
library(dplyr) was missing function tibble was unknown
Your function interpol doesn't have a drop argument
Object 'Scenario' not found
library(ggplot2)
library(shiny)
library(shinyMatrix)
library(dplyr)
interpol <- function(a, b) { # a = periods, b = matrix inputs
c <- rep(NA, a)
c[1] <- b[1]
c[a] <- b[2]
c <- approx(seq_along(c)[!is.na(c)], c[!is.na(c)], seq_along(c))$y # << interpolates
return(c)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput('periods', 'Modeled periods (X variable):', min=1, max=10, value=10),
matrixInput("matrix1",
label = "Matrix 1:",
value = matrix(c(5), ncol = 1, dimnames = list("Base rate",NULL)),
cols = list(names = FALSE),
class = "numeric"),
matrixInput("matrix2",
label = "Matrix 2 (will link to Matrix 1):",
value = matrix(c(10,5), ncol = 2, dimnames = list(NULL,c("X","Y"))),
rows = list(extend = TRUE, delete = TRUE),
class = "numeric"),
),
mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output, session){
plotData <- reactive({
# browser()
req(input$periods, input$matrix2) # << this doesn't help
tryCatch(
# drop = FALSE
tibble(
X = seq_len(input$periods),
Y = interpol(input$periods,input$matrix2)
),
error = function(e) NULL
)
})
output$plot <- renderPlot({
req(plotData())
# Error in is.factor: object 'Scenario' not found
# , colour = as.factor(Scenario)
plotData() %>% ggplot() +
geom_line(aes(x = X, y = Y)) +
theme(legend.title=element_blank())
})
}
shinyApp(ui, server)

How to add a new trace while removing the last one?

I am new to shiny and plotly. What I'm trying to do is to add a trace first and then I want it to be replaced by a new one every time I click on a button.
here is my minimal example:
library(shiny)
library(plotly)
ui <- fluidPage(plotlyOutput("fig1"),
numericInput("A",
label = h5("A"),
value = "",
width = "100px"),
numericInput("B",
label = h5("B"),
value = "",
width = "100px"),
actionButton("action3", label = "Add to plot"),
actionButton("action4", label = "Remove point")
)
server <- function(input, output) {
A <- 1:5
B <- c(115, 406, 1320, 179, 440)
data <- data.frame(A, B)
fig <- plot_ly(data, x = A, y = B, type = 'scatter', mode = 'markers')
output$fig1 <- renderPlotly(fig)
observeEvent(input$action3, {
vals <- reactiveValues(A = input$A, B = input$B)
plotlyProxy("fig1") %>%
plotlyProxyInvoke("addTraces",
list(x = c(vals$A,vals$A),
y = c(vals$B,vals$B),
type = "scatter",
mode = "markers"
)
)
})
observeEvent(input$action4, {
vals <- reactiveValues(A = input$A, B = input$B)
plotlyProxy("fig1") %>%
plotlyProxyInvoke("deleteTraces")
})
}
shinyApp(ui,server)
I can add a new trace easily but they all remain on the plot.
My solution was to add a new button to delete the trace but it did not work.
I have already read this but I couldn't make it work.
Based on what you described, it sounds like you want to add a trace and remove the most recent trace added at the same time when the button is pressed. This would still leave the original plot/trace that you started with.
I tried simplifying a bit. The first plotlyProxyInvoke will remove the most recently added trace (it is zero-indexed, leaving the first plotly trace in place).
The second plotlyProxyInvoke will add the new trace. Note that the (x, y) pair is included twice based on this answer.
library(shiny)
library(plotly)
A <- 1:5
B <- c(115, 406, 1320, 179, 440)
data <- data.frame(A, B)
ui <- fluidPage(plotlyOutput("fig1"),
numericInput("A",
label = h5("A"),
value = "",
width = "100px"),
numericInput("B",
label = h5("B"),
value = "",
width = "100px"),
actionButton("action3", label = "Add to plot"),
)
server <- function(input, output, session) {
fig <- plot_ly(data, x = A, y = B, type = 'scatter', mode = 'markers')
output$fig1 <- renderPlotly(fig)
observeEvent(input$action3, {
plotlyProxy("fig1", session) %>%
plotlyProxyInvoke("deleteTraces", list(as.integer(1)))
plotlyProxy("fig1", session) %>%
plotlyProxyInvoke("addTraces",
list(x = c(input$A, input$A),
y = c(input$B, input$B),
type = 'scatter',
mode = 'markers')
)
})
}
shinyApp(ui,server)

R Shiny animation scatterplot speed performance

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))

Is there a way to preselect points in ggiraph (R shiny)?

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)

Resources