I am trying to make multiple dynamic plots (one does not know how many plots will output) based on one or more selected dropdown value(s) (species in this case).
I did succeed making plots based on the dropdown. For instance, two plots are displayed if a user selected two values/species from the dropdown list, one plot is displayed if one value/species is selected.
Although the number of plots match the number of dropdown values, the plots show a duplicate if two or more dropdown values/species are selected (it only works if exactly one value is selected). Any advice would be of great help.
The below code uses the iris dataset in R.
image1
image2
library(shiny)
library(shinyWidgets)
library(ggplot2)
library(tidyverse)
library(shinydashboard)
species = c("setosa", "versicolor", "virginica")
ui <- dashboardPage(
dashboardHeader(title = "ddPCR"),
sidebar <- dashboardSidebar(
sidebarMenu(
menuItem("General Overview", tabName = "tab1", icon = icon("dashboard"))
)
),
body <- dashboardBody(
tabItems(
tabItem(
tabName = "tab1",
uiOutput("species_dropdown"),
DT::dataTableOutput("table1"),
uiOutput("plots")
)
)
)
)
server <- function(input, output) {
output$species_dropdown <- renderUI({
pickerInput(
"var1",
"Species:",
choices = species,
options = pickerOptions(
actionsBox = T,
header = "Close",
liveSearch = T
),
multiple = T
)
})
filtered_data <- reactive({
iris %>%
filter(Species %in% input$var1)
})
output$table1 <- DT::renderDataTable({
req(input$var1)
filtered_data()
})
# Insert the right number of plot output objects into the web page
output$plots <- renderUI({
req(input$var1)
plot_output_list <- lapply(1:length(input$var1), function(i) {
plotname <- paste("plot", i, sep="")
plotOutput(plotname, height = 280, width = 250)
})
do.call(tagList, plot_output_list)
})
for (i in 1:length(species)) {
local({
plotname <- paste("plot", i, sep="")
output[[plotname]] <- renderPlot({
ggplot(filtered_data(), aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
labs(title = paste(input$var1, sep = ""), x = "Sepal Length", y = "Sepal Width")
})
})
}
}
shinyApp(ui, server)
From the comments in this app on line 34:
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.
So you need to assign i to a local variable otherwise it will be the same for all renderPlots, hence why your plots are identical. Also, your title should be changed to index the inputs, otherwise only the first element is used to the title argument because paste will return a list when it is passed a list, while title requires just one value.
Full app:
library(shiny)
library(shinyWidgets)
library(ggplot2)
library(tidyverse)
library(shinydashboard)
species = c("setosa", "versicolor", "virginica")
ui <- dashboardPage(
dashboardHeader(title = "ddPCR"),
sidebar <- dashboardSidebar(
sidebarMenu(
menuItem("General Overview", tabName = "tab1", icon = icon("dashboard"))
)
),
body <- dashboardBody(
tabItems(
tabItem(
tabName = "tab1",
uiOutput("species_dropdown"),
DT::dataTableOutput("table1"),
uiOutput("plots")
)
)
)
)
server <- function(input, output) {
output$species_dropdown <- renderUI({
pickerInput(
"var1",
"Species:",
choices = species,
options = pickerOptions(
actionsBox = T,
header = "Close",
liveSearch = T
),
multiple = T
)
})
filtered_data <- reactive({
iris %>%
filter(Species %in% input$var1)
})
output$table1 <- DT::renderDataTable({
req(input$var1)
filtered_data()
})
# Insert the right number of plot output objects into the web page
output$plots <- renderUI({
req(input$var1)
plot_output_list <- lapply(1:length(input$var1), function(i) {
plotname <- paste("plot", i, sep="")
plotOutput(plotname, height = 280, width = 250)
})
do.call(tagList, plot_output_list)
})
for (i in 1:length(species)) {
local({
my_i <- i #crucial
plotname <- paste("plot", my_i, sep="") # use my_i instead of i
output[[plotname]] <- renderPlot({
ggplot(filtered_data(), aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
labs(title = paste(input$var1[my_i], sep = ""), x = "Sepal Length", y = "Sepal Width") # title needs input$var1 indexed as paste will return a list otherwise, in which case only a first element gets used for the title hence all titles are identical
})
})
}
}
shinyApp(ui, server)
Related
I want to create a r shiny app in which for each y variable one tab is produced (so tabs must be dynamically generated) and in each tab the plots are in two columns and n rows (as in the figure). However, I received the following error:
`x` must contain exactly 1 expression, not 2
In fact, I want to combine the two codes to create what I described above:
Code 1: This code creates one tab for every y variable
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "x", label = "X var", choices = names(mtcars), selected = names(mtcars)[1], multiple = F),
selectizeInput(inputId = "y", label = "Y var", choices = names(mtcars), selected = names(mtcars)[1], multiple = T)
),
mainPanel(
uiOutput("plots")
)
)
)
server <- function(input, output, session) {
output$plots <- renderUI({
plt_list <- list()
plt_list <- lapply(input$y, function(y){
renderPlot({
ggplot(mtcars, aes_string(input$x, y)) + geom_point()
})
})
names(plt_list) <- input$y
do.call(tabsetPanel, c(id='tab',lapply(input$y, function(y) {
tabPanel(
title=paste0('tab ', y),
fluidRow(column(6, plt_list[[y]]))
)
})))
})
}
shinyApp(ui, server)
Code 2: This code creates multiple plots of y vs. different x variables, and the plots are arranged in a way so that we have two columns and n rows.
library(shiny)
library(ggplot2)
library(plotly)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectInput(inputId = "y", label = "Y", choices = names(mtcars), selected = names(mtcars)[1], multiple = F),
selectizeInput(inputId = "xvars", label = "X", choices = names(mtcars), selected = names(mtcars)[1],
multiple = T)),
mainPanel(uiOutput("allplots"))
)
)
server <- function(input, output, session) {
output$allplots <- renderUI({
plt_list <- list()
plt_list <- lapply(input$xvars, function(x){
renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = input$y)) + geom_point()
})
})
if (length(input$xvars) == 1) {
plottoUI <- fluidRow(column(12, plt_list[1]))
} else {
plottoUI <- fluidRow(
lapply(1:length(input$xvars), function(x) column(6, plt_list[x]))
)
}
return(plottoUI)
})
}
shinyApp(ui, server)
Code 3: The combination of the codes above that results in the error:
library(shiny)
library(ggplot2)
library(plotly)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectizeInput(inputId = "y", label = "Y", choices = names(mtcars), selected = names(mtcars)[1:2], multiple = T),
selectizeInput(inputId = "xvars", label = "X", choices = names(mtcars), selected = names(mtcars)[1:3],
multiple = T)),
mainPanel(uiOutput("allplots"))
)
)
server <- function(input, output, session) {
output$allplots <- renderUI({
plt_list <- list()
plots <- lapply(input$y, function(y){
plt_list <- lapply(input$xvars, function(x){
renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = input$y)) + geom_point()
})
})
})
names(plots) <- input$y
plotarrange <- lapply(input$y, function(y){
if (length(input$xvars) == 1) {
plottoUI <- fluidRow(column(12, plots[[y]][1]))
} else {
plottoUI <- fluidRow(
lapply(1:length(input$xvars), function(x) column(6, plots[[y]][x]))
)
}
return(plottoUI)
})
names(plotarrange) <- input$y
do.call(tabsetPanel, c(id='tab',lapply(input$y, function(y) {
tabPanel(
title=paste0('tab ', y),
plotarrange[[y]]
)
})))
})
}
shinyApp(ui, server)
What is the source of the error and how do I resolve it ?
I found the source of the error in code 3, input$y should have been changes to y as follows
plots <- lapply(input$y, function(y){
plt_list <- lapply(input$xvars, function(x){
renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = y)) + geom_point()
})
})
})
Therefore:
library(shiny)
library(ggplot2)
library(plotly)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectizeInput(inputId = "y", label = "Y", choices = names(mtcars), selected = names(mtcars)[1:2], multiple = T),
selectizeInput(inputId = "xvars", label = "X", choices = names(mtcars), selected = names(mtcars)[1:3],
multiple = T)),
mainPanel(uiOutput("allplots"))
)
)
server <- function(input, output, session) {
rval <- reactiveValues(
plt_list = NULL
)
output$allplots <- renderUI({
plt_list <- list()
plots <- lapply(input$y, function(y){
rval$plt_list <- lapply(input$xvars, function(x){
renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = y)) + geom_point()
})
})
})
names(plots) <- input$y
plotarrange <- lapply(input$y, function(y){
if (length(input$xvars) == 1) {
plottoUI <- fluidRow(column(12, plots[[y]][1]))
} else {
plottoUI <- fluidRow(
lapply(1:length(input$xvars), function(x) column(6, plots[[y]][x]))
)
}
return(plottoUI)
})
names(plotarrange) <- input$y
do.call(tabsetPanel, c(id='tab',lapply(input$y, function(y) {
tabPanel(
title=paste0('tab ', y),
plotarrange[[y]]
)
})))
})
}
shinyApp(ui, server)
I want the plots (generated by plotly) to be laid out in two columns and n rows. The number of rows depends on the number of plots. For instance, the layout should be 3(row) x 2(col) if there are 5 or 6 plots. However, there are two problems with the following code. First, only one of them is repeated when we have multiple plots. Second, they are stacked on top of each other, although the column width is 6.
Here is the code:
library(shiny)
library(ggplot2)
library(plotly)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectInput(inputId = "y", label = "Y", choices = names(mtcars), selected = names(mtcars)[1], multiple = F),
selectizeInput(inputId = "xvars", label = "X", choices = names(mtcars), selected = names(mtcars)[1],
multiple = T)),
mainPanel(uiOutput("allplots"))
)
)
server <- function(input, output, session) {
output$allplots <- renderUI({
plt_list <- list()
for (x in input$xvars){
plt_list[[x]] <- renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = input$y)) + geom_point()
})
}
if (length(input$xvars) == 1) {
plottoUI <- fluidRow(column(12, plt_list[1]))
} else {
plottoUI <- fluidRow(column(6, plt_list[1:length(input$xvars)]))
}
return(plottoUI)
})
}
shinyApp(ui, server)
UPDATTE:
#lz100 seems to have resolved the main issue with the layout. Here is a further update on how to prevent one plot being repeated. I replaced the for loop (I don't know the reason why it didn't work) with lapply.
plt_list <- lapply(input$xvars, function(x){
renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = input$y)) + geom_point()
})
})
So, considering the #lz100 suggestion the final solution will be:
library(shiny)
library(ggplot2)
library(plotly)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectInput(inputId = "y", label = "Y", choices = names(mtcars), selected = names(mtcars)[1], multiple = F),
selectizeInput(inputId = "xvars", label = "X", choices = names(mtcars), selected = names(mtcars)[1],
multiple = T)),
mainPanel(uiOutput("allplots"))
)
)
server <- function(input, output, session) {
output$allplots <- renderUI({
plt_list <- list()
plt_list <- lapply(input$xvars, function(x){
renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = input$y)) + geom_point()
})
})
if (length(input$xvars) == 1) {
plottoUI <- fluidRow(column(12, plt_list[1]))
} else {
plottoUI <- fluidRow(
lapply(1:length(input$xvars), function(x) column(6, plt_list[x]))
)
}
return(plottoUI)
})
}
shinyApp(ui, server)
You need to wrap each plot with a column, not a column for all plots, see below:
library(shiny)
library(ggplot2)
library(plotly)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectInput(inputId = "y", label = "Y", choices = names(mtcars), selected = names(mtcars)[1], multiple = F),
selectizeInput(inputId = "xvars", label = "X", choices = names(mtcars), selected = names(mtcars)[1],
multiple = T)),
mainPanel(uiOutput("allplots"))
)
)
server <- function(input, output, session) {
output$allplots <- renderUI({
plt_list <- list()
for (x in input$xvars){
plt_list[[x]] <- renderPlotly({
mtcars %>% ggplot(aes_string(x = x, y = input$y)) + geom_point()
})
}
if (length(input$xvars) == 1) {
plottoUI <- fluidRow(column(12, plt_list[1]))
} else {
plottoUI <- fluidRow(
lapply(1:length(input$xvars), function(x) column(6, plt_list[x]))
)
}
return(plottoUI)
})
}
shinyApp(ui, server)
I have a shinyApp that allows the user to create a barplot and dynamically select the colors of the plot.
As you can see, this app is working fine, however, as the colors are represented by the Hex code, there is no really a way of telling which color is it until you have plot it,
hece I would like a way to improve the display of the colors selection.
In the webpage (https://ssc.wisc.edu/shiny/users/jstruck2/colorpicker/) there is an example of what I want, although there is no code.
And here you can find a RepEx of my app.
# Shiny
library(shiny)
library(shinyWidgets)
library(shinyjqui)
library(shinyjs)
library(DT)
# Data
library(readxl)
library(dplyr)
library(vcd)
# Plots
library(ggplot2)
not_sel <- "Not Selected"
ui <- navbarPage(
title = "Plotter",
windowTitle = "Plotter",
tabPanel(
"Plotter",
fluidPage(
fluidRow(
sidebarPanel(
title = "Inputs",
fileInput("xlsx_input", "Select XLSX file to import", accept = c(".xlsx")),
selectInput("num_var_1", "Variable X axis", choices = c(not_sel)),
selectInput("num_var_2", "Variable Y axis", choices = c(not_sel)),
br(),
actionButton("run_button", "Run Analysis", icon = icon("play"))
),
# Main panel
mainPanel(
tabsetPanel(
tabPanel(
"Plot",
br(),
plotOutput("plot_1"),
uiOutput("factor"),
)
)
)
)
)
)
)
################# --------------------------------------------------------------
# Server
################# --------------------------------------------------------------
server <- function(input, output){
# Dynamic selection of the data
data_input <- reactive({
#req(input$xlsx_input)
#inFile <- input$xlsx_input
#read_excel(inFile$datapath, 1)
Arthritis
})
# We update the choices available for each of the variables
observeEvent(data_input(),{
choices <- c(not_sel, names(data_input()))
updateSelectInput(inputId = "num_var_1", choices = choices)
updateSelectInput(inputId = "num_var_2", choices = choices)
})
num_var_1 <- eventReactive(input$run_button, input$num_var_1)
num_var_2 <- eventReactive(input$run_button, input$num_var_2)
# data
data_discrete_plot <- reactive({
req(data_input(), input$num_var_1, input$num_var_2)
df <- data_input()
df1 <- as.data.frame(prop.table(table(df[[input$num_var_1]], df[[input$num_var_2]]), margin = 1))
df1
})
output$factor <- renderUI({
#req(input$num_var_2,data_input())
if (is.null(input$num_var_2) | (input$num_var_2=="Not Selected")) return(NULL)
df <- data_input()
uvalues <- unique(df[[input$num_var_2]])
n <- length(uvalues)
choices <- as.list(uvalues)
myorder <- as.list(1:n)
mycolors <- list("#727CA3", "#464653", "#638CAE", "#836A61", "#889328", "#F5AF22", "#DF564B", "#6B4A8C", "#CE7EB8")
nk <- length(mycolors) ## to repeat colors when there are more bars than the number of colors
tagList(
div(br()),
div(
lapply(1:n, function(i){
k <- i %% nk
if (k==0) k=nk
pickerInput(paste0("colorvar",i),
label = paste0(uvalues[i], ": " ),
choices = list(# DisplayOrder = myorder,
FillColor = mycolors),
selected = list( i, mycolors[[k]]),
multiple = T,
options = list('max-options-group' = 1, `style` = "btn-primary"))
})
)
)
})
#observe({print(input$colorvar1)})
output$t1 <- renderDT(data_discrete_plot())
# Function for printing the plots
draw_barplot <- function(data_input) {
n <- length(unique(data_input[,"Var2"]))
val <- list()
myvaluesx <- lapply(1:n, function(i) {
input[[paste0("colorvar",i)]]
if (i==1) val <- list(input[[paste0("colorvar",i)]])
else val <- list(val,input[[paste0("colorvar",i)]])
})
print(myvaluesx)
ggplot(data = data_input, aes(x = Var1, y = Freq, fill = factor(Var2), label = round(Freq, 3))) +
geom_bar(stat = "identity") +
#scale_fill_discrete(guide = guide_legend(fill = myvaluesx, reverse = TRUE)) +
scale_fill_manual(values = unlist(myvaluesx)) +
ylim(0, 1) +
theme_bw()
}
## BarPlot -------------------------------------------------------------------
plot_1 <- eventReactive(input$run_button,{
req(data_input())
draw_barplot(data_discrete_plot())
})
output$plot_1 <- renderPlot(plot_1())
}
# Connection for the shinyApp
shinyApp(ui = ui, server = server)
I am tying to create an R shiny app and I would like to have two selectInput i.e. data set name and column name. Right now, I am able to get data set names in the first Input but I am not able to create dependent column selectIput (whose list will depend upon data set selected). Please guide.
require(shiny)
require(MASS)
a <- data(package = "MASS")[3]
b <- a$results[,3]
ui <- fluidPage(
sidebarPanel(width = 2,
selectInput(inputId = "dsname",label = "Select Dataset:",choices = c(b)),
colnames <- names("dsname"),
selectInput(inputId = "columns", label = "Choose columns",
choices = c(colnames))
)
)
server <- function(input,output) {}
shinyApp(ui <- ui, server <- server)
In order to have "responsive" elements in Shiny, you need to wrap your expressions for computing the responsive elements in reactive({...}).
You could use renderUI in your server() and uiOutput in your ui() with something like this. Here is an example I had built for using some of R's data sets (iris, mtcars, and diamonds):
library(shinythemes)
library(shiny)
library(ggplot2)
ui <- fluidPage(theme = shinytheme("superhero"),
titlePanel("Welcome to Responisve Shiny"),
sidebarLayout(
sidebarPanel(
selectInput("data", "Dataset:",
choices = c("mtcars", "iris", "diamonds")
),
uiOutput("x_axis"),
uiOutput("y_axis"),
uiOutput("color")
),
mainPanel(
plotOutput("distPlot")
)
)
)
server <- function(input, output) {
output$x_axis <- renderUI({
col_opts <- get(input$data)
selectInput("x_axis2", "X-Axis:",
choices = names(col_opts))
})
output$y_axis <- renderUI({
cols2 <- reactive({
col_opts2 <- get(input$data)
names(col_opts2)[!grepl(input$x_axis2, names(col_opts2))]
})
selectInput("y_axis2", "Y-Axis:",
choices = cols2(),
selected = "hp")
})
output$color <- renderUI({
col_opts <- get(input$data)
selectInput("color", "Color:",
choices = names(col_opts),
selected = "cyl")
})
output$distPlot <- renderPlot({
if(input$data == "mtcars"){
p <- ggplot(mtcars, aes_string(input$x_axis2, input$y_axis2, color = input$color)) +
geom_point()
}
if(input$data == "iris"){
p <- ggplot(iris, aes_string(input$x_axis2, input$y_axis2, color = input$color)) +
geom_point()
}
if(input$data == "diamonds"){
p <- ggplot(diamonds, aes_string(input$x_axis2, input$y_axis2, color = input$color)) +
geom_point()
}
print(p)
})
}
shinyApp(ui = ui, server = server)
I am having trouble with some code that I've written.
Here is a sample of the dataset: https://docs.google.com/spreadsheets/d/1C_P5xxzYr7HOkaZFfFiDhanqDSuSIrd2UkiC-6_G2q0/edit?usp=sharing
Objective:
I have a dataset that contains a column of Purchase_Month, candy and freq of the number of times that type of candy was purchased in that given month.
I have an rPlot which I was to change based on the chosen Candy bar in the SelectInput. And output a line chart based on the number of times that candy was purchased that month.
I have my current code below, but it tells me that candyCount is not found.
## ui.R ##
library(shinydashboard)
library(rCharts)
dashboardPage(
dashboardHeader(title = "Dashboard"),
dashboardSidebar(
width = 150,
sidebarMenu(
menuItem("Dashboard", tabName = "dashboard", icon = icon("bar-chart"))
)
),
dashboardBody(
sidebarPanel(
htmlOutput("candy")
),
mainPanel(
showOutput("plot2", "polycharts")
))
)
##server.R##
library(rCharts)
library(ggplot2)
library(ggvis)
server <- function(input, output, session) {
output$candy <- renderUI({
available2 <- dataset[(dataset$candy == input$candy), "candy"]
selectInput(
inputId = "candy",
label = "Choose a candy: ",
choices = sort(as.character(unique(available2))),
selected = unique(available2[1])
)
})
observeEvent(input$candy, {
candyChoice<- toString(input$customer_issue)
print(candyChoice)
candyCount<- dataset[dataset$candy == candyChoice, ]
})
})
output$plot2 <- renderChart2({
p2 <- rPlot(freq~purchase_month, data = candyCount, type = 'line')
p2$guides(y = list(min = 0, title = ""))
p2$guides(y = list(title = sprintf("%s Claims",input$candy)))
p2$addParams(height = 300, dom = 'chart2')
return(p2)
})
}
Updated Data: Why wouldn't this work?
candyCount<- reactive({
dataset[dataset$candy == input$candy, ]
})
output$plot2 <- renderChart2({
p2 <- rPlot(freq~purchase, data = candyCount(), type = 'line')
p2$guides(y = list(min = 0, title = ""))
p2$guides(y = list(title = ""))
p2$addParams(height = 300, dom = 'chart2')
return(p2)
})
output$candy <- renderUI({
available2 <- dataset[(dataset$candy == input$candy), "candy"]
selectInput(
inputId = "candy",
label = "Choose a candy: ",
choices = sort(as.character(unique(available2))),
selected = unique(available2[1])
)
})
In the above you are trying to subset by an input, which is inside your output. The selectInput needs to be inside UI.R.
A working basic example you may find useful.
library(shiny)
df <- read.csv("/path/to/my.csv")
ui <- shinyUI(pageWithSidebar(
headerPanel('Candy Data'),
sidebarPanel(
selectInput('candy', 'Candy', unique(as.character(df[,2])), selected = "Twix")
),
mainPanel(
plotOutput('plot1')
)
))
server <- shinyServer(function(input, output, session) {
selectedData <- reactive({
df[which(df[,2] == input$candy),3]
})
output$plot1 <- renderPlot({
barplot(selectedData())
})
})
shinyApp(ui, server)
In the above example the ui renders a selectInput which has the ID candy. The value, i.e the candy selected is now assigned to input$candy scope. In server we have a reactive function watching for any input change. When the user selects a new candy this function, df[which(df[,2] == input$candy),3] is saying "subset my data frame, df, by the new input$candy". This is now assigned to the selectedData(). Finally we render then boxplot.
EDIT
server.R
require(rCharts)
options(RCHART_WIDTH = 500)
df <- read.csv("path/to/my.csv")
shinyServer(function(input, output, session) {
selectedData <- reactive({
df[which(df[,2] == input$candy),]
})
output$plot1 <- renderChart({
p <- rPlot(freq~purchase_month, data = selectedData(), type = "line")
p$addParams(dom = 'plot1')
return(p)
})
})
ui.R
require(rCharts)
options(RCHART_LIB = 'polycharts')
shinyUI(pageWithSidebar(
headerPanel('Candy Data'),
sidebarPanel(
selectInput('candy', 'Candy', unique(as.character(df[,2])), selected = "Twix")
),
mainPanel(
showOutput('plot1', 'polycharts')
)
))
save files in directory and then runApp.
At available2 you're filtering the data about a selected candy with dataset$candy == input$candy. But you use the same available2 to determine which are the choices at selectInput. I'm guessing you wanted: available2 <- dataset[, "candy"].