I don't get renderImage()to work with a .svg file.
My minimal example (adapted from the corresponding RStudio-tutorial):
ui.R
shinyUI(pageWithSidebar(
headerPanel("renderSVG example"),
sidebarPanel(
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500),
actionButton("savePlot", "savePlot")
),
mainPanel(
# Use imageOutput to place the image on the page
imageOutput("plot"),
imageOutput("plot_as_svg")
)
))
server.R
require("ggplot2")
library(svglite)
shinyServer(function(input, output, session) {
## create plot with renderPlot()
output$plot<- renderPlot({
hist(rnorm(input$obs), main="Generated in renderPlot()")
})
## create .svg file of the plot and render it with renderImage()
output$plot_as_svg <- renderImage({
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.svg')
# Generate the svg
svglite(outfile, width=400, height=300)
hist(rnorm(input$obs), main="Generated in renderImage()")
dev.off()
# Return a list containing the filename
list(src = outfile,
contentType = 'text/svg+xml',
width = 400,
height = 300,
alt = "This is alternate text")
}, deleteFile = TRUE)
})
Any ideas where the problem is?
I struggled with this same issue, and there is nothing out on the web that I have found to address this problem which is frustrating. Here is the solution that worked for me:
ui.R:
shinyUI(pageWithSidebar(
headerPanel("renderSVG example"),
sidebarPanel(
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500),
actionButton("savePlot", "savePlot")
),
mainPanel(
# Use imageOutput to place the image on the page
imageOutput("plot"),
imageOutput("plot_as_svg")
)
))
server.R:
require("ggplot2")
shinyServer(function(input, output, session) {
## create plot with renderPlot()
output$plot<- renderPlot({
hist(rnorm(input$obs), main="Generated in renderPlot()")
})
## create .svg file of the plot and render it with renderImage()
output$plot_as_svg <- renderImage({
width <- session$clientData$output_plot_width
height <- session$clientData$output_plot_height
mysvgwidth <- width/96
mysvgheight <- height/96
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.svg')
# Generate the svg
svg(outfile, width=mysvgwidth, height=mysvgheight)
hist(rnorm(input$obs), main="Generated in renderImage()")
dev.off()
# Return a list containing the filename
list(src = normalizePath(outfile),
contentType = 'image/svg+xml',
width = width,
height = height,
alt = "My Histogram")
})
})
Notice I did not use the svglite package, just the svg device from the grDevices (base) package. I also normalized the path in the source because I am on a Windows machine (I believe this will change my source from forward slashes to back slashes, but maybe someone will comment on that).
Also, I created four new variables to house the svg width and height and image width and height to still be fluid with the page. I'm sure there is an easier workaround than this, but this is what I found that worked.
Inspired by R_User123456789s solution (here) for base graphics above I got it the following way with ggplot2
ui.r
shinyUI(pageWithSidebar(
headerPanel("renderSVG example"),
sidebarPanel(
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500)
),
mainPanel(
# Use imageOutput to place the image on the page
imageOutput("plot"),
imageOutput("plot_as_svg")
)
))
server.r
require("ggplot2")
shinyServer(function(input, output, session) {
## create plot with renderPlot()
output$plot<- renderPlot({
hist(rnorm(input$obs), main="Generated in renderPlot() as png")
})
## create .svg file of the plot and render it with renderImage()
output$plot_as_svg <- renderImage({
width <- session$clientData$output_plot_width
height <- session$clientData$output_plot_height
mysvgwidth <- width/96
mysvgheight <- height/96
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.svg')
# Generate the svg
#to see actually what will be plotted and compare
qplot(clarity, data=diamonds, fill=cut, geom="bar")
#save the plot in a variable image to be able to export to svg
image=qplot(clarity, data=diamonds[1:input$obs,], fill=cut, geom="bar", main = "ggplot as svg")
#This actually save the plot in a image
ggsave(file=outfile, plot=image, width=mysvgwidth, height=mysvgheight)
# Return a list containing the filename
list(src = normalizePath(outfile),
contentType = 'image/svg+xml',
width = width,
height = height,
alt = "My svg Histogram")
})
})
Related
I have a Shiny app which reads in data from a spreadsheet and produces two plots - a box plot and a histogram. I want to be able to download each plot separately (as png) using a download button when I have made various adjustments using sliders etc.
My plots work, and the download buttons work, but will only download one of the plots (apparently the one that was drawn most recently). In a reactive environment this is not very predictable and does not do what I want.
My question is, how do I specify in the download function which of the two plots to download?
An abbreviated version of my code is here:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
<... various components ...>
}}
h3("Save the Box Plot"),
downloadButton("SaveBox", label = "Save Box Plot as PNG"),
h3("Save the Histogram"),
downloadButton("SaveHist", label = "Save Histogram as PNG")
),
mainPanel(
## Display the Boxplot
plotOutput("BoxPlot"),
## Display the Histogram
plotOutput("Hist"),
)
)
)
server <- function(input, output) {
############
## Read file and process data
graphData <- reactive ({
... draw box plot ...
})
################################
## Display the box plot
output$BoxPlot <- renderPlot({
...
})
################################
## Display the histogram
output$Hist <- renderPlot({
... draw histogram ...
})
################################
## Save to PNG with the plot title as a default file name.
## Skip saving if no file has been loaded
## Save file using the system file save utility so you can name it and put it where you want
output$SaveBox = downloadHandler(
filename = function(file){
ifelse (is.null(input$DataFile),return(), str_c(input$Title, ".png"))
},
content = function(file) {
ggsave(file, width = 290, height = 265, units = "mm", device = "png")
}
)
output$SaveHist = downloadHandler(
filename = function(file){
ifelse (is.null(input$DataFile),return(), str_c(input$HistTitle, ".png"))
},
content = function(file) {
ggsave(file, width = 290, height = 265, units = "mm", device = "png")
}
)
)
The issue is that you have to tell ggsave which plot you want to save. If not specified the last plot will be saved. To fix you code you could move the plot codes into reactives, which could then be called both in the renderPlots and passed to the plot argument of ggsave so that the right plot is saved:
I adjusted your code a bit to make it a reproducible example using mtcars as example data:
library(shiny)
library(ggplot2)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
h3("Save the Box Plot"),
downloadButton("SaveBox", label = "Save Box Plot as PNG"),
h3("Save the Histogram"),
downloadButton("SaveHist", label = "Save Histogram as PNG")
),
mainPanel(
## Display the Boxplot
plotOutput("BoxPlot"),
## Display the Histogram
plotOutput("Hist")
)
)
)
server <- function(input, output) {
## Read file and process data
graphData <- reactive({
mtcars
})
box_plot <- reactive({
ggplot(graphData(), aes(mpg)) +
geom_boxplot()
})
hist_plot <- reactive({
ggplot(graphData(), aes(mpg)) +
geom_histogram()
})
## Display the box plot
output$BoxPlot <- renderPlot({
box_plot()
})
## Display the histogram
output$Hist <- renderPlot({
hist_plot()
})
output$SaveBox <- downloadHandler(
filename = function(file) {
"box_plot.png"
#ifelse(is.null(input$DataFile), return(), str_c(input$Title, ".png"))
},
content = function(file) {
ggsave(file, plot = box_plot(), width = 290, height = 265, units = "mm", device = "png")
}
)
output$SaveHist <- downloadHandler(
filename = function(file) {
"hist_plot.png"
#ifelse(is.null(input$DataFile), return(), str_c(input$HistTitle, ".png"))
},
content = function(file) {
ggsave(file, , plot = hist_plot(), width = 290, height = 265, units = "mm", device = "png")
}
)
}
shinyApp(ui, server)
I seek a method in R shiny that I can include inside a render or an observe to check if a certain value has changed.
For example :
library(shiny)
library(shinycssloaders)
ui <- fluidPage(
fluidRow(
column(5,
imageOutput("image") %>% withSpinner()
),
actionButton("button", "redo")
)
)
server <- function(input, output, session) {
data = reactiveVal(data.frame(
gp = factor(rep(letters[1:3], each = 10)),
y = rnorm(30)
))
getWidth = function(image)
paste0(session$clientData[[paste0("output_", image, "_width")]], "px")
output$image = renderImage({
input$button
outfile = tempfile(fileext = ".png")
p = ggplot(data(), aes(gp, y)) +
geom_point()
Sys.sleep(2) # to symbolise a plot which is very slow to appear
ggsave(filename = outfile, p)
return(list(src = outfile, width = getWidth("image")))
}, deleteFile = F)
}
shinyApp(ui, server)
Here just when I resize the window, the image is resaved, I do not want that. But I want that code to save the file if and only if data() or input$button is changed.
The only solution I see so far is to copy the data in an independent variable and to check if the value has changed. If the data change, save the new plot, change the value of the independent variable.
But I am not convinced that is it a good solution because the value data will be copied twice. For this dataset it not very severe, but for a dataset with millions lines the strain is harder. Or a graph that takes more than 10 seconds to save.
Thank you,
My suggestion would be
Use renderPlot instead of renderImage
Create the plot in a reactive expression
Save only when the plot changes (now it only reacts to data changes not to resizes) or the button is pressed, by using an observeEvent with those two events as triggers.
Find a working example below. If you want to change the size of the saved plot do it in the ggsave.
library(shiny)
library(shinycssloaders)
library(tidyverse)
ui <- fluidPage(
fluidRow(
column(5,
imageOutput("image") %>% withSpinner()
),
actionButton("button", "redo")
)
)
server <- function(input, output, session) {
data = reactiveVal(data.frame(
gp = factor(rep(letters[1:3], each = 10)),
y = rnorm(30)
))
p <- reactive({ggplot(data(), aes(gp, y)) +
geom_point()
})
observeEvent(c(p(), input$button), {
outfile = tempfile(fileext = ".png")
ggsave(filename = outfile, p())
})
output$image = renderPlot({
Sys.sleep(2) # to symbolise a plot which is very slow to appear
p()
})
}
shinyApp(ui, server)
I have part of a shiny function where the user selects the download image type (.png, .tiff etc) and clicks a button to download it. But all options download in .png format and I can't seem to find out what is wrong.
Note that the preview is always png. The download function creates different file types upon clicking the button. Another point to note is the use of file.copy() in downloadhandler rather than something like
png(name)
plot()
dev.off()
This is because my plotting function is complex and file.copy() is more practical.
The code is below.
#ui.R ----------------------------------------------------------
shinyUI(fluidPage(
titlePanel("Download test"),
sidebarLayout(
sidebarPanel(
numericInput("fheight", "Height (cm)", min=2, max=15, step=1, value = 10),
numericInput("fwidth", "Width (cm)", min=2, max=15, step=1, value = 10),
selectInput("fres", "Res", choices=c("100","200","300"), selected = "100"),
selectInput("fformat", "File type", choices=c("png","tiff","jpeg","pdf"), selected = "png", multiple = FALSE, selectize = TRUE),
downloadButton('bn_download', 'Download Plot')
),
# Show a plot of the generated distribution
mainPanel(
imageOutput("plotoutput")
)
)
))
# server.R ----------------------------------------------------------
shinyServer(function(input, output) {
# store some values
store <- reactiveValues(dname="AwesomeDownload")
# data creation
fn_data <- reactive({
df <- data.frame(x=rnorm(50),y=rnorm(50))
})
# create filename
fn_downloadname <- reactive({
if(input$fformat=="png") filename <- paste0(store$dname,".png",sep="")
if(input$fformat=="tiff") filename <- paste0(store$dname,".tif",sep="")
if(input$fformat=="jpeg") filename <- paste0(store$dname,".jpg",sep="")
if(input$fformat=="pdf") filename <- paste0(store$dname,".pdf",sep="")
return(filename)
})
# render png preview
output$plotoutput <- renderImage({
df <- fn_data()
fheight <- input$fheight
fwidth <- input$fwidth
fres <- as.numeric(input$fres)
png(paste0(store$dname,".png",sep=""), height=fheight, width=fwidth, res=fres, units="cm")
plot(df)
dev.off()
return(list(src = paste0(store$dname,".png",sep=""),
contentType = "image/png",
width = round((input$fwidth*as.numeric(input$fres))/2.54, 0),
height = round((input$fheight*as.numeric(input$fres))/2.54, 0),
alt = "plot"))
},deleteFile=TRUE)
# download function
fn_download <- function()
{
df <- fn_data()
fheight <- input$fheight
fwidth <- input$fwidth
fres <- as.numeric(input$fres)
if(input$fformat=="pdf") fheight <- round(fheight*0.3937,2)
if(input$fformat=="pdf") fwidth <- round(fwidth*0.3937,2)
if(input$fformat=="png") png(fn_downloadname(), height=fheight, width=fwidth, res=fres, units="cm")
if(input$fformat=="tiff") tiff(fn_downloadname(), height=fheight, width=fwidth, res=fres, units="cm",compression="lzw")
if(input$fformat=="jpeg") jpeg(fn_downloadname(), height=fheight, width=fwidth, res=fres, units="cm",quality=100)
if(input$fformat=="pdf") pdf(fn_downloadname(), height=fheight, width=fwidth)
plot(df)
dev.off()
}
# download handler
output$bn_download <- downloadHandler(
filename = fn_downloadname(),
content = function(file) {
fn_download()
file.copy(fn_downloadname(), file, overwrite=T)
}
)
})
Removing parenthesis in the download filename fixed the issue. Yeah, don't even ask. I have no idea why this is so either. But it works.
Joe Cheng's answer:
Change this line:
filename = fn_downloadname(),
to this:
filename = fn_downloadname,
I would like to create a plot in Shiny that the user can then download as a pdf using a custom font specified as a user input.
To be specific, I would like to use a pdf function such as pdf("plot.pdf", width = 5, height = 5, family = font.family), where the value of font.family is specified by the user.
Here is a simple example below: If I run the example on my machine, it works fine. However, when it is hosted on the RStudio shiny servers, I receive an error saying that the specified font family cannot be found. I think the problem is that the fonts I want are not accessible on the RStudio shiny servers, but is there a way I can include them?
server.R
shinyServer(function(input, output) {
output$distPlot <- renderPlot({
plot(1, xlim = c(0, 1), ylim = c(0, 1))
text(.5, .5, "Custom Font!!"
})
output$downloadPlot <- downloadHandler(
filename = function() {paste('CustomFont.pdf')},
content = function(file){
font.family <- input$font.family
pdf(file, width = 11, height= 8.5, family = font.family)
plot(1, xlim = c(0, 1), ylim = c(0, 1))
text(.5, .5, fonts(), cex = 10)
dev.off()
}, contentType = "image/pdf"
)
})
ui.R
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("font.family", "Choose Font",
choices = c("Helvetica Neue", "Times New Roman", "Arial")
),
downloadButton("downloadPlot", "Download Plot as PDF")
),
# Show a plot of the plot
mainPanel(
plotOutput("distPlot", width = "800px", height = "800px")
))))
I had a similar problem. To solve that, much of the renderPlot() functionality was recreated using renderImage(), as described in this Shiny tutorial article. Font rendering then worked as desired.
This is the code which solved that question; it might also solve this one.
ui.R amend to
mainPanel(
imageOutput("myImage")
)
server.R
shinyServer(function(input, output, session) {
# A dynamically-sized plot
output$myImage <- renderImage({
# Read myImage's width and height. These are reactive values, so this
# expression will re-run whenever they change.
width <- session$clientData$output_myImage_width
height <- session$clientData$output_myImage_height
# For high-res displays, this will be greater than 1
pixelratio <- session$clientData$pixelratio
# A temp file to save the output.
outfile <- tempfile(fileext='.png')
# Generate the image file
png(outfile, width=width*pixelratio, height=height*pixelratio,
res=72*pixelratio)
plot(rnorm(100), rnorm(100), family="serif")
dev.off()
# Return a list containing the filename
list(src = outfile,
width = width,
height = height,
alt = "This is alternate text")
}, deleteFile = TRUE) # delete the temp file when finished
})
I am going to build a web page that clusters iris data based on the number of clusters the user enters. It uses K means algorithm to cluster the data and shows a plot of clustered data.
It does not work and I do not know why. I started from this link:
http://rstudio.github.io/shiny/tutorial/#sending-images
Here are my files:
ui.R
library(shiny)
shinyUI(pageWithSidebar(
headerPanel("Clustering iris Data"),
sidebarPanel(
sliderInput("k", "Number of clusters:",
min = 1, max = 5, value = 3)
),
mainPanel(
# Use imageOutput to place the image on the page
imageOutput("myImage")
)
))
and server.R
library(shiny)
library(caret)
library(ggplot2)
data(iris)
inTrain <- createDataPartition(y=iris$Species, p=0.7, list=FALSE)
training <- iris[inTrain,]
testing <- iris[-inTrain,]
shinyServer(function(input, output, session) {
output$myImage <- renderImage({
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.png')
kMeans1 <- kmeans(subset(training,select=-c(Species)),centers=input$k)
training$clusters <- as.factor(kMeans1$cluster)
# Generate the PNG
png(outfile, width=400, height=600)
qplot(Petal.Width,Petal.Length,colour=clusters,data=training,main="iris Data Clusters")
print(qplot)
#plot(training$Petal.Width,training$Petal.Length,colour=clusters,data=training,main="iris Data Clusters")
#hist(rnorm(input$k), main="Generated in renderImage()")
#myImage
dev.off()
# Return a list containing the filename
list(src = outfile,
contentType = 'image/png',
width = 400,
height = 600,
alt = "This is alternate text")
}, deleteFile = TRUE)
})
I think you just have to change
qplot(Petal.Width,Petal.Length,colour=clusters,data=training,main="iris Data Clusters")
print(qplot)
to something like this:
qP <- qplot(
Petal.Width,Petal.Length,
colour=clusters,data=training,
main="iris Data Clusters")
print(qP)
Because your call to qplot() was not actually creating an object; which is why print(qplot) was printing the function definition of qplot in the console.