Adding captions to multiple images in a shiny app - r

In shiny you can add images from disk using the line below. I also found that a tag called figcaption or caption can be used to add captions to the images. But, unfortunately, I couldn't find an example on how to structure figcaption. How can I add captions right under multiple images in a shiny app? The code below stacks the captions one under the other instead of placing them under each figure respectively.
ui = navbarPage("Project Eddy", theme = shinytheme("sandstone"),
div((img(src = "Study_Area.png", height = '640px', width = '480px'),img(src = "Picture1.png", height = '640px', width = '480px'),img(src = "Picture2.png", height = '640px', width = '480px')),
div(tags$figcaption("Figure 1: Ed), tags$figcaption("Figure 2: Edd), tags$figcaption("Figure 3: Eddy")),

This turned out to be trickier than I expected. Looking at the spec for the <figcaption> HTML element, it needs to be the first or last element of the parent <figure> tag. Shiny's renderPlot() function doesn't wrap its image in a <figure> tag. Maybe there's a way of coercing renderPlot() to do this, but I couldn't find it. I resorted to using renderUI() and manually constructing the necessary nested tags.
Here's a working example:
library(shiny)
library(tidyverse)
ui <- fluidPage(
uiOutput("all")
)
server <- function(input, output, session) {
d1 <- tibble(x=runif(10), y=runif(10))
d2 <- tibble(x=runif(10), y=runif(10))
output$all <- renderUI({
tagList(
uiOutput("plot1"),
uiOutput("plot2")
)
})
output$plot1 <- renderUI({
tags$figure(
htmltools::plotTag(
d2 %>% ggplot() + geom_point(aes(x=x, y=y)),
alt="Plot 1"
),
tags$figcaption("This is plot 1"))
})
output$plot2 <- renderUI({
tags$figure(
htmltools::plotTag(
d2 %>% ggplot() + geom_point(aes(x=x, y=y)),
alt="Plot 2"
),
tags$figcaption("This is plot 2"))
})
}
shinyApp(ui = ui, server = server)

Related

How do I draw a circle using JavaScript in R Shiny?

I am looking to draw simple shapes in R Shiny. (The goal is to draw a static legend using HTML instead of loading a png.)
I can't get the canvas tag to work. It simply does not draw anything.
library(shiny)
ui <- fluidPage(
tags$script(HTML(
"function draw_legend() {",
"var canvas = document.getElementById('simple_legend');",
"const canvas = document.getElementById('canvas');",
"const ctx = canvas.getContext('2d');",
"ctx.fillStyle = 'green';",
"ctx.fillRect(10, 10, 150, 100);",
"}"
)),
sidebarLayout(
sidebarPanel(
),
mainPanel(
tags$body(onload="draw_legend();"),
tags$canvas(id="simple_legend", height = "30"),
tags$div("Some text")
)
)
)
server <- function(input, output, session) {
}
shinyApp(ui = ui, server = server)
Expanding on my comment, here is an app showing two techniques for drawing a circle in Shiny, one using CSS, the other using a data frame. You can make obvious adjustments to size, colour, position etc to get the effect you want.
library(shiny)
library(tidyverse)
ui <- fluidPage(
tags$head(
tags$style("#circleText {color: red; font-size: 20px; }")
),
uiOutput("circleText"),
plotOutput("circleGraph")
)
server <- function(input, output, session) {
output$circleText <- renderUI({
HTML("⬤")
})
output$circleGraph <- renderPlot({
tibble(theta=seq(0, 2*pi, 0.025), x=sin(theta), y=cos(theta)) %>%
ggplot(aes(x, y)) +
geom_path() +
coord_fixed() +
theme_void()
},
height=75,
width=75)
}
shinyApp(ui = ui, server = server)
Giving
You have to
remove the line const canvas = document.getElementById('canvas');
set a width to the canvas element, not only a height

How to arrange or align the output of renderUI

I am learning how to use renderUI to dynamically generate multiple plots. Here is an example app I designed (https://yuchenw.shinyapps.io/Format_UI_Example/). The idea is to design an app that allows users to select one or more parameters in the mtcars data set and plot the row index and the value as a scatter plot dynamically.
The example app works, but all the plots are aligned in one column. As the users selected more parameters, the number of plots increases, and the length of the web page also increases. In addition, there are lots of white space. If possible, I would like to arrange or align the multiple plots as a two columns or three columns structure to reduce the length of the web page and to reduce the white space.
I usually used the column function and set the width argument to achieve this. But I don't how to do it using renderUI. I would appreciate any help.
Here is the code.
### This script creates an R shiny app that plot mpg, disp, and hp, from the mtcars data set
# Load packages
library(shiny)
library(tidyverse)
# Load data
data("mtcars")
# Add row id
mtcars2 <- mtcars %>% mutate(ID = 1:n())
# ui
ui <- fluidPage(
sidebarPanel(
selectInput(inputId = "sel", label = "Select one or more parameters",
choices = names(mtcars), multiple = TRUE)
),
mainPanel(
uiOutput("plots")
)
)
# server
server <- function(input, output, session){
# Create plot tag list
output$plots <- renderUI({
plot_output_list <- lapply(input$sel, function(par) {
plotname <- paste("plot", par, sep = "_")
plotOutput(plotname)
})
do.call(tagList, plot_output_list)
})
# Dynamically generate the plots based on the selected parameters
observe({
req(input$sel)
lapply(input$sel, function(par){
output[[paste("plot", par, sep = "_")]] <- renderPlot({
ggplot(mtcars2, aes_string(x = "ID", y = par)) +
geom_point() +
ggtitle(paste("Plot: ", par))
},
width = 250,
height = 250)
})
})
}
# Run app
shinyApp(ui, server)
Try this :
plotOutput(plotname, height = '250px', inline=TRUE)
It will give you the following output:

includeHTML conflicting with renderPlotly

I'm creating a small app with shiny to show simulation results based on user input with plot_ly() (need plotly for animation). It utilizes navbarpage() to show a home page (where I explain the rationale) and a simulation page (where the app is actually displayed).
To create the homepage, I created a .Rmd file and knitted to html. Unfortunately, it appears that includeHTML() and renderPlotly() have some sort of javascript conflict and so plotly will not render. Double unfortunately, I know almost nothing about HTML or javascript.
A simple (almost reprex) version:
# Define UI for application that draws a histogram
ui <- fluidPage(
navbarPage("RCV", position = "fixed-top", collapsible = TRUE,
tabPanel("Home",
includeHTML("www/yourFav.html")),
tabPanel("Simulation",
plotlyOutput("plot")
)
)
# Define server logic required to draw a histogram
server <- function(input, output, session){
output$plot <- renderPlotly({
plot_ly(data = cars,
x = ~mgp,
y = ~wt)
})
}
Any suggestions you have will be well received!
Best,
Brennan
includeHTML is intended to be used for HTML fragments. Use an iframe for a full HTML page. The HTML file must be in the www subfolder, and you have to pass it to the src argument of tags$iframe without the www/ prefix.
library(shiny)
library(plotly)
ui <- fluidPage(
navbarPage("RCV", position = "fixed-top", collapsible = TRUE,
tabPanel("Home",
tags$iframe(src = "rcv_homePage.html",
width = "600", height = "500",
style = "margin-top: 70px;")),
tabPanel("Simulation",
plotlyOutput("plot")
)
)
)
server <- function(input, output, session){
output$plot <- renderPlotly({
plot_ly(data = cars,
x = ~mgp,
y = ~wt)
})
}
shinyApp(ui, server)

Shiny- Tooltip for each check-able box (basic)

I have a check-able list of ~60 different inputs, and I was hoping to set a tooltip for each individual box. Currently, using bsTooltip, I can only set a tooltip for the entire panel. Here is the relevant script...
library(shiny)
library(shinyBS)
library(networkD3)
ui <- fluidPage(
tabPanel("Analyze By Experiment",
sidebarLayout(
sidebarPanel(
width = 2,
fluid = FALSE,
bsTooltip("data1", "PDRF = Parkinsons Disease Risk Factors", placement = "right", trigger = "hover"),
checkboxGroupInput ( inputId = "data1", label = "High Throughput Experiment",
choices = unique(unlist(data$Present_In)))),
mainPanel(simpleNetworkOutput("coolplot", height = "800px"),
width = 10))
)
)
server <- function(input, output, session) { }
shinyApp(ui, server)
Building on the brilliant answer by #K. Rohde from this answer, we can do the same for your example. The issue you have is that you have 60 checkboxinputs, so writing out the tipify and bsButtonright for each one becomes tedious and longwinded. However, since if you notice from the two calls to tipify in the second part of his answer, only the id and help text changes, the rest stays the same. So we can write a function that takes id and help text and produces html code for a help info using that code. Then we can use lapply to create 60 or even more of these items, by simply passing a list of ids and help text to with our function to lapply. I've used the euStockMarkets dataset for this. It has 1720 unique rows, which with your code will give 1720 checkbox inputs. This is of course ridiculous but it demonstrates that the code works and hence will likely work on much fewer checkboxes
I've generated the help text using R but you'll probably type yours out.
Here is the full code below:
library(shiny)
library(shinyBS)
library(networkD3)
extendedCheckboxGroup <- function(..., extensions = list()) {
cbg <- checkboxGroupInput(...)
nExtensions <- length(extensions)
nChoices <- length(cbg$children[[2]]$children[[1]])
if (nExtensions > 0 && nChoices > 0) {
lapply(1:min(nExtensions, nChoices), function(i) {
# For each Extension, add the element as a child (to one of the checkboxes)
cbg$children[[2]]$children[[1]][[i]]$children[[2]] <<- extensions[[i]]
})
}
cbg
}
bsButtonRight <- function(...) {
btn <- bsButton(...)
# Directly inject the style into the shiny element.
btn$attribs$style <- "float: right;"
btn
}
data("EuStockMarkets")
eustocks <- as.data.frame(EuStockMarkets)
choiceNames <- paste0("cb", 1:length(unique(unlist(eustocks$FTSE))))
txt <- paste(rep("Help", length(unique(unlist(eustocks$FTSE)))), seq(1:length(unique(unlist(eustocks$FTSE)))))
txt[1] <- "PDRF = Parkinsons Disease Risk Factors"
ids <- paste0("pB", rep(1:length(unique(unlist(eustocks$FTSE)))))
inputData <- data.frame(cbid = ids, helpInfoText = txt)
inputData$cbid <- sapply(inputData$cbid, as.character)
inputData$helpInfoText <- sapply(inputData$helpInfoText, as.character)
checkBoxHelpList <- function(id, Text){
extensionsList <- tipify(bsButtonRight(id, "?", style = "inverse", size = "extra-small"), Text)
return(extensionsList)
}
# checkBoxHelpList(id = x["cbid"], Text = x["helpInfoText"])
helpList <- split(inputData, f = rownames(inputData))
checkboxExtensions <- lapply(helpList, function(x) checkBoxHelpList(x[1], as.character(x[2])))
server <- function(input, output, session) {
output$rendered <- renderUI({
extendedCheckboxGroup("qualdim", label = "High Throughput Experiment", choiceNames = choiceNames, choiceValues = unique(unlist(eustocks$FTSE)), selected = c("check2"),
extensions = checkboxExtensions)
})
}
ui <- fluidPage(
tabPanel("Analyze By Experiment",
sidebarLayout(
sidebarPanel(
width = 2,
fluid = FALSE,
uiOutput("rendered")),
mainPanel(simpleNetworkOutput("coolplot", height = "800px"),
width = 10))
)
)
shinyApp(ui, server)
As you can see below, the tool tip works.

Can a box status value (color) be reactive and conditional in Shinydashboard?

I have a Shinydashboard with reactive Dygraph boxes. I successfully setup a reactive box title to display the maximum value in the dataset and I'd like to do the same for the Status option. Here's what I've got so far:
ui <- dashboardPage(
dashboardHeader(title = "Sites", disable = TRUE),
dashboardSidebar(
#collapsed = TRUE,
disable = TRUE,
sidebarMenu()
),
dashboardBody(
fluidRow(
box(title = textOutput('dyermax'), background = "black", status = textOutput('dyerStat'), dygraphOutput("plot1", height = 173))
)
)
)
The title works as expected but the status gives an error: status can only be "primary", "success", "info", "warning", or "danger".
server <- function(input, output, session) {
#reactivePoll code for importing CSV data (datap)
renderTable(datap())
#Plot1
output$plot1 <- renderDygraph({
dyersburgp <- xts(x = datap()$dyersburg, order.by = datap()$date)
dyersburgf <- xts(x = datap()$dyersburg.1, order.by = datap()$date)
dyersburgmain <- cbind(dyersburgf, dyersburgp)
output$dyermax <- renderPrint({
cat("Dyersburg (max:", max(dyersburgp, na.rm = TRUE),"ug/m3)")
})
dyersburgMx <- max(dyersburgp, na.rm = TRUE)
output$dyerStat <- renderPrint({
if(dyersburgMx >60)("danger" else "info")
})
dygraph(dyersburgmain)
})
}
shinyApp(ui, server)
I would prefer to use the Color option instead of the Status option, but adding "color = "red"" to the box doesn't change the color at all for some reason.
Background
This is actually a really good question. To my understanding, the reason textOutput doesn't work is that, by default, text is rendered within an HTML div. So instead of just passing the raw string ('danger', 'info', etc.), it is rendered as raw HTML. For example, if we inspect the textOutput element in our browser when we run the following,
output$my_text <- renderText({
'this is some text'
})
textOutput('my_text')
we can see it actually renders the below HTML, rather than just "this is some text".
<div id="my_text" class="shiny-text-output shiny-bound-output">this is some text</div>
Obviously this is for a very good reason, and enables us to make good-looking Shiny apps without having to worry about any HTML. But it means we have to be careful when passing outputs as arguments to UI functions.
Solution
There may be better ways to do this, but one way would be creating the HTML yourself by using renderUI/uiOutput, and using the HTML function in combination with paste0 to dynamically render out HTML string to be read directly by uiOutput (which is an alias for the more descriptive htmlOutput). This example changes the status of the box when the user changes the numericInput to above 60, and allows the user to change the title of the box as well. Extend this as required for your own project.
library(shiny)
library(shinydashboard)
body <-
dashboardBody(
fluidRow(
numericInput(
inputId = 'status_input',
label = 'numeric input',
value = 50),
textInput(
inputId = 'box_title',
label = 'box title',
value = ''),
uiOutput('my_box')
)
)
server <- function(input, output, session) {
# get box status as string representing html element
box_status <- reactive({
if (input$status_input > 60) {
'box-danger'
} else {
'box-info'
}
})
# get user input for box title
box_title <- reactive({
input$box_title
})
# generate html to display reactive box
output$my_box <- renderUI({
status <- box_status()
title <- box_title()
# generate the dynamic HTML string
HTML(paste0("
'
<div class=\"box box-solid ", status, "\">
<div class=\"box-header\">
<h3 class=\"box-title\">", title, "</h3>
</div>
<div class=\"box-body\">
Box content!
</div>
</div>
'"
))
})
}
shinyApp(ui = dashboardPage(dashboardHeader(), dashboardSidebar(),body), server)

Resources