I am building a Shiny app, where the user can add different curves to the plot, but I can't make the plot to retain the already added curves. How can I make that happen?
In the simplified reproducible code below are altogether 8 curve possibilities, based on 3 radiobuttons selections which have 2 options each. I would like to keep these as radiobuttons and not use checkboxes, as in my real app that I am working on there will be about 300 combinations that would be too confusing to use with checkboxes. Please see the code below:
library(shiny)
library(plotly)
library(dplyr)
data111 <-data.frame("x"=1:10, "y"=c(99,98,97,96,95,94,93,92,91,90))
data112 <-data.frame("x"=5:14, "y"=c(79,78,77,76,75,74,73,72,71,70))
data121 <-data.frame("x"=9:18, "y"=c(59,58,57,56,55,54,53,52,51,50))
data122 <-data.frame("x"=3:12, "y"=c(49,48,47,46,45,44,43,42,41,40))
data211 <-data.frame("x"=7:16, "y"=c(29,28,27,26,25,24,23,22,21,20))
data212 <-data.frame("x"=11:20, "y"=c(19,18,17,16,15,14,13,12,11,10))
data221 <-data.frame("x"=2:11, "y"=c(95,94,93,92,91,90,89,88,87,86))
data222 <-data.frame("x"=1:10, "y"=c(45,44,43,42,41,40,39,38,37,36))
ui <- fluidPage(
titlePanel("Curve selection"),
sidebarLayout(
sidebarPanel(
radioButtons(inputId = "option",
label="Choose the option",
choices=c("option1"=1,
"option2"=2),
selected = 1),
radioButtons(inputId = "type",
label="Choose the type",
choices=c("type1"=1,
"type2"=2),
selected = 1),
radioButtons(inputId = "group",
label="Choose the group",
choices=c("group1"=1,
"group2"=2),
selected = 1),
actionButton("add","Add curve to the plot")
),
mainPanel(
plotlyOutput("plot")
)
)
)
server <- function(input, output) {
data <- eventReactive(input$add,{
get(paste0("data",input$option,input$type, input$group))
})
output$plot <- renderPlotly({
data <- data()
p <-plot_ly(type = "scatter", mode="lines")
p<-add_data(p, data) %>% add_trace(p, x= ~x, y = ~y)
p
})
}
shinyApp(ui = ui, server = server)
I expect that the user can choose the option, type and group in the radiobutton selections, then add the curve. After that, make a new selection of option, type and group and add the new curve to the already existing one in the plot. The user should be able to do this several times. Do you know how this can be achived?
Thanks!
You will have to use a reactiveValues object, in which you save already added traces.
Then you can use add_trace with a for loop for example:
I changed your eventReactive to an observeEvent, which appends the new data to the reactiveValues list.
To prevent plotting the same object twice, I created a reactiveValues usedData object, which will save the plot legend names. If the name is already at hand, nothing will be added to the plot.
server <- function(input, output) {
d <- reactiveValues(a=NULL, name=NULL)
usedData <- reactiveValues(d = NULL)
data <- observeEvent(input$add,{
src = paste0("data",input$option,input$type, input$group)
var <- get(src)
if (src %in% usedData$d) {
print("Data is already plotted")
req(F)
}
if (is.null(d$a)) {
d$a <- list(var)
d$name = list(src)
usedData$d <- src
} else {
d$a <- append(d$a, list(var))
d$name = append(d$name, list(src))
usedData$d <- append(usedData$d, src)
}
})
output$plot <- renderPlotly({
req(d$a)
data <- d$a
names = d$name
p <- plot_ly(data = data[[1]], type = "scatter", mode="lines")
if (length(data) == 1) {
p <- add_trace(p, x= ~x, y = ~y, name = names)
} else {
for (i in 1:length(data)) {
d <- data[[i]]
p <- add_trace(p, data = d, x= ~x, y = ~y, type = "scatter", mode="lines", name = names[[i]])
}
}
p
})
}
Related
This shiny app has some radio buttons to see whether the plotly object needs to have a log scale. The textOutput verifies that the reactive function is following the changes in the input, yet the layout does not change.
Could anyone help?
library(tidyverse)
library(plotly)
c1 <- c(1,2,3,4,5)
c2 <- c(6,3,4,6,5)
c3 <- c(1,2,3,4,5)
df<- data.frame(c1,c2,c3)
cols <- names(df)
ui <- fluidPage(
titlePanel("Log Test"),
sidebarLayout(
sidebarPanel(
selectInput("x",
"x-axis",
cols),
selectInput("y",
"y-axis",
cols),
radioButtons("rb", "Log Axis", choiceNames = list("X", "Y", "Both", "None"), choiceValues = list("X", "Y", "Both", "None"))
),
mainPanel(
plotlyOutput("plot"),
textOutput("note")
)
)
)
server <- function(input, output, session) {
x <- reactive({
df[,input$x]
})
y <- reactive({
df[,input$y]
})
logsc <- reactive({
if (input$rb=='X'){
list('log','linear')
}else if (input$rb=='Y'){
list('linear','log')
}else if (input$rb=='Both'){
list('log','log')
}else{
list('linear','linear')
}
})
output$plot <- renderPlotly(
{
plot1 <- plot_ly(
x = x(),
y = y(),
type = 'scatter',
mode = 'markers',
)
plot1 <- layout(plot1, xaxis = list(type = logsc()[1], ticks ='inside'),yaxis = list(type = logsc()[2], ticks = 'inside'))
plot1
}
)
output$note <- renderText({
paste0("rb ", logsc()[1],"-", logsc()[2])
})
}
shinyApp(ui = ui, server = server)
As you checked it, reactive works fine. Your issue is with logsc() value (of type list) and being subset with single bracket (like a vector).
Single bracket subsetting of a list returns a list containing one item:
> list(1,2,3)[2]
[[1]]
[1] 2
Double bracket subsetting of a list returns a single item of the list
> list(1,2,3)[[2]]
[1] 2
You have been fooled by paste that unlisted you list
To fix your code you can write the call to layout() this way:
plot1 <- layout(plot1,
xaxis = list(type = logsc()[[1]],
ticks ='inside'),
yaxis = list(type = logsc()[[2]],
ticks = 'inside'))
I am having issues to be able to subset dynamically a dataframe by the column selected in a dropdown menu. Basically, I want to allow the user to decide which one is going to be the column to be on the y axis.
File global.R:
library(shiny)
library(plotly)
# Cars
data("USArrests")
USArrests$state <- row.names(USArrests)
File ui.R:
ui <- fluidPage(
fluidRow(
selectInput(inputId = "select_col",
label = tags$h4("Select Column"),
choices = c("Murder", "Assault", "UrbanPop", "Rape"),
selected = "Murder"
),
plotlyOutput("plot")
)
)
File server.R:
server <- function(input, output) {
output$plot <- renderPlotly({
plot_ly(USArrests,
x = ~state,
y = ~input$select_col, # this works but is not reactive y = ~Murder
type = 'bar')
})
}
Here on this last file is the problem that I am having. It is not accepting as a valid input the value from the select_col dropdown menu (y = ~input$select_col).
Bad Solution:
I have come up with this solution, bad I do not like it. It is too verbose. There is a more efficient way to do it?
Corrected server.R:
server <- function(input, output) {
output$plot <- renderPlotly({
df <- USArrests[c('state', input$select_col)]
names(df) <- c('state', 'to_y')
plot_ly(df,
x = ~state,
y = ~to_y,
type = 'bar')
})
}
One option is to generate the formula programmatically:
server <- function(input, output) {
output$plot <- renderPlotly({
plot_ly(USArrests,
x = ~state,
y = formula(paste("~", input$select_col)),
type = 'bar')
})
}
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)
})
}
In ShinyApp, I want to plot a graph whose name has an interactive input value. So in the ui.R side, the user chooses an input value from 0, 1 or 2. And in the server.R side, I want the App to plot a graph whose name is either pl0, pl1 or pl2. That is to say, if the user chooses 0 as an input value, the App plots a graph pl0, so does the same for pl1 for input 1, and for pl2 and input 2. I am using plotly library for plotting graphs.
I have tried print(), plot(), return(), but neither of them worked.
Any solution or advice would be appreciated. Thank you very much!
Here is my ui.R
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Star Cluster Simulations"),
# Sidebar with a slider input for time
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "time",
label = "Select time to display a snapshot",
min = 0,
max = 2,
value = 0)
),
# Show a plot of the generated distribution
mainPanel(
plotlyOutput("distPlot")
)
)
))
And here is my server.R
library(shiny)
library(plotly)
# load data
for(i in 0:2) {
infile <- paste0("Data/c_0", i, "00.csv")
a <- read.csv(infile)
b <- assign(paste0("c_0", i, "00"), a)
names(a) <- paste0("c_0", i, "00")
pl <- plot_ly(b, x = ~x, y = ~y, z = ~z, color = ~id) %>%
add_markers() %>%
layout(scene = list(xaxis = list(title = 'x'),
yaxis = list(title = 'y'),
zaxis = list(title = 'z')))
assign(paste0("pl", i), pl)
}
# shinyServer
shinyServer(function(input, output) {
output$distPlot <- renderPlotly({
# this doesn't work
print(paste0("pl", input$time))
})
})
I can't test this since your question isn't reproducible (i.e. doesn't include data), but one way to switch between text values (i.e. the values returned from Shiny inputs) and R objects is by making a reactive expression that uses the switch function. You can call the reactive expression (in the case below, plot.data()) inside renderPlotly (or any other render function) to switch between datasets.
shinyServer(function(input, output) {
plot.data <- reactive({
switch(paste0("pl", input$time),
"pl0" = pl0,
"pl1" = pl1,
"pl2" = pl2)
})
output$distPlot <- renderPlotly({
plot.data()
})
})
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)
})