Plotting user selected images in Shiny - r

This is my first attempt at creating a Shiny app so I wanted to do something very simple: use fileInput so the user can select an image on their computer and then renderImage to plot the image.
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("plot image"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
fileInput("image",
"Select your image:", placeholder = "No file selected")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("photo")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$photo <- renderImage({
img <- load.image(input$image)
plot(img)
})
}
# Run the application
shinyApp(ui = ui, server = server)
However, when I try to load an image with this I get an error: invalid filename argument.
Ultimately I would like to integrate selectInput the choices option would have a few default images as well as those the user uploaded with fileInput but I feel that I'm already getting ahead of myself on that.
UPDATE 1:
library(shiny)
library(ggplot2)
library(imager)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("plot images"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
fileInput("image",
"Select your image:", placeholder = "No file selected")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("photo")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$photo <- renderPlot({
# Ensure the values for 'image' are available
# If not, the operation is stopped by raising a "silent" exception
req(input$image)
# Get edges of image with imager::cannyEdges
img <- cannyEdges(input$image)
# img is now a logical array with 4 dimensions but we only want 2 - discard two of the dimensions
img <- img[, , 1, 1]
# Convert the matrix into a list of coordinates
coords <- which(img, arr.ind = T)
# Turn the coordinates into a dataframe
df <- data.frame(x = coords[, 1], y = coords[, 2])
# Plot the coordinates
ggplot(df, aes(x, -y)) +
geom_point()
})
}
# Run the application
shinyApp(ui = ui, server = server)

See ?fileInput. If "image" is the id, then the path to the uploaded file is input$image$datapath, not input$image.
So you can do something like:
output$photo <- renderImage({
req(input$image)
list(src = input$image$datapath, alt="alternative text")
})
and in ui,
imageOutput("photo")

I encountered the same issue and solved. the image can't be loaded is because the input$image doesn't just give the filename you want, but also give more information. That make it a named list rather than a single value. Maybe you can try as following in your first case.
server <- function(input, output) {
output$photo <- renderImage({
img <- load.image(input$image[[4]])
plot(img)
})
}
Hope it works.

Related

How to overcome width parameter in shinyDashboard valueBox being ignored when wrapped in renderUI and htmlOutput

I am wanting to create shinyDashboard valueBoxes. The number of these is variable depending on select inputs, so I have followed this example to render n valueBoxes using renderUI, htmlOutput and tagList. However when I do this the width parameter in valueBox seems to be ignored and the valueBoxes take up the remaining width of the page and are stacked vertically. If rendered manually within the UI they are the correct width and sit on a single fluid row.
Here is an example. Here the number of valueBoxes is hard coded as 3 but in reality this would be determined by dyanmic data:
library(shiny)
library(shinyWidgets)
library(shinydashboard)
# Define UI for application that draws a histogram
ui <- fluidPage(
useShinydashboard(),
# Application title
titlePanel(title = 'Title'),
sidebarLayout(
sidebarPanel(width = "3",
selectInput(inputId = 'pick', 'Pick a letter',
choices = c('A','B','C'))
),
mainPanel(
uiOutput("stats")
)
)
)
server <- function(input, output, session) {
#how many statistics to show - in reality this will be dynamic
nstats <- 3
stats <- c(-10,0,10)
#dynamically create the right number of htmlOutputs
#https://stackoverflow.com/questions/31686773/shiny-dynamic-number-of-output-elements-plots
output$stats <- renderUI({
vboxes <- lapply(1:nstats, function(i) {
outputId <- paste0("valuebox_", i)
htmlOutput(outputId)
})
tagList(vboxes)
})
#now populate these with valueboxes containing the ith statistic
for(i in 1:nstats){
local({
#my_i <- i
outputId <- paste0("valuebox_", i)
temp <- stats[i]
output[[outputId]] <- renderValueBox({
valueBox(
subtitle = 'Change measure',
width = 2,
value = paste0(temp,"%"),
icon = icon('chart-line')
)
})
})
}
}
# Run the application
shinyApp(ui = ui, server = server)
This generates the following, with the valueBoxes taking up all 9 columns, rather than 2 each.
I have tried inline=TRUE in htmlOutput but this makes no difference. How can I make the width=2 be respected or where do I need to move this to? Thanks
Change htmlOutput to valueBoxOutput:
output$stats <- renderUI({
vboxes <- lapply(1:nstats, function(i) {
outputId <- paste0("valuebox_", i)
valueBoxOutput(outputId)
})
tagList(vboxes)
})
As an aside, I think this is a classic example of where using modules would be beneficial. It would mean you wouldn't need to track the valueBox IDs in the server or UI, and could delegate all the manipulation of the data in the value box to the module. This will make your main ui and server functions shorter and cleaner.

how to print sampled output from actionButton on multiple lines in R Shiny

I would like to have an action button in R Shiny that samples three elements of a character variable and returns each on its own line. I have seen that htmltools can be used to break the text of the action button itself onto new lines, but I don't see an obvious way to pass such commands into the output of the button, especially when using the sample() function.
In the toy shiny app below, the actionButton prints three greek letters on one line,
alpha beta delta
I would like each sampled element to appear on its own line,
alpha
beta
delta
Below is the toy app
library(shiny)
# Define UI ----
ui <- fluidPage(
titlePanel("Toy"),
# Copy the line below to make an action button
actionButton("greek", label = "Greek letters"),
verbatimTextOutput("text")
)
# Define server logic ----
server <- function(input, output, session) {
observeEvent(input$greek, {
greek <- c("alpha","beta","gamma","delta")
})
observeEvent(input$greek,{
output$text <- renderText(sample(greek,3))
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Here, try this:
library(shiny)
library(glue)
# Define UI ----
ui <- fluidPage(
titlePanel("Toy"),
# Copy the line below to make an action button
actionButton("greek", label = "Greek letters"),
verbatimTextOutput("text")
)
# Define server logic ----
server <- function(input, output, session) {
greeks <- eventReactive(input$greek, {
sample(c("alpha", "beta", "gamma", "delta"), size = 3)
})
output$text <- renderText(
sample(greeks(), 3) |> glue_collapse(sep = "\n")
)
}
# Run the app ----
shinyApp(ui = ui, server = server)

Click Interactive Plot in R Shiny

I'm trying to create a plot with a bunch of boxes and then when a box gets clicked on it gets colored in up. I'm having two issues with this. 1. I can't figure out a way for the figure to update dynamically when I click. 2. I can't figure out how to store the values that come out of the click input variable so that I have stored all previous clicks and would be able to color in multiple boxes. You can see a few ways I've tried to solve and test either of the two issues and I'm not having any luck. Any help with either issue would be appreciated.
ui <- fluidPage(
# Application title
titlePanel("Boxes"),
sidebarLayout(
sidebarPanel(
textOutput("text")),
# Get it it's a pun
mainPanel(
plotOutput("boxPlot",click = "test")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
vals <- reactiveValues(x=NA,y=NA,test=NA)
observeEvent(input$click, {
vals$x <- c(vals$x,input$test$x)
vals$y <- c(vals$y,input$test$y)
vals$test <- input$test$x
})
output$boxPlot <- renderPlot({
par(mai=c(0,0,0,0))
plot(1,ylim=c(2,15),xlim=c(2,15),type='n',yaxs='i',xaxs='i',ylab='',xlab='',axes=F)
for (i in 2:15) {
abline(v=i)
abline(h=i)
}
observeEvent(input$click, { rect(floor(input$test$x),floor(input$test$y),ceiling(input$test$x),ceiling(input$test$y),col='blue')})
# if (length(vals$x) > 0) {
# rect(floor(vals$x[1]),floor(vals$y[1]),ceiling(vals$x[1]),ceiling(vals$y[1]),col='blue')
# }
})
# output$text <- renderText(vals$x[length(vals$x)])
output$text <- renderText(vals$test)
}
# Run the application
shinyApp(ui = ui, server = server)
This might be a simple solution.
You should only have one single observeEvent for your click event. In that observeEvent, store your x and y values as reactiveValues as you current are doing.
Then, your renderPlot should plot the grid lines and filled in rectangles based on your reactiveValues. By including input$boxPlot_click (as I called it) in renderPlot the plot will be redrawn each click.
library(shiny)
ui <- fluidPage(
titlePanel("Boxes"),
sidebarLayout(
sidebarPanel(
textOutput("text")),
mainPanel(
plotOutput("boxPlot", click = "boxPlot_click")
)
)
)
server <- function(input, output) {
vals <- reactiveValues(x=NA,y=NA)
observeEvent(input$boxPlot_click, {
vals$x <- c(vals$x,input$boxPlot_click$x)
vals$y <- c(vals$y,input$boxPlot_click$y)
})
output$boxPlot <- renderPlot({
input$boxPlot_click
par(mai=c(0,0,0,0))
plot(1,ylim=c(2,15),xlim=c(2,15),type='n',yaxs='i',xaxs='i',ylab='',xlab='',axes=F)
for (i in 2:15) {
abline(v=i)
abline(h=i)
}
for (i in seq_along(length(vals$x))) {
rect(floor(vals$x),floor(vals$y),ceiling(vals$x),ceiling(vals$y),col='blue')
}
})
output$text <- renderText(paste0(vals$x, ', ' , vals$y, '\n'))
}
shinyApp(ui = ui, server = server)

r shiny add element dynamically to reactive value

I'm trying to learn how to use shiny modules to simplify a messy shiny app I have. The app currently reads in several data sets using a list of names like this:
dataSetsToLoad <- c("set1name", "set2name", "etc")
for (i in 1:length(dataSetsToLoad) {
dt <- readRDS(paste0(dataSetsToLoad[i], ".RDS")
assign(dataSetsToLoad[i], dt)
}
These end up in the global environment and are accessible to all my non-modularized code.
Following a code pattern from here, I'd like to modify the above to something like the following
stash = reactiveValues()
for (i in 1:length(dataSetsToLoad) {
stashVar <- paste0("stash$", dataSetsToLoad[i])
dt <- readRDS(paste0(dataSetsToLoad[i], ".RDS")
assign(stashVar, dt)
}
The summary question is how do I put the dt into the stash reactive with the dynamically created name in stashVar. A second question is whether there is any way to test this without actually running it in a shiny app.
You can do something like this. Store the dataframes in a list and then assign them in a loop to the reactiveValues().
dflist <- list(mtcars, airquality, mtcars)
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 3,
value = 3)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
values <- reactiveValues()
for(i in 1:length(dflist)) {
values[[paste0("df_", i)]] <- dflist[[i]]
}
observeEvent(input$bins, {
print(values$df_1)
print(values$df_2)
print(values$df_3)
})
}
# Run the application
shinyApp(ui = ui, server = server)

conditionalPanel in R/shiny

Quick question on conditionalPanel for shiny/R.
Using a slightly modified code example from RStudio, consider the following simple shiny app:
n <- 200
# Define the UI
ui <- bootstrapPage(
numericInput('n', 'Number of obs', n),
conditionalPanel(condition = "input.n > 20",
plotOutput('plot') ),
HTML("Bottom")
)
# Define the server code
server <- function(input, output) {
output$plot <- renderPlot({
if (input$n > 50) hist(runif(input$n)) else return(NULL)
})
}
# Return a Shiny app object
shinyApp(ui = ui, server = server)
My objective is to hide the graph and move up the HTML text to avoid a gap. Now, you can see that if the entered value is below 20, the graph is hidden and the text "Bottom" is moved up accordingly. However, if the entered value is larger than 20, but smaller than 50, the chart function returns NULL, and while no chart is shown, the text "Bottom" is not moving up.
Question is: is there a way I can set a conditionalPanel such that it appears/is hidden based on whether or not a plot function returns NULL? The reason I'm asking is because the trigger a bit complex (among other things it depends on the selection of input files, and thus needs to change if a different file is loaded), and I'd like to avoid having to code it on the ui.R file.
Any suggestions welcome,
Philipp
Hi you can create a condition for conditionalPanel in the server like this :
n <- 200
library("shiny")
# Define the UI
ui <- bootstrapPage(
numericInput('n', 'Number of obs', n),
conditionalPanel(condition = "output.cond == true", # here use the condition defined in the server
plotOutput('plot') ),
HTML("Bottom")
)
# Define the server code
server <- function(input, output, session) {
output$plot <- renderPlot({
if (input$n > 50) hist(runif(input$n)) else return(NULL)
})
# create a condition you use in the ui
output$cond <- reactive({
input$n > 50
})
outputOptions(output, "cond", suspendWhenHidden = FALSE)
}
# Return a Shiny app object
shinyApp(ui = ui, server = server)
Don't forget to add the session in your server function and the outputOptions call somewhere in that function.

Resources