Strange actual parameter assignment in shiny's renderPlot - shiny

When using a for loop to render some plots I obtained results I didn't understand. All plots became equal to the (intended) last one. I hope to make this more clear in the example below. Note the plot1 shows a '6' (the last one), not a '5' (as intended).
Can anyone explain this and/or give a solution avoid this kind of behaviour.
shiny ui:
shinyUI (fluidPage(
fluidRow (
column (6, plotOutput ('plot1', height = "180px")),
column (6, plotOutput ('plot2', height = "180px"))
),
fluidRow (
column (6, plotOutput ('plot3', height = "180px")),
column (6, plotOutput ('plot4', height = "180px"))
)
))
shiny server:
shinyServer (function (input, output, session) {
require (ggplot2)
plotId <- c('plot1', 'plot2', 'plot3', 'plot4')
pw <- initPlotWindows ()
output[['plot1']] <- renderPlot ({pw[[1]]})
output[['plot2']] <- renderPlot ({pw[[2]]})
output[['plot3']] <- renderPlot ({pw[[3]]})
output[['plot4']] <- renderPlot ({pw[[4]]})
id <- 'plot1'
i <- 5
output[[id]] <- renderPlot ({pw[[i]]})
id <- 'plot3'
i <- 6
# output[[id]] <- renderPlot ({pw[[i]]})
})
initPlotWindows <- function () {
pw <- vector (mode = 'list', length = 6) # plot window data
for (i in 1:6) pw[[i]] <- p_empty (plotN = i)
pw
}
p_empty <- function (plotN) {
p <- ggplot()
p <- p + annotate("text", label = plotN, x = .5, y = .5, size = 20, colour = "grey")
p <- p + theme(axis.ticks.x = element_blank(), axis.text.x = element_blank(), axis.title.x=element_blank())
p <- p + theme(axis.ticks.y = element_blank(), axis.text.y = element_blank(), axis.title.y=element_blank())
p
}
Resulting output. Plot1 contains a 6 not a 5!

The reason why this happens is because shiny does not execute your server side code line by line. Shiny executes all of the non-reactive code once at the beginning. But renderPlot() and other reactive code will only run when it is necessary. (when they are called)
So what happened is, it assigned 5, and then 6 to i, and only rendered the plot afterwards.
output[[id]] <- renderPlot ({
print("section D")
pw[[i]]})
You can check it pretty easily in the R console using the print() function:
shinyServer (function (input, output, session) {
print("section A")
require (ggplot2)
plotId <- c('plot1', 'plot2', 'plot3', 'plot4')
pw <- initPlotWindows ()
output[['plot1']] <- renderPlot ({
print("section B1")
pw[[1]]})
output[['plot2']] <- renderPlot ({
print("section B2")
pw[[2]]})
output[['plot3']] <- renderPlot ({
print("section B3")
pw[[3]]})
output[['plot4']] <- renderPlot ({
print("section B4")
pw[[4]]})
print("section C")
id <- 'plot1'
i <- 5
output[[id]] <- renderPlot ({
print("section D")
pw[[i]]})
print("section E")
id <- 'plot3'
i <- 6
# output[[id]] <- renderPlot ({pw[[i]]})
})
Console output:
[1] "section A"
[1] "section C"
[1] "section E"
[1] "section B1"
[1] "section B2"
[1] "section B3"
[1] "section B4"
[1] "section D"
Edit:
As for the output[[id]], id gets evaluated at the beginning, so output[[id]] becomes output[[plot1]]. But pw[[i]] is only evaluated when shiny asks the plot to render, which is after the value has been set to 5 and then to 6.
If you want plot1 to show 5, then you could assign id outside of renderPlot(), and assign i inside of renderPlot():
id <- 'plot1'
output[[id]] <- renderPlot ({
i <- 5
pw[[i]]})

Thank you GyD,
From your answer I conclude that a normal for-loop is not possible. The running variable 'i' would than be defined outside and not inside renderPlot().
So it seems that I have to use eval (parse()) for at least renderPlot().
A for-loop like:
for ( i in 1:4) {
pltId <- paste ('plot', i, sep='')
output[[pltId]] <- renderPlot ({pw[i]})
}
would then become (using eval(parse()) for the whole assignment):
for ( i in 1:4) {
eval (parse (text = paste ("output[['plot", i, "']] <- renderPlot ({pw[", i, "]})", sep='')))
}

Your answer should work, but you could generate both the UI elements and the server side with loops. (don't know if that interests you)
I am not a huge fan of for loops in R, so I'm using lapply.
library(shiny)
library(ggplot2)
# I want to plot these elements
vec <- c(5, 2, 3, 4)
# Defining some functions
initPlotWindows <- function () {
pw <- vector (mode = 'list', length = 6) # plot window data
for (i in vec) pw[[i]] <- p_empty (plotN = i)
pw
}
p_empty <- function (plotN) {
p <- ggplot()
p <- p + annotate("text", label = plotN, x = .5, y = .5, size = 20, colour = "grey")
p <- p + theme(axis.ticks.x = element_blank(), axis.text.x = element_blank(), axis.title.x=element_blank())
p <- p + theme(axis.ticks.y = element_blank(), axis.text.y = element_blank(), axis.title.y=element_blank())
p
}
# Creating pw list
pw <- initPlotWindows()
ui <- fluidPage(
# Creating UI from loop
lapply(vec, function(i) {
column(6, plotOutput(paste0('plot', i), height = "180px"))
})
)
server <- function(input, output, session){
# Generating UI output from loop
lapply(vec, function(i) {
output[[paste0('plot', i)]] <- renderPlot({
pw[[i]]
})
})
}
shinyApp(ui, server)

Related

How can I print tops (print[:]) according user preference with slide bar?

How can i run a r shiny app, which print the top features based on user's choice?
I tried the following but I want the user to select, not putting it manually the top 3 for example
library(KEGGgraph)
library(xml2)
library(Rgraphviz)
ui <- fluidPage(
sidebarLayout(
sliderInput("range",
label = "Range of interest:",
min = 0, max = 10, value = c(0, 100))
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plots", plotOutput(outputId="genePlot", width = 1300, height=900)),
tabPanel("Edges", verbatimTextOutput("out_tops")),
tabPanel("Compounds", tableOutput("out_compounds")))
)
)
)
server <- function(input, output) {
output$out_tops <- renderPrint({
mapkGall <- parseKGML2Graph(read_xml(sprintf("%s.xml", input$geneInput)), genesOnly=FALSE)
mapkGsub <- subGraphByNodeType(mapkGall, "gene")
graphs <- list(mapk=mapkGsub, wnt=mapkGall)
merged <- mergeGraphs(graphs)
merged
outs <- sapply(edges(merged), length) > 0
ins <- sapply(inEdges(merged), length) > 0
ios <- outs | ins
mapkGoutdegrees <- sapply(edges(mapkGall), length)
mapkGindegrees <- sapply(inEdges(mapkGall), length)
topouts <- sort(mapkGoutdegrees, decreasing=T)
topins <- sort(mapkGindegrees, decreasing=T)
if(require(org.Hs.eg.db)) {
top_nodes_out <- translateKEGGID2GeneID(names(topouts))
tmp <- c()
for (i in top_nodes_out) {
if (is.na(mget(sprintf("%s",i), org.Hs.egSYMBOL, ifnotfound = NA))) {
tmp <- append(tmp,sprintf("%s",i))
}
else {
tmp <- append(tmp,mget(sprintf("%s",i), org.Hs.egSYMBOL))
}
}
nodesNames_outs <- sapply(tmp, "[[",1)
} else {
nodesNames_outs <- names(topouts)
}
how can I let the user specify the printings by the slider bar?
names(nodesNames_outs) <- names(topouts)
print("top genes with out connections")
print(nodesNames_outs[1:3]) ### Here like something for the slider bar print[:,sliderbar$input]
I would like not to print manually the top 3 but the user to select how many he wants. the code should sort the top genes and print them accordingly when user uses the slidebar
Can you please suggest something?

shiny DT::renderDT() multiple tables

I can't get renderDT() to display multiple data tables that my script creates. The code below. Reading the input table works, the progress indicator progresses through each line, but the hg38 and hg19 tabs are empty in the display.
If I move the hg38 renderDT() inside of the my_data <- reactive( it will display the hg38 table, but I get the following error and nothing in the hg19 tab
```Warning: Error in $: object of type 'closure' is not subsettable```
```105: <Anonymous>```
If I move both renderDT() inside the my_data <- reactive( I get nothing in either tab. I'm clearly misunderstanding something, but I'm not sure what.
library(shiny)
library(DT)
library("dplyr")
library(GenomeInfoDb)
library(BSgenome)
library("MafDb.gnomAD.r2.1.hs37d5")
library("MafH5.gnomAD.v3.1.2.GRCh38")
library(BSgenome.Hsapiens.UCSC.hg19)
library(BSgenome.Hsapiens.UCSC.hg38)
mafdb_hg19 <- MafDb.gnomAD.r2.1.hs37d5
mafdb_hg38 <- MafH5.gnomAD.v3.1.2.GRCh38
hg19 <- BSgenome.Hsapiens.UCSC.hg19
hg38 <- BSgenome.Hsapiens.UCSC.hg38
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"),
#checkboxInput("header", "Header", TRUE),
width = 2
),
mainPanel(
tabsetPanel(
tabPanel("Primers(input)", DT::dataTableOutput("primers")),
tabPanel("SNPs(hg38)", DT::dataTableOutput("hg38")),
tabPanel("SNPs(hg19)", DT::dataTableOutput("hg19"))
)
)
)
)
server <- function(input, output){
primer_file <- reactive(
{
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == "csv", "Please upload a csv file"))
primer_file <- as.data.frame(read.csv(file$datapath, header = TRUE))
return(primer_file)
})
output$primers <- DT::renderDT(primer_file())
my_data <- reactive(
{
primers_hg38 <- data.frame(primer_id=character(),
seq=character(),
chr=character(),
hg38_pos=integer(),
AF_allpopmax_hg38=integer(),
stringsAsFactors=FALSE)
primers_hg19 <- data.frame(primer_id=character(),
seq=character(),
chr=character(),
hg19_pos=integer(),
AF_afr=integer(),AF_amr=integer(), AF_asj=integer(), AF_eas=integer(), AF_fin=integer(), AF_nfe=integer(), AF_oth=integer(),
stringsAsFactors=FALSE)
progress <- shiny::Progress$new()
# Make sure it closes when we exit this reactive, even if there's an error
on.exit(progress$close())
progress$set(message = "Calculating", value = 0)
lastrow <- nrow(primer_file())
firstrow=1
for (no in (firstrow:lastrow))
{
row = primer_file()[no,]
temp_chr <- row$chr
temp_FP <- row$FP
temp_RP <- row$RP
progress$inc(1/lastrow, detail = paste("line ", no))
###################lets do hg38 first######################
subject_hg38 <- hg38[[temp_chr]]
products_hg38 <- matchProbePair(temp_FP,temp_RP,subject_hg38)
amp_start_hg38 = (start(products_hg38))
fp_end_hg38 = (start(products_hg38) + as.integer(nchar(row$FP)) - 1)
rp_start_hg38 = ( (start(products_hg38) + width(products_hg38)) - as.integer(nchar(row$RP)) )
amp_end_hg38 = (start(products_hg38) + width(products_hg38) - 1)
fp_range_hg38 <- GRanges(seqnames=temp_chr, IRanges(start=amp_start_hg38:fp_end_hg38, width=1))
fp_scores_hg38 <- gscores(mafdb_hg38,fp_range_hg38,pop="AF_allpopmax")
fp_scores_hg38 <- as.data.frame(fp_scores_hg38)
rp_range_hg38 <- GRanges(seqnames=temp_chr, IRanges(start=rp_start_hg38:amp_end_hg38, width=1))
rp_scores_hg38 <- gscores(mafdb_hg38,rp_range_hg38,pop="AF_allpopmax")
rp_scores_hg38 <- as.data.frame(rp_scores_hg38)
#primers_hg38 <- data.frame(primer_id=character(),
# seq=character(),
# chr=character(),
# hg38_pos=integer(),
# AF_allpopmax_hg38=integer(),
# stringsAsFactors=FALSE)
primers_hg38 <- bind_rows(primers_hg38,setNames(data.frame(primer_id <- paste0(row$ID,"_F"),
seq <- temp_FP,
chr <- fp_scores_hg38$seqnames,
hg38_pos <- fp_scores_hg38$start,
AF_allpopmax_hg38 <- fp_scores_hg38$AF_allpopmax, stringsAsFactors=FALSE),c("primer_id", "seq", "chr","hg38_pos","AF_allpopmax_hg38")))
#names(primers_hg38) <- c("primer_id","seq","chr","hg38_pos","AF_allpopmax_hg38")
primers_hg38 <- bind_rows(primers_hg38,setNames(data.frame(primer_id <- paste0(row$ID,"_R"),
seq <- temp_RP,
chr <- rp_scores_hg38$seqnames,
hg38_pos <- rp_scores_hg38$start,
AF_allpopmax_hg38 <- rp_scores_hg38$AF_allpopmax,stringsAsFactors=FALSE),c("primer_id", "seq", "chr","hg38_pos","AF_allpopmax_hg38")))
##########################now hg19######################################
#names(primers_hg38) <- c("primer_id","seq","chr","hg38_pos","AF_allpopmax_hg38")
subject_hg19 <- hg19[[temp_chr]]
products_hg19 <- matchProbePair(temp_FP,temp_RP,subject_hg19)
amp_start_hg19 = start(products_hg19)
fp_end_hg19 = ( start(products_hg19) + as.integer(nchar(row$FP)) - 1)
rp_start_hg19 = ( (start(products_hg19) + width(products_hg19)) - as.integer(nchar(row$RP)) )
amp_end_hg19 = (start(products_hg19) + width(products_hg19) - 1)
fp_range_hg19 <- GRanges(seqnames=temp_chr, IRanges(start=amp_start_hg19:fp_end_hg19, width=1))
fp_scores_hg19 <- gscores(mafdb_hg19,fp_range_hg19,pop=c("AF_afr","AF_amr","AF_asj","AF_eas","AF_fin","AF_nfe","AF_oth"))
fp_scores_hg19 <- as.data.frame(fp_scores_hg19)
rp_range_hg19 <- GRanges(seqnames=temp_chr, IRanges(start=rp_start_hg19:amp_end_hg19, width=1))
rp_scores_hg19 <- gscores(mafdb_hg19,rp_range_hg19,pop=c("AF_afr","AF_amr","AF_asj","AF_eas","AF_fin","AF_nfe","AF_oth"))
rp_scores_hg19 <- as.data.frame(rp_scores_hg19)
primers_hg19 <- bind_rows(primers_hg19,setNames(data.frame(primer_id <- paste0(row$ID,"_F"),
seq <- temp_FP,
chr <- fp_scores_hg19$seqnames,
hg19_pos <- fp_scores_hg19$start,
AF_afr <- fp_scores_hg19$AF_afr,
AF_amr <- fp_scores_hg19$AF_amr,
AF_asj <- fp_scores_hg19$AF_asj,
AF_eas <- fp_scores_hg19$AF_eas,
AF_fin <- fp_scores_hg19$AF_fin,
AF_nfe<- fp_scores_hg19$AF_nfe,
AF_oth<- fp_scores_hg19$AF_oth,
stringsAsFactors=FALSE),
c("primer_id", "seq", "chr","hg19_pos","AF_afr","AF_amr","AF_asj","AF_eas","AF_fin","AF_nfe","AF_oth")))
primers_hg19 <- bind_rows(primers_hg19,setNames(data.frame(primer_id <- paste0(row$ID,"_R"),
seq <- temp_RP,
chr <- rp_scores_hg19$seqnames,
hg19_pos <- rp_scores_hg19$start,
AF_afr <- rp_scores_hg19$AF_afr,
AF_amr <- rp_scores_hg19$AF_amr,
AF_asj <- rp_scores_hg19$AF_asj,
AF_eas <- rp_scores_hg19$AF_eas,
AF_fin <- rp_scores_hg19$AF_fin,
AF_nfe<- rp_scores_hg19$AF_nfe,
AF_oth<- rp_scores_hg19$AF_oth,
stringsAsFactors=FALSE),
c("primer_id", "seq", "chr","hg19_pos","AF_afr","AF_amr","AF_asj","AF_eas","AF_fin","AF_nfe","AF_oth")))
}
return(list(as.data.frame(primers_hg38), as.data.frame(primers_hg19)))
})
output$hg38 <- DT::renderDT(my_data()$primers_hg38)
#, options = list(paging = F, info = F, search = list(),
# dom = "Brtip", fixedColumns = T, fixedHeader = T,
# buttons = c("copy", "excel")),
# fillContainer = TRUE)
output$hg19 <- DT::renderDT(my_data()$primers_hg19, options = list(paging = F, info = F, search = list(),
dom = "Brtip", fixedColumns = T, fixedHeader = T,
buttons = c("copy", "excel")),
fillContainer = TRUE)
}
shinyApp(ui, server)
Please consider posting a MRE in the future. If you access the data as my_data()[[1]] it should work. However, if you define a named list, your method works. Take a look at an MRE below.
library(DT)
ui <- fluidPage(
DTOutput("t1"), DTOutput("t2")
)
server <- function(input, output) {
myd <- reactive(list(iris,mtcars))
myd2 <- reactive(list(ab=rock,cd=pressure))
output$t1 <- renderDT(head(myd()[[2]])) ### works
# output$t1 <- renderDT(myd()$mtcars) ### does not work
output$t2 <- renderDT(head(myd2()$ab)) ### works as a named list
}
shinyApp(ui = ui, server = server)

How to provide metadata values from server to ui method in shiny

I'm very new to shiny and butting against something there must be a pattern for, but have had no luck Googling.
I have a simple shiny file (app.R below) that returns a plot for n observations. In addition to making the plot available to the ui method, I'd like to pass it n to help it size the plot to my liking
library(shiny)
server <- function(input, output) {
output$distPlot <- renderPlot({
firstNames <- c("Bob", "Jane", "Bob")
lastNames <- c("Builder", "Gorillas", "Weave")
ages <- c(25, 26, 27)
df <- data.frame(firstNames, lastNames, ages)
# I want to pass this to `ui`
numFirstNames <- length(unique(df$firstNames))
mapping <- aes(x = firstNames, y = ages)
ggplot(df, mapping) + geom_violin() + coord_flip()
})
}
getHeightInPx <- function(numFirstNames) {
paste(100 * numFirstNames, "px", sep="")
}
ui <- fluidPage(
# I want height to be a function of numFirstNames as calculated in the server definition
mainPanel(plotOutput("distPlot", height = getHeightInPx(2)))
)
shinyApp(ui, server)
Since ui isn't a function and doesn't have direct access to output (to me it's getting to distPlot by framework magic) how can I get to data prepared in server to help layout the page?
Thanks
https://github.com/rstudio/shiny/issues/650 tipped me off that you could use the height parameter in renderPlot. So I combined that with some use of reactive and observe to call a function that returns the plot AND gives me the number of elements to use.
I hardly get reactive and observe, so while it seems to work I would not be surprised to hear that I am abusing them or that there's an easier way.
library(shiny)
renderDistPlot <- function(input) {
firstNames <- c("Bob", "Jane", "Bob", "Carol")
lastNames <- c("Builder", "Gorillas", "Weave", "Xmasing")
ages <- c(25, 26, 27, 23)
df <- data.frame(firstNames, lastNames, ages)
# I want to pass this to `ui`
numFirstNames <- length(unique(df$firstNames))
mapping <- aes(x = firstNames, y = ages)
plot <- ggplot(df, mapping) + geom_violin() + coord_flip()
list(Plot = plot, NumFirstNames = numFirstNames)
}
server <- function(input, output) {
renderDistPloatResult <- reactive(renderDistPlot(input))
observe(output$distPlot <- renderPlot(renderDistPloatResult()$Plot, height = renderDistPloatResult()$NumFirstNames * 100))
}
ui <- fluidPage(
# I want height to be a function of numFirstNames as calculated in the server definition
mainPanel(plotOutput("distPlot"))
)
shinyApp(ui, server)

interactive correlation heatmap in shiny

I want to reproduce the example at: https://scip.shinyapps.io/scip_app/
Basically, I have a 300 by 300 adjusted correlation matrix and a 300 by 300 unadjusted correlation matrix and want to show them interactively with zoom in and zoom out function. The text descriptions should display the point estimates and confidence intervals.
Is there any template I can quickly refer to?
Building on the data from Mike, you can use the d3heatmap library
library(d3heatmap)
library(shiny)
n1 <- 100
n2 <- 100
nr <- 30
nc <- 30
set.seed(1)
x <- matrix(rnorm(n1), nrow=nr, ncol=nc)
y <- matrix(rnorm(n2), nrow=nr, ncol=nc)
MAT <- cor(x,y)
ui <- fluidPage(
mainPanel(
d3heatmapOutput("heatmap", width = "100%", height="600px")
)
)
## server.R
server <- function(input, output) {
output$heatmap <- renderD3heatmap({d3heatmap(MAT)})
}
shinyApp(ui = ui, server = server)
Edit: Specify the colours if needs to be and display the data as is, note that Colv = T by default, which means it will group the correlated items together
output$heatmap <- renderD3heatmap({d3heatmap(MAT, colors = "Blues", Colv = FALSE)})
I think plotly can do this well. Here are the docs https://plot.ly/r/heatmaps/:
And here is a little template-example (returning Porkchop's favor by borrowing his minimal shiny template) with some fake data:
library(shiny)
n1 <- 100
n2 <- 100
nr <- 30
nc <- 30
set.seed(1)
x <- matrix(rnorm(n1), nrow=nr, ncol=nc)
y <- matrix(rnorm(n2), nrow=nr, ncol=nc)
cmat <- cor(x,y)
plot_ly(z = cmat, type = "heatmap")
ui <- fluidPage(
mainPanel(
plotlyOutput("heatmap", width = "100%", height="600px")
)
)
## server.R
server <- function(input, output) {
output$heatmap <- renderPlotly({plot_ly(z = cmat, type = "heatmap")})
}
shinyApp(ui,server)
Here is the Shiny output. Note it is fully zoomable:

valuebox missing true/false statement

I am attempting to have two if else statements in a renderValuebox in shiny R. Here's the code:
observe({
a <- which(blkname==input$e0)
start <- input$dateRange[1]
end <- input$dateRange[2]
confInt <- input$CI
inc <- input$increment
output$Name <- renderText(paste0("Overview of ",input$e0," block"))
start1 <- as.character(format(input$dateRange[1], format="%B %d %Y"))
end1 <- as.character(format(input$dateRange[2], format="%B %d %Y"))
output$Head <- renderText(paste0("Distribution of dry matter across ", input$e0, " between ",start1, " and ", end1))
pts$DryMatter <- pts$DryMatter+(pts$Amend*inc)
pts$DryMatter <- round(pts$DryMatter, digits=2)
grap <- pts[which(pts$Date>start&pts$Date<end),]
grap <- grap[which(grap$paddock==a),]
grap$DryMatter<- round(grap$DryMatter, digits=2)
yup <- round(mean(grap$DryMatter), digits=2)
nump <- nrow(grap)
grapdevdm <- grap[which(grap$Amend==min(grap$Amend)),]
grapdev <- sd(grapdevdm$DryMatter)
grapsampnum <- nrow(grapdevdm)
grapdevmean <- mean(grapdevdm$DryMatter)
cv <- (grapdev/grapdevmean)*100
sampReq <-(1.7*1.7)*(cv*cv)/((100-confInt)^2)
~
output$treenum <- renderValueBox({
valueBox(
paste0(nrow(grap), "Trees sampled out of ", sampReq, "required."),
if(nrow(grap)>=sampReq){
icon = icon("thumbs-up", lib = "glyphicon")}else
{icon = icon("thumbs-down", lib = "glyphicon")},
if(nrow(grap)>=sampReq){ color = "green"} else {colour = "red"}
)
})
I am receiving Warning: Error in if: missing value where TRUE/FALSE needed
However both if else is completed, maybe I am missing something.
Cheers,