Using a dynamic UI to draw a 3d plot in shiny - r

I have a dataframe:
df1<-data.frame(a=rnorm(100),b=rnorm(100),c=rnorm(100),ID1=c("A","B"),ID2=(c("A","B","C","D")))
I am drawing a 3d plot with plotly by adding add_trace in a loop, like:
library(shiny)
library(plotly)
library(tidyverse)
df1<-data.frame(a=rnorm(100),b=rnorm(100),c=rnorm(100),ID1=c("A","B"),ID2=(c("A","B","C","D")))
test<-unique(df1$ID2)
tempt.col<-c("red","blue","green","yellow")
p<-plot_ly()
for(i in 1:length(test)){
df2<-df1[df1$ID2==test[i],] %>%
select(a,b,c)
p<-add_trace(p=p,
data = df2,
x=~a,y=~b,z=~c,
type="scatter3d",
marker = list(size=5,color=tempt.col[i]),
mode="markers"
)
}
p
It works very well like:
Now I want to achieve this in shiny, I would like to generate colourInput based on the length of the selected ID, the ui:
ui<-fluidPage(
fluidRow(
sidebarPanel(
selectInput("select1","Select the ID",choices = colnames(df1[,4:5]),multiple = FALSE),
actionButton("act1","Go"),
uiOutput("ui1"),
),
mainPanel(
tableOutput("table1"),
plotlyOutput("plot.3d",height = "1000px")
)
)
)
server:
server<-function(input,output){
tempt.group<-reactive({
unique(df1[,input$select1])
})
observeEvent(input$act1,{
tempt.vector<-list()
tempt.col.name<-isolate(
vector(mode = "list",length = 2)
)
for(i in 1:length(tempt.group())){
tempt.vector[[i]]<-colourpicker::colourInput(
inputId = paste0("ColorID",i),
label = tempt.group()[i])
tempt.col.name[[1]][i]<-paste0("ColorID",i)
tempt.col.name[[2]][i]<-tempt.group()[i]
}
output$ui1<-renderUI({
tempt.vector
})
names(tempt.col.name)<-c("inputId","label")
col.name<-reactive({
data.frame(sapply(tempt.col.name,cbind))
})
col.df<-reactive({
tempt.col.df<-reactiveValuesToList(input)
data.frame(
names = names(tempt.col.df[grepl("ColorID", names(tempt.col.df))]),
values = unlist(tempt.col.df[grepl("ColorID", names(tempt.col.df))], use.names = FALSE)
)
})
group.col.df<-reactive({
merge(col.df(),col.name(),by.x="names",by.y="inputId")
})
output$table1<-renderTable(
group.col.df()
)
pp<-reactive({
p<-plot_ly()
for(i in 1:length(tempt.group())){
# col<-group.col.df()[group.col.df()[,"label"]==tempt.group()[i],"values"] ####it should be something wrong with here
df2<-df1[df1$ID==tempt.group()[i],] %>%
select(a,b,c)
p<-add_trace(p=p,
data = df2,
x=~a,y=~b,z=~c,
type="scatter3d",
# marker = list(size=5,color=col[i]), ####it should be something wrong with here
mode="markers"
)
}
p
})
output$plot.3d<-renderPlotly({
pp()
})
})
}
shinyApp(ui=ui,server=server)
The app is like:
I want to fetch the colourInput and pass to the color of the 3d scatter plot, but nothing works. The page either keeps refreshing or frozen,
That must be something wrong with col<-group.col.df()[group.col.df()[,"label"]==tempt.group()[i],"values"] and marker = list(size=5,color=col[i]),
please help.

The below works as intended.
library(shiny)
library(plotly)
df1<-data.frame(a=rnorm(100),b=rnorm(100),c=rnorm(100),ID1=c("A","B"),ID2=(c("A","B","C","D")))
# Define UI
ui<-fluidPage(
fluidRow(
sidebarPanel(
selectInput("select1","Select the ID",choices = colnames(df1[,4:5]),multiple = FALSE),
# actionButton("act1","Go"),
uiOutput("myui"),
# keep track of the last selection on all selectInput created dynamically
),
mainPanel(
#tableOutput("table1"),
plotlyOutput("plot.3d",height = "1000px")
)
)
)
# Define server logic required to draw a histogram
server<-function(input,output){
rv <- reactiveValues(mygroup=0, uitaglist = list(), uilabels = list(), input_subset = list(), plotly=NULL)
observeEvent(input$select1, {
newgroup <- unique(df1[,input$select1])
rv$mygroup <- newgroup
# ui tags
rv$uitaglist <- list()
for(i in 1:length(rv$mygroup)){
rv$uitaglist[[i]]<-colourpicker::colourInput(
inputId = paste0("ColorID",i),
label = rv$mygroup[i])
rv$uilabels[[i]] <- paste0("ColorID",i)
}
})
output$myui <- renderUI({
rv$input_subset <- rv$uitaglist
})
observe({
rv$input_subset <- lapply(rv$uilabels, function(x) input[[x]])
p<-plot_ly()
for(i in 1:length(rv$mygroup)) {
df2<-df1[df1$ID2 == rv$mygroup[i],] %>% select(a,b,c)
p<-add_trace(p=p,
data = df2,
x=~a,y=~b,z=~c,
type="scatter3d",
marker = list(size=5,color=rv$input_subset[[i]]),
mode="markers"
)
}
rv$plotly <- p
})
output$plot.3d<-renderPlotly({
rv$plotly
})
} # end server
# Run the application
shinyApp(ui = ui, server = server)
The main difficulty was to observe all your dynamically-generated UI inputs at once. Turns out it could be done using observe and lapply.
Observing several inputs is problematic because the error Must use single string to index into reactivevalues is returned by trying to index input by a vector or list.
Now, Why this can't be done out-of-the-box is a good question.

Related

Lists containing reactive items not fully generating in shiny

I fixed a bug, but I do not understand why it happened in the first place. Can anyone help clarify?
I am building an interactive data explorer app, with each figure contained in its own module. Under my first approach ("old version that does not work") I first build a look-up list and then use it to display the correct control and plot.
However, no plot is displayed until you have clicked on all the options in the figure selection list.
I fixed this by switching to the "new version that works" without an intermediate look-up list. But do not understand why this happened.
Minimal reproducible example below, sorry about the length.
# required packages
library(shiny)
library(tidyverse)
## ui ----
ui = fluidPage(
sidebarLayout(
sidebarPanel(
uiOutput("plot_controls"),
width=3
),
mainPanel(
selectInput("selected_plot", "Select figure:", choices = c("Histogram", "Scatter")),
plotOutput("plot_plot", height = "700px"),
width=9
)))
## server ----
server = function(input, output, session) {
data(starwars)
### modules ----
fig_histogram = callModule(figure_histogram_plot, "histogram", datafile = starwars)
fig_scatter = callModule(figure_Scatter_Plot, "scatter", datafile = starwars)
module_list = list( fig_histogram, fig_scatter )
### old version that does not work ----
resource.map_text_to_plot = reactive({
map = list("Histogram" = module_list[[1]]$plot(), "Scatter" = module_list[[2]]$plot())
})
output$plot_plot = renderPlot({ resource.map_text_to_plot()[input$selected_plot] })
resource.map_text_to_control = reactive({
map = list("Histogram" = module_list[[1]]$control, "Scatter" = module_list[[2]]$control)
})
output$plot_controls = renderUI({ resource.map_text_to_control()[input$selected_plot] })
### new version that works ----
# output$plot_controls = renderUI({
# for(module in module_list)
# if(module$text == input$selected_plot)
# return(module$control)
# })
#
# output$plot_plot = renderPlot({
# for(module in module_list)
# if(module$text == input$selected_plot)
# return(module$plot())
# })
}
shinyApp(ui = ui, server = server)
The figure modules are as follows:
### histogram - server ----
figure_histogram_plot = function(input, output, session, datafile){
text = "Histogram"
control = sliderInput(session$ns("num_bins"), "Number of bins", min = 5, max = 50, value = 30)
plot = reactive({
p = ggplot(data = datafile) + geom_histogram(aes_string(x = "height"), bins = input$num_bins)
return(p)
})
return(list(text = text, plot = plot, control = control))
}
### scatter - server ----
figure_Scatter_Plot = function(input, output, session, datafile){
text = "Scatter"
control = radioButtons(session$ns("plot_design"), "Plot design", choices = c("points", "lines", "both"))
plot = reactive({
p = ggplot(data = datafile)
if(input$plot_design != "lines")
p = p + geom_point(aes_string( x = "mass", y = "height" ))
if(input$plot_design != "points")
p = p + geom_line(aes_string( x = "mass", y = "height" ))
return(p)
})
return(list(text = text, plot = plot, control = control))
}
So I think the problem is when exactly you are evaluating the reactive element. When you use () to "call" a reactive element, it's not really reactive any more. When you set up the list, use
resource.map_text_to_plot = reactive({
map = list("Histogram" = module_list[[1]]$plot,
"Scatter" = module_list[[2]]$plot)
map
})
and when you set up the renderPlot, use
output$plot_plot = renderPlot({
resource.map_text_to_plot()[[input$selected_plot]]() })
Note we removed the () from the list, and put it in the render function instead.
Also, your controls aren't being initialized before the first plot is drawn, so the input$plot_design value can be NULL and you don't seem to check for that which causes a problem at the first draw (you will briefly see an error most likely)

Produce a graph from an editable table in Shiny

I have a shiny application with the following ui:
library(rhandsontable)
library(shiny)
library(ggplot2)
ui = fluidPage(
# App title ----
titlePanel("Tabsets"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Tabset w/ plot, summary, and table ----
tabsetPanel(type = "tabs",
tabPanel("Summary", rHandsontableOutput('contents'),
actionButton("saveBtn", "Save changes")
),
tabPanel("Tab",
rHandsontableOutput('contentFinal')),
tabPanel("Dashboard",
plotOutput('dashboard1'))
)
)
)
)
And the following server
library(dplyr)
library(rhandsontable)
options(shiny.maxRequestSize = 9*1024^2)
server = function(input, output) {
values <- reactiveValues()
Post <- c("", "")
list2 <- c(12,13)
df <- data.frame(Post, list2)
output$contents <- renderRHandsontable({
rhandsontable(df, width = 550, height = 300) %>%
hot_col(col = "Post", type = "dropdown")
})
saveData <- eventReactive({input$saveBtn},{
finalDF <- hot_to_r(input$contents)
finalDF$Post <- ifelse(finalDF$Post =="",NA,finalDF$Post)
newDF <- finalDF[complete.cases(finalDF),]
return(newDF)
})
output$contentFinal <- renderRHandsontable(
rhandsontable(saveData())
)
output$dashboard1 <- renderPlot(
ggplot(input$contentFinal, aes(x = Post, y = list2 )) +
geom_bar(stat = "identity")
)
observeEvent(input$saveBtn, saveData())
}
shinyApp(ui = ui, server = server)
The flow is like this:
In the first tab, I bring up data with an empty post column
In this tab, I can add a name for the post and save it.
As soon as I save he rows with values for post become visible in the next tab.
Then the next thing I want to do is to have a visual in the dashboard tab that shows the data. Therefore I create:
output$dashboard1 <- renderPlot(
ggplot(input$contentFinal, aes(x = Post, y = List2 )) +
geom_bar(stat = "identity")
)
This however gives me the following ggplot2 errror:
ggplot2 doesn't know how to deal with data of class list
Any thoughts on what goes wrong here?
The problem is because input$contentFinal is handsontable data. We need to convert it to R object using hot_to_r function.
The ggplot should be plotted using the following:
ggplot(hot_to_r(input$contentFinal), aes(x = Post, y = list2 )) +
geom_bar(stat = "identity")
Hope it helps!

Shiny renderUI with multiple inputs

My Shiny App has multiple inputs that depend on the number of variables used. A simplified version, though not working, is below. I was able to get the UI to update based upon the numericInput using a function called Make.UI which I used to make uiOutput, but getting the inputs back into the server is beyond my Shiny skill set! Any suggestions would be greatly appreciated.
gwynn
library(shiny)
D = matrix(runif(400), nrow = 20)
colnames(D) = labs = sapply(1:20, function(i) {paste0("col",i)})
# Define UI for application that summarises data
ui <- fluidPage(
# Application title
titlePanel("Summaries"),
# Select columns to get fed into summary
tabsetPanel(
tabPanel("Matching Variables Info",
sidebarPanel(
numericInput("NoVars","No. of variables to summarize",
value = 3, min = 2, max = dim(D)[2]),
uiOutput("VarsInput")
),
# Show summaries of columns choosen above
mainPanel(
verbatimTextOutput("dataInfo")
)
)
)
)
# Define the server code
server <- function(input, output){
Make.UI <- function(NoV){
C = sapply(1:NoV, function(i){paste0("cols",i)})
L = sapply(1:NoV, function(i){paste0("label",i)})
output = tagList()
for(i in seq_along(1:NoV)){
output[[i]] = tagList()
output[[i]][[1]] = selectInput(C[i], "Variable to summarize:", labs)
output[[i]][[2]] = textInput(L[i], label = "Label for variable:",
value = "Label for variable Here")
} ## for loop
output
} # closes Make.UI function
K <- reactive({
input$NoVars
})
output$VarsInput <- renderUI({
Make.UI(K())
})
output$dataInfo <- renderPrint({
C <- sapply(1:K(), function(i) {input[[paste0("cols",i)]]})
## the code in the line above doesn't work
summary(D[, C()])
})
}
# Return a Shiny app object
shinyApp(ui = ui, server = server)
Like I wrote in the first comment, I am unsure about the Make.UI()function. If you really want to keep it as a seperate function you should make it reactive. Or just use it as I did in the code below.
Moreover, in output$dataInfo <- renderPrint({ C is not a reactive() function so you would need to remove brackets there.
library(shiny)
D = matrix(runif(400), nrow = 20)
colnames(D) = labs = sapply(1:20, function(i) {paste0("col",i)})
# Define UI for application that summarises data
ui <- fluidPage(
# Application title
titlePanel("Summaries"),
# Select columns to get fed into summary
tabsetPanel(
tabPanel("Matching Variables Info",
sidebarPanel(
numericInput("NoVars","No. of variables to summarize",
value = 3, min = 2, max = dim(D)[2]),
uiOutput("VarsInput")
),
# Show summaries of columns choosen above
mainPanel(
verbatimTextOutput("dataInfo")
)
)
)
)
# Define the server code
server <- function(input, output){
K <- reactive({
input$NoVars
})
output$VarsInput <- renderUI({
NoV = K()
C = sapply(1:NoV, function(i){paste0("cols",i)})
L = sapply(1:NoV, function(i){paste0("label",i)})
output = tagList()
for(i in seq_along(1:NoV)){
output[[i]] = tagList()
output[[i]][[1]] = selectInput(C[i], "Variable to summarize:", labs)
output[[i]][[2]] = textInput(L[i], label = "Label for variable:",
value = "Label for variable Here")
}
output
})
output$dataInfo <- renderPrint({
C <- sapply(1:K(), function(i) {input[[paste0("cols",i)]]})
## the code in the line above doesn't work
summary(D[, C])
})
}
# Return a Shiny app object
shinyApp(ui = ui, server = server)

Shiny app not reacting to changes in a Biostrings::DNAString object

I'm working on a shiny app that accepts a DNA sequence (e.g. "ACTGACTG"), does some calculations and plots the result when a button is clicked. When I store a Biostrings::DNAString in a reactiveValues object, my shiny app only reacts to changes if the number of characters of the sequence changes, e.g. if "AA" is entered first, the plot doesn't change if "CC" is then entered but does change if "AAAA" is entered. It responds to all changes if I store the object as a character. Here's a simplified example:
library(shiny)
library(shinyBS)
library(Biostrings)
library(ggplot2)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
ref_seqs <- textInput("ref_seqs", "Sequence", width = "100%",
value = NULL, placeholder = "ATGCTGCTGGTTATTAGATTAGT"),
run_guide <- bsButton("run", 'Run', type = "action",
style = "success", block = TRUE)
),
mainPanel(
plotOutput("reference")
)
)
)
server <- function(input, output) {
ref <- reactiveValues(sq = NULL)
dat <- reactive({
req(input$run)
chrs <- strsplit(as.character(ref$sq),"")[[1]]
data.frame(label = chrs, x = seq_along(chrs))
})
observeEvent(input$run, {
ref$sq <- Biostrings::DNAString(input$ref_seqs)
#ref$sq <- input$ref_seqs
})
output$reference <- renderPlot({
ggplot(dat(), aes(x = x, y = factor(1), label = label)) + geom_text(size = 12)
})
}
shinyApp(ui = ui, server = server)
If I comment out the line ref$sq <- Biostrings::DNAString(input$ref_seqs) and uncomment the line below it, the plot updates upon changes.
Can anyone explain why this happens? Do reactiveValues only work with base types? Thanks!

Multiplot on the same graph with checkboxGroupInput in a Shiny App

I want to plot on the same graph one or more plots, depending on the boxes checked in my "checkboxGroupInput".
After checking a few topics on SO, I have found that a easy-to-use solution would be to add "add=TRUE" in the second, third...plots.
Here is a simplified copy of my code :
Server.R
function(input, output) {
dataInput <- reactive({
#I use getMyPlotValues with two parameters.
#idFcast is the one which doesn't work.
m_PARAM$idSite <- input$country
m_PARAM$idFcast <- input$model[i]
getMyPlotValues(m_PARAM)
})
output$plot1 <- renderPlot({
## We plot all the models on the same graph.
if(length(input$model) > 0)
{
firstplot<- TRUE
#To put add = TRUE for the second, third... plots
for(i in 1:length(input$model))
{
valueshere <- dataInput()
#We pick up the vectors (ts,obs) with the function
#And plot them.
if(firstplot)
{
plot(valueshere$ts,valueshere$obs)
}
else
{
plot(valueshere$ts,valueshere$obs,add=TRUE)
}
firstplot <- FALSE
}
}
})
}
And now the UI.R simplified :
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
fluidRow(
column((4),
selectInput("country", label = h3("Pays"),
choices = list("First" = 2,"Second" = 1), selected = 1)
)
fluidRow(
#checkboxGroupInput
column(6,
checkboxGroupInput("model",
choices = list("First" = 1,"Second"=2,"Third"=3),
selected = 1)
)
)
),
mainPanel
(
plotOutput("plot1")
)
)
)
)
I found on this topic (How can I pass data between functions in a Shiny app) that I could try to pass the variable "i" by writing valueshere <- dataInput()$i... Because I have the feeling this is where the error comes from. I try do display input$model and it works, input$model[i] doesn't.
There must be a problem in my code, as I am a beginner in Shiny, but I couldn't fix, even with all the topics about Shiny on SO.
Thanks for your help and have a nice day !
Seems that add argument isn't work. I finished with the solution that consists in creating plots for all your models and only displays them when it's value is selected. So here is:
I have tried to make your example reproduceble so you will observe some changes
UPDATE: Supposing your SQL statement will give you a vector with your model names (models_list) you could generate Shiny objects using lapply (like here)
library(shiny)
# Simulate data
set.seed(189)
df <- data.frame("obs" = 1:30, "ts" = (rnorm(30)+100)
, "country" = rep(c("First", "Second"), 15)
, "models" = rep(c("First", "Second", "Third"), 10),
stringsAsFactors = F)
# Suppose your SQL query returns this kind of output
models_list <- c("First", "Second", "Third")
models_num <- length(models_list)
# Run App
shiny::runApp(list(
ui = fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("country", label = "Countries",
choices = c("First", "Second"), selected = "First"),
checkboxGroupInput("model", label = "Models",
choices = c("First", "Second", "Third"),
selected = "First")
),
mainPanel(
lapply(1:models_num, function(i) {
plotOutput(paste0('plot', i))
})
)
)
),
server = function(input, output) {
dataInput <- reactive({
df_out <- df[(df$country == input$country), ]
return(df_out)
})
lapply(1:models_num, function(i) {
output[[paste0('plot', i)]] <- renderPlot({
if(any(input$model %in% models_list[i])){
valueshere <- dataInput()[df$models == models_list[i],]
plot(valueshere$ts,valueshere$obs)
}
})
})
}
))

Resources