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

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)

Related

Dynamically add and remove trace in the presence of reactive values

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)

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

R Shiny: relayout plotly annotations

I want a plotly plot to change an annotation if the user clicks a button in a shiny app.
I have no idea why this does not work:
library(shiny)
library(plotly)
d <- data.frame(x = c(1,2,3), y = c(9,99,999))
ui <- fluidPage(
plotlyOutput("plot"),
actionButton("button", "toggle visibility"))
server <- function(input, output) {
output$plot <- renderPlotly({
plot_ly(d)%>%
add_lines(y=d$y, x= d$x)%>%
layout(annotations = list(x = 2, y= 99 , text = "hi"))})
observeEvent(input$button, {
plotlyProxy("plot", session= shiny::getDefaultReactiveDomain()) %>%
plotlyProxyInvoke("relayout", list(annotations= list(x = 2, y= 99 ,
text = "ho")))})}
shinyApp(ui, server)
That is not the way to use relayout in plotly. See below for your example using relayout.
I prefer using native shiny buttons for this purpose because of the greater flexibility it offers. Here is how one might go about achieving the hi-ho toggle.
shiny way
library(shiny)
library(plotly)
d <- data.frame(x = c(1,2,3), y = c(9,99,999))
ui <- fluidPage(
plotlyOutput("plot"),
actionButton("button", "toggle visibility"))
server <- function(input, output) {
output$plot <- renderPlotly({
p <- plot_ly(d)%>%
add_lines(y=d$y, x= d$x)
if (is.null(input$button) | (input$button%%2 == 0)) {
p <- p %>% layout(annotations = list(x = 2, y= 99 , text = "hi"))
} else {
p <- p %>% layout(annotations = list(x = 2, y= 99 , text = "ho"))
}
p
})
}
shinyApp(ui, server)
In this case though, it is simple enough to make the relayout feature work, although it does require an extra button.
plotly relayout way
library(shiny)
library(plotly)
d <- data.frame(x = c(1,2,3), y = c(9,99,999))
ui <- fluidPage(
plotlyOutput("plot")
)
server <- function(input, output) {
output$plot <- renderPlotly({
updatemenus <- list(
list(
active = -1,
type = 'buttons',
buttons = list(
list(
label = "hi",
method = "relayout",
args = list(list(annotations = list(list(x = 2, y= 99 , text = "hi"))))),
list(
label = "ho",
method = "relayout",
args = list(list(annotations = list(list(x = 2, y= 99 , text = "ho")))))
)
)
)
p <- plot_ly(d) %>%
add_lines(y=d$y, x= d$x) %>%
layout(updatemenus = updatemenus)
p
})
}
shinyApp(ui, server)
I believe all that needs to change in your code in order to get this to work is wrapping another list around the defined annotation list in your plotly proxy relayout code. I recently discovered that this recursive list structure is all that's needed in order to manipulate annotations using relayout - you can check out my answer to another SO question related to the same issue, but with slightly different context: https://stackoverflow.com/a/70610374/17852464
library(shiny)
library(plotly)
d <- data.frame(x = c(1,2,3), y = c(9,99,999))
ui <- fluidPage(
plotlyOutput("plot"),
actionButton("button", "toggle visibility"))
server <- function(input, output) {
output$plot <- renderPlotly({
plot_ly(d)%>%
add_lines(y=d$y, x= d$x)%>%
layout(annotations = list(x = 2, y= 99 , text = "hi"))
})
observeEvent(input$button, {
plotlyProxy("plot", session= shiny::getDefaultReactiveDomain()) %>%
plotlyProxyInvoke("relayout", list(annotations= list(list(x = 2, y= 99 ,
text = "ho"))))})}
}
shinyApp(ui, server)

Plotly graph misbehaving in Shiny

When I select options in my Shiny application, I expect it to produce a graph in plotly and it does. It look like this:
When I unselect the options, it SHOULD look like this:
However, the output is misbehaving in a way I don't understand why. This is output I get:
As you can see, the x and y axis still remains. If I hover over the plot with no options selected, the plot still shows the coordinates.
Here is my code:
# UI
box(width = NULL, uiOutput("plotTitle"),
plotlyOutput('plot1', height="730px"), collapsed = F, title = "Figure",
status = "warning", solidHeader = T, height = "830px")
# SERVER
output$plot1 <- renderPlotly({
validate(
need(input$crops, "INSTRUCTIONS: \n\n1) Select Crops Of Interest.\n2)
Use 'Farm' tab to refine results.\n3) Hover over graph to see values.")
)
crop.data <- reactive({
crop.list <- input$crops
z.df <- dat
c.df <- subset(z.df, Crop %in% crop.list)
c.df <- unique(c.df)
})
output$plotTitle <- renderUI({
if(length(crop.data()) > 0) {
div(style='background-color:green; color:white; text-align:center;
font-size:22px; font-family:"Open Sans",verdana,arial,sans-serif',
input$metric)
}
})
# s.df is the Dataframe
s.df <- subset(s.df, Crop %in% input$crops)
if(length(crop.data()) > 0) {
if(input$metric == "Percentage") {
p <- plot_ly(s.df, x = ~Farm, y = ~Percentage, color = ~Crop) %>%
layout(barmode = 'stack', xaxis = list(title = ''), margin = list(b = 140))
} else if(input$metric == "Acreage") {
p <- plot_ly(s.df, x = ~Farm, y = ~Acreage, color = ~Crop) %>%
layout(barmode = 'stack', xaxis = list(title = ''), margin = list(b = 140))
} else {
p <- plot_ly(s.df, x = ~Farm, y = ~Exposure_Costs, color = ~Crop) %>%
layout(barmode = 'stack', xaxis = list(title = ''),
yaxis = list(title = 'Exposure Costs'), margin = list(b = 140))
}
p
}
})
Is there a specific reason as to why this happens? Also, this error doesn't exist on my local instance but occurs on the shiny server and another's local instance. How do I fix it?
You're using length(crop.data()) to check whether or not to plot but this will return a value greater than 0 for an "empty" data.frame because it is counting the number of columns.
e.g.
> (x <- iris[0, ])
[1] Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<0 rows> (or 0-length row.names)
> length(x)
[1] 5
You really should untangle your mangling of the reactives, renderUI and renderPlotly but replacing length(crop.data()) with nrow(crop.data()) should solve your problem.
This reproducible example shows that your desired behavior does occur for a simple example:
library("shiny")
library("plotly")
set.seed(100)
d <- diamonds[sample(nrow(diamonds), 1000), ]
ui <- fluidPage(
titlePanel("Null Plotly Object"),
sidebarLayout(
sidebarPanel(
checkboxGroupInput("cuts", "Cut", choices = sort(unique(d$cut)))
),
mainPanel(
plotlyOutput("distPlot")
)
)
)
server <- function(input, output) {
plot_dat <- reactive({
shiny::validate(
need(input$cuts, "Select a cut")
)
d[d$cut %in% input$cuts, ]
})
output$distPlot <- renderPlotly({
plot_ly(plot_dat(), x = ~carat, y = ~price, color = ~carat, type = "scatter",
mode = "markers", size = ~carat, text = ~paste("Clarity: ", clarity))
})
}
shinyApp(ui = ui, server = server)

bsplus: Carousel for dynamic number of plots in Shiny

I am trying to create an application where a user selection determines the number of plots to be placed into a carousel. I have a MWE below, where a user "selects" anywhere between 1-10 of the lines in the parallel coordinate plot on the left. After doing so, on the right, 1-10 plots are created (one for each of the lines the user selected). This all seems to be working, and the dynamic number of plots are stored in a tagList() object.
With larger datasets, the number of lines the user can select can be large and the output plots can look crowded. Hence, I am trying to put the output plots into a carousel. Currently, I have all output plots in a carousel - but they are all shoved into the first page of the carousel.
I would be grateful to hear any advice on how I can tweak this MWE so that each page of the carousel only contains one of the output plots.
library(shiny)
library(plotly)
library(data.table)
library(dplyr)
library(tidyr)
library(bsplus)
ui <- shinyUI(pageWithSidebar(
headerPanel("Dynamic number of plots"),
sidebarPanel(
plotlyOutput("plot")
),
mainPanel(
# This is the dynamic UI for the plots
bs_carousel(id = "tabPrev", use_indicators = TRUE) %>%
bs_append(content = uiOutput("plots"))
)
)
)
server <- shinyServer(function(input, output) {
set.seed(1)
dat <- data.frame(ID = paste0("ID",1:10), A.1 = runif(10), A.2 = runif(10), A.3 = runif(10), B.1 = runif(10), B.2 = runif(10), B.3 = runif(10))
dat$ID <- as.character(dat$ID)
# Convert DF from scatterplot to PCP
datt <- data.frame(t(dat))
names(datt) <- as.matrix(datt[1, ])
datt <- datt[-1, ]
datt[] <- lapply(datt, function(x) type.convert(as.character(x)))
setDT(datt, keep.rownames = TRUE)[]
colnames(datt)[1] <- "x"
dat_long <- melt(datt, id.vars ="x" )
dat_long <- separate(dat_long, x, c("group", "rep"), remove=FALSE)
dat_long$group <- factor(dat_long$group)
output$plot <- renderPlotly({
plot_ly(dat_long, x= ~x, y= ~value, type = 'scatter', mode = 'lines+markers', color = ~variable) %>% layout(dragmode="box", showlegend = FALSE)
})
d <- reactive(event_data("plotly_selected"))
observeEvent(d(),{
# Insert the right number of plot output objects into the web page
output$plots <- renderUI({
lengthY <- reactive((length(unique(d()$curveNumber))))
if (lengthY()<1){
plot_output_list <- list()
}
else{
plot_output_list <- lapply(1:lengthY(), function(i) {
plotname <- paste("plot", i, sep="")
plotlyOutput(plotname, height = 280, width = 250)
})
}
# Convert the list to a tagList - this is necessary for the list of items
# to display properly.
do.call(tagList, plot_output_list)
})
})
# Call renderPlot for each one. Plots are only actually generated when they
# are visible on the web page.
observeEvent(d(),{
lengthY <- reactive(length(unique(d()$curveNumber)))
for (i in 1:lengthY()) {
# Need local so that each item gets its own number. Without it, the value
# of i in the renderPlot() will be the same across all instances, because
# of when the expression is evaluated.
local({
my_i <- i
curveY <- reactive(d()$curveNumber[my_i])
plotname <- paste("plot", my_i, sep="")
ax <- list(title = "", showticklabels = TRUE)
ay <- list(title = "Read Count")
indDat <- as.data.frame(dat_long[variable %in% dat[curveY()+1,]$ID])
g1 <- levels(indDat$group)[1]
g2 <- levels(indDat$group)[2]
g1m <- mean(filter(indDat, group==g1)$value)
g2m <- mean(filter(indDat, group==g2)$value)
output[[plotname]] <- renderPlotly({
indDat %>% plot_ly(x = ~group, y = ~value, type = "scatter", marker = list(size = 10), color = ~group, colors = "Set2", hoverinfo = "text", text = paste0("Read count = ", format(round(indDat$value, 2), nsmall = 2))) %>% layout(xaxis = ax, yaxis = ay, legend = list(x = 0.35, y = -0.26)) %>% add_segments(x = g1, xend = g2, y = g1m, yend = g2m, showlegend = FALSE, line = list(color='#000000')) %>% add_trace(x = g1, y= g1m, showlegend = FALSE, hoverinfo = "text", text = paste0("Mean Read Count = ", round(g1m, digits = 2)), marker = list(color='#000000')) %>% add_trace(x = g2, y= g2m, showlegend = FALSE, hoverinfo = "text", text = paste0("Mean Read Count = ", round(g2m, digits = 2)), marker = list(color='#000000'))
})
})
}
})
})
shinyApp(ui, server)
The way I would do this is embedding the bs_carousel inside the renderUI. It does work but I couldn't manage to remove the plots object totally, which sometimes plots... If I remove it, only the first plot appears in the carousel.
1- Change the ui to :
ui <- shinyUI(pageWithSidebar(
headerPanel("Dynamic number of plots"),
sidebarPanel(
plotlyOutput("plot")
),
mainPanel(
uiOutput("car_ui"),
uiOutput("plots")
)
)
)
2- Add this code in the first observeEvent, just above the output$plots
output$car_ui <- renderUI({
lengthY <- length(unique(d()$curveNumber))
if (lengthY<1){
plot_output_list <- list()
}
else{
plot_output_list <- lapply(1:lengthY, function(i) {
plotname <- paste("plot", i, sep="")
plotlyOutput(plotname, height = 280, width = 250)
})
}
car <- bs_carousel(id = "carousel", use_indicators = TRUE)
Reduce(bs_append, plot_output_list, init=car)
})
Also note you don't have to put all your calculations (lengthY...) in a reactive

Resources