I recently started using rPivotTable to produce some impressive charts and tables. I am using rPivotTable in a Shiny application. I was wondering if it is possible to export the output of the rPivotTable(Table, Bar chart, line chart etc) as image from the web browser. In RStudio(without Shiny), it can be done as the viewer has an option for Export->Save as Image. Is there any way to save the charts and tables.
A pivotTable is a htmlwidget, so you can use htmlwidgets::saveWidget to save the table in a html file and webshot::webshot to export it to png (or pdf).
library(shiny)
library(rpivotTable)
library(htmlwidgets)
library(webshot)
ui <- fluidPage(
br(),
rpivotTableOutput("pivotbl"),
br(),
downloadButton("export", "Export")
)
server <- function(input, output, session){
pivotTable <- rpivotTable(
Titanic,
rows = "Survived",
cols = c("Class","Sex"),
aggregatorName = "Sum as Fraction of Columns",
inclusions = list( Survived = list("Yes")),
exclusions= list( Class = list( "Crew")),
vals = "Freq",
rendererName = "Table Barchart"
)
output[["pivotbl"]] <- renderRpivotTable({
pivotTable
})
output[["export"]] <- downloadHandler(
filename = function(){
"pivotTable.png"
},
content = function(file){
tmphtml <- tempfile(fileext = ".html")
saveWidget(pivotTable, file = tmphtml)
webshot(tmphtml, file = file)
}
)
}
shinyApp(ui, server)
EDIT
Here is a way to export only the graph, using the dom-to-image JavaScript library.
Download the file dom-to-image.min.js and put it in the www subfolder of the app.
Here is the app:
library(shiny)
library(rpivotTable)
js <- "
function filter(node){
return (node.tagName !== 'i');
}
function exportPlot(filename){
var plot = document.getElementsByClassName('pvtRendererArea');
domtoimage.toPng(plot[0], {filter: filter, bgcolor: 'white'})
.then(function (dataUrl) {
var link = document.createElement('a');
link.download = filename;
link.href = dataUrl;
link.click();
});
}
Shiny.addCustomMessageHandler('export', exportPlot);
"
ui <- fluidPage(
tags$head(
tags$script(src = "dom-to-image.min.js"),
tags$script(HTML(js))
),
br(),
rpivotTableOutput("pivotbl"),
br(),
actionButton("export", "Export")
)
server <- function(input, output, session){
pivotTable <- rpivotTable(
Titanic,
rows = "Survived",
cols = c("Class","Sex"),
aggregatorName = "Sum as Fraction of Columns",
inclusions = list( Survived = list("Yes")),
exclusions= list( Class = list( "Crew")),
vals = "Freq",
rendererName = "Table Barchart"
)
output[["pivotbl"]] <- renderRpivotTable({
pivotTable
})
observeEvent(input[["export"]], {
session$sendCustomMessage("export", "plot.png")
})
}
shinyApp(ui, server)
Related
I'm trying to get input from the User( a bunch of images) and then display them on R shiny using Lightbox gallery. Unfortunately I'm unable to get the images, Please help with this regard,Thank you in advance for your help .
below is my code:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fluidRow(
fileInput(inputId = "file_upload", label = "Upload Images", multiple = TRUE, accept = c('image/*', ".zip"),
width = NULL, buttonLabel = "Browse",
placeholder = "No file selected"),
actionButton("go","Run")
)
),
mainPanel(
fluidRow(
column(12,(uiOutput('lb'))
))
)
)
)
server <- function(input, output) {
vals<-reactiveValues(result=NULL,img_fldr_name=NULL,images=NULL)
observeEvent(input$file_upload, {
c_t <- Sys.time()
dt_str <- format(c_t, "%Y_%m_%d")
hr_str <- format(c_t, "%H_%M_%S")
vals$img_fldr_name <- paste0(dt_str, "_", hr_str)
if (tools::file_ext(input$file_upload$datapath)[[1]] %in% c("jpeg","png","jpg")){
create_folder_name=paste0("trials/www/",vals$img_fldr_name)
dir.create(path = create_folder_name)
for(i in 1:length(input$file_upload$datapath)){
file.copy(input$file_upload$datapath[[i]], paste0(create_folder_name,"/",input$file_upload$name[[i]]), overwrite = TRUE)
}
df <- list.files(paste0("trials/www/",vals$img_fldr_name), full.names = T)
print(df)
images<<-data.frame(src=list.files(paste0("trials/www/",vals$img_fldr_name), full.names = T))
#print(head(vals$images))
vals$result<-images
}
})
observeEvent(input$go,{
output$lb <- renderUI({
images <<- data.frame(src = vals$result$src)
vals$images <- images
lightbox_gallery <- function(df, gallery, display = 'block'){
print(df)
tags$div(style = sprintf('display: %s;', display),
tagList(tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "lightbox-2.10.0/lightbox.min.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "gallerystyle.css")
),
tags$div(class = 'card-deck',
lapply(seq_len(nrow(df)), function(i){
print("Inside Loop")
print(df$src[i])
tags$div(`data-type`="template", class = 'card',
tags$a(#id = df$key[i],
href = df$src[i],
`data-lightbox` = gallery, # this identifies gallery group
`data-title` = paste0("Image"),
tags$span(style="color:black;text-align: center"),
tags$img(class = 'card-img-top',
src = df$src[i],
width = '80px',
height = 'auto')),
)
})
),
includeScript("www/lightbox-2.10.0/lightbox.min.js")
))
}
lightbox_gallery(vals$images, 'gallery', display = TRUE)
#paste0()
})
})
}
shinyApp(ui = ui, server = server)
Print statement inside loop gives proper path to image.
Also the respective folders and scripts are in place.
The code below works. I removed the nesting of how you prepare the output for better readability.
I think the main problem was that you read the image files from a directory outside www in your project folder. I would keep everything in there. This makes it easier. Take a look at the code: when the images are uploaded I explicitely save them to www/.... While preparing the output, I remove the www prefix using gsub, since Shiny is looking for resources in there by default.
Finally, make sure to use reactive values properly. You don't need to define a global images variable. Just use the reactive values. And there again, it is probably sufficient to have one reactive value which holds all the paths as a vector (e.g. paths <- reactiveVal(NULL)).
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fluidRow(
fileInput(inputId = "file_upload", label = "Upload Images", multiple = TRUE, accept = c('image/*', ".zip"),
width = NULL, buttonLabel = "Browse",
placeholder = "No file selected"),
actionButton("go","Run")
)
),
mainPanel(
fluidRow(
column(12,(htmlOutput('lb'))
))
)
)
)
server <- function(input, output) {
vals<-reactiveValues(result=NULL,img_fldr_name=NULL,images=NULL)
observeEvent(input$file_upload, {
c_t <- Sys.time()
dt_str <- format(c_t, "%Y_%m_%d")
hr_str <- format(c_t, "%H_%M_%S")
vals$img_fldr_name <- paste0(dt_str, "_", hr_str)
if (tools::file_ext(input$file_upload$datapath)[[1]] %in% c("jpeg","png","jpg")){
create_folder_name=paste0("www/trials/www/",vals$img_fldr_name)
dir.create(path = create_folder_name)
for(i in 1:length(input$file_upload$datapath)){
file.copy(input$file_upload$datapath[[i]], paste0(create_folder_name,"/",input$file_upload$name[[i]]), overwrite = TRUE)
}
images <- data.frame(src=list.files(paste0("www/trials/www/",vals$img_fldr_name), full.names = T))
vals$result <- images
}
})
get_lb <- eventReactive(input$go,{
images <- data.frame(src = vals$result$src)
vals$images <- images
lightbox_gallery(vals$images, 'gallery', display = TRUE)
})
lightbox_gallery <- function(df, gallery, display = 'block'){
tags$div(style = sprintf('display: %s;', display),
tagList(tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "lightbox.min.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "gallerystyle.css")
),
tags$div(class = 'card-deck',
lapply(seq_len(nrow(df)), function(i){
print("Inside Loop")
print(df$src[i])
tags$div(`data-type`="template", class = 'card',
tags$a(#id = df$key[i],
href = gsub("^www/", "", df$src[i]),
`data-lightbox` = gallery, # this identifies gallery group
`data-title` = paste0("Image"),
tags$span(style="color:black;text-align: center"),
tags$img(class = 'card-img-top',
src = df$src[i],
width = '80px',
height = 'auto')),
)
})
),
includeScript("www/lightbox.min.js")
))
}
output$lb <- renderUI({
get_lb()
})
}
shinyApp(ui = ui, server = server)
I'm modularizing a Shiny app I developed using shinydashboard packages. Despite it traditionally works when I use it without involving modules, I can't make it work when I try to divide it into modules and submodules. Here I would like to combine two UIs (one for the sidebar, one for the body) in order to upload a dataset from the sidebar and show it into the body.
I'd be very glad if anybody could provide me some help with this.
Here is the code of the general Shiny app:
library(shiny)
library(excelR)
library(vroom)
library(readxl)
library(janitor)
library(dplyr)
library(shinydashboard)
library(shinydashboardPlus)
# # load separate module and function scripts
source("modules.R")
# app_ui
app_ui <- function() {
tagList(
shinydashboardPlus::dashboardPagePlus(
header = shinydashboardPlus::dashboardHeaderPlus(title = "module_test",
enable_rightsidebar = FALSE),
sidebar = shinydashboard::dashboardSidebar(
shinydashboard::sidebarMenu(id = "tabs",
import_sidebar_ui("import"))
),
body = shinydashboard::dashboardBody(shinydashboard::tabItems(
import_body_ui("import"))
),
rightsidebar = NULL,
title = "Module App"
)
)
}
# app_server
app_server <- function(input, output, session) {
shiny::moduleServer(id = "import", module = import_server)
}
####################################################################
run_app <- function(...) {
shiny::shinyApp(
ui = app_ui,
server = app_server)
}
#---------------------------------
run_app()
and here is the modules.R file I wrote containing the UIs for sidebar and body, plus the server:
# Import module ####
#
# Import sidebar UI
import_sidebar_ui <- function(id) {
ns <- NS(id)
shinydashboard::menuItem("Module Testing",
tabName = "tab_testing_mod",
icon = icon("th"),
tagList(
selectInput(ns("input_type"),
"Type of file:",
choices = c("Choose one" = "",".csv" = "csv",
".txt" = "txt", ".xls/.xlsx" = "xlsx"),
selected = NULL),
uiOutput(ns("inputControls")),
fileInput(ns("file"), "Data", buttonLabel = "Upload..."),
checkboxInput(ns("rownames"), "Check if 1st column contains rownames"),
checkboxInput(ns("constant"), "Remove constant columns?"),
checkboxInput(ns("empty"), "Remove empty cols?"),
actionButton(ns("bttn_import"), "Import data")
)
)
}
# Import body UI
import_body_ui <- function(id) {
ns <- NS(id)
shinydashboard::tabItem(tabName = "tab_testing_mod",
fluidRow(
h3("Imported Data"),
excelR::excelOutput(ns("preview")))
)
}
# Import server
import_server <- function(input, output, session) {
ns <- session$ns
output$inputControls <- renderUI({
tagList(
switch(input$input_type,
"csv" = textInput("delim", "Delimiter (leave blank to guess)", ""),
"txt" = textInput("delim", "Delimiter (leave blank to guess)", "")
),
switch(input$input_type,
"xlsx" = numericInput("sheet", "Sheet number", value = 1))
)
})
raw <- reactive({
req(input$file)
if (input$input_type == "csv" || input$input_type == "txt") {
delim <- if (input$delim == "") NULL else input$delim
data <- vroom::vroom(input$file$datapath, delim = delim)
} else if (input$input_type == "xlsx") {
data <- tibble::as.tibble(readxl::read_excel(input$file$datapath, sheet = input$sheet, col_names = TRUE))
} else {
return(NULL)
}
raw <- data
raw
})
tidied <- eventReactive(input$bttn_import,{
out <- raw()
if (input$empty) {
out <- janitor::remove_empty(out, "cols")
}
if (input$constant) {
out <- janitor::remove_constant(out)
}
if (input$rownames) {
out <- tibble::column_to_rownames(out, var = colnames(out[1]))
}
out <- out %>% dplyr::mutate_if(is.character,as.factor)
out
})
output$preview <- excelR::renderExcel({
excelR::excelTable(data = raw(),
colHeaders = toupper(colnames(raw())),
fullscreen = FALSE,
columnDrag = TRUE,
rowDrag = TRUE,
wordWrap = FALSE,
search =TRUE,
showToolbar = TRUE,
minDimensions = c(ncol(raw()),10)
)
})
}
It seems to me I can upload the dataset (.csv, .txt or .xlsx) files but I can't show it into the body.
I'd be very glad if you can help me, thank you very much in advance for your assistance.
I am setting up a module, with the purpose of having two data tables in the same app, in different tabs.
I would like to be able to edit and save each table separately.
In my code, only the first "save" button works, and it saves both data tables.
Ideally, each save button should work and save only the corresponding table.
Important: I use the modified version of DTedit:
devtools::install_github('DavidPatShuiFong/DTedit#2.2.1')
Here is my problematic code:
library(shiny)
library(DTedit)
myModuleUI <- function(id,nam) {
ns <- shiny::NS(id)
shiny::tagList(
br(),
##### needs corrections!!
tabsetPanel(tabPanel("XXX", dteditmodUI(ns(nam)),actionButton(ns("reset"), "Reset to Saved", styleclass = "warning"), actionButton(ns("saveBtn"), label = "save"), br(),
id=ns('tabset'), type = 'tabs')
)
)
####
}
myModule <- function(input, output, session,df,nam,taby,wb) {
dfr=reactiveVal()
dfr(df)
Grocery_List_Results <- shiny::callModule(
dteditmod,
id = nam,
thedata =dfr)
# ### save part
savd = data.frame(isolate(dfr()))
observeEvent(input$saveBtn, {
print("Q")
## Add worksheets
st = paste(taby,as.character(unclass(Sys.time())),sep="_")
addWorksheet(wb, st)
writeData(x = Grocery_List_Results$thedata,
wb = wb,
sheet = st)
saveWorkbook(wb, "wb.xlsx", overwrite = T)
savd <<- Grocery_List_Results$thedata
shinyalert(title = "Saved!", type = "success")
})
observeEvent(input$reset, {
dfr(savd)
print(dfr)
shinyalert(title = "Reset to saved data!", type = "info")
})
}
########
ui <- fluidPage(
h3('Grocery List'),
myModuleUI('myModule1',nam="groc"),br(),
myModuleUI('myModule1',nam="groc2")
)
server <- function(input, output, session) {
df= data.frame(
Buy = c('Tea', 'Biscuits', 'Apples',"Tea","Apples"),
Quantity = c(7, 2, 5,9,44),
stringsAsFactors = FALSE
)
file = "AICs.xlsx"
wb <- loadWorkbook(file)
shiny::callModule(myModule, 'myModule1',nam="groc",df=df,taby="Tea",wb)
shiny::callModule(myModule, 'myModule1',nam="groc2",df=df,taby="Apples",wb)
}
shinyApp(ui = ui, server = server)
Appreciate your time!
At long last,
and thanks to this thread,
it seems that I managed to solve this:
library( "openxlsx" )
library("shiny" )
library(shinyalert)
library(shinysky)
library(DTedit) ## used the modified version from https://github.com/DavidPatShuiFong/DTedit
#installed with devtools::install_github('DavidPatShuiFong/DTedit#2.2.1')
results_2_UI <- function(id,nam) {
useShinyalert()
ns <- NS(id)
tabPanel(
title = "Export1",
dteditmodUI(ns(nam)),
actionButton(ns("reset"), "Reset to Saved", styleclass = "warning"), actionButton(ns("saveBtn"), label = "save")
)
}
results_3_UI <- function(id,nam) {
useShinyalert()
ns <- NS(id)
tabPanel(
title = "Export2",
dteditmodUI(ns(nam)),
actionButton(ns("reset"), "Reset to Saved", styleclass = "warning"), actionButton(ns("saveBtn"), label = "save")
)
}
results <- function(input, output, session,df,nam,taby,wb) {
## do some complicated data transformations
dfr=reactiveVal()
dfr(df)
Grocery_List_Results <- shiny::callModule(
dteditmod,
id = nam,
thedata =dfr)
# ### save part
savd = data.frame(isolate(dfr()))
observeEvent(input$saveBtn, {
print("Q")
## Add worksheets
st = paste(taby,as.character(unclass(Sys.time())),sep="_")
addWorksheet(wb, st)
writeData(x = Grocery_List_Results$thedata,
wb = wb,
sheet = st)
saveWorkbook(wb, "wb.xlsx", overwrite = T)
savd <<- Grocery_List_Results$thedata
shinyalert :: shinyalert(title = "Saved!", type = "success")
})
observeEvent(input$reset, {
dfr(savd)
print(dfr)
shinyalert::shinyalert(title = "Reset to saved data!", type = "info")
})
}
### module end
ui <- fluidPage(
tabsetPanel(
id = "tabs",
# results_1_UI(id = "test1"),
results_2_UI(id = "test2",nam="groc"),
results_3_UI(id = "test3",nam="groc2")
)
)
server <- function(input, output, session) {
df= data.frame(
Buy = c('Tea', 'Biscuits', 'Apples',"Tea","Apples"),
Quantity = c(7, 2, 5,9,44),
stringsAsFactors = FALSE
)
file = "AICs.xlsx"
wb <- loadWorkbook(file)
callModule(
module = results,
id = "test2",
nam="groc",df=df,taby="groc",wb=wb
)
callModule(
module = results,
id = "test3",
nam="groc2",df=df,taby="groc2",wb=wb
)
}
shinyApp(ui = ui, server = server)
Fingers crossed, it is OK!
The extensions Buttons works great for shiny application, from library(DT). However it export the data without formatting. Is there a way to export data with format (e.g. percentage, or currency)? Similar question left unsolved.
Reproducible code
library(DT)
data.frame(a = c(1,2),
b = c(2,3)) %>%
datatable(extensions = 'Buttons', options = list(
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')) )%>%
formatPercentage('a') %>%
formatCurrency('b')
Instead of using the Buttons extension, you can use the TableExport library.
library(shiny)
library(DT)
library(shinyjs)
js_export <-
"
var $table = $('#DTtable').find('table');
var instance = $table.tableExport({
formats: ['xlsx'],
exportButtons: false,
filename: 'myTable',
sheetname: 'Sheet1'
});
var exportData0 = instance.getExportData();
var exportData = exportData0[Object.keys(exportData0)[0]]['xlsx'];
instance.export2file(exportData.data, exportData.mimeType, exportData.filename,
exportData.fileExtension, exportData.merges,
exportData.RTL, exportData.sheetname);
"
ui <- fluidPage(
useShinyjs(),
tags$head(
# put these files in the www subfolder
tags$script(src = "xlsx.core.min.js"),
tags$script(src = "FileSaver.min.js"),
tags$script(src = "tableexport.min.js")
),
DTOutput("DTtable"),
actionButton("export", "Export table")
)
server <- function(input, output, session){
output$DTtable <- renderDT({
data.frame(
a = c(1,2),
b = c(2,3)
) %>%
datatable() %>%
formatPercentage('a') %>%
formatCurrency('b')
})
observeEvent(input$export, {
runjs(js_export)
})
}
shinyApp(ui, server)
Shiny experts!
In our application we have download button for plot downloading. The button is working only when some data are loaded and processed. When you push the button before, there is an error message from plotting function, because it has no data.
content = function(file) {
r <- rChart_line_plot(follow_view_func(),log_scale = input$checkbox_log_scale_plot,isRel = input$checkboxRelativeTab2)
r$save(file, standalone = TRUE)
}
We want to make our app foolproof and error-free. Is there any possible way to send to downloadHandler's content "NULL"? This don't work.
content = function(file) {
if ( "our data are ready for printing" ) {
r <- rChart_line_plot(follow_view_func(),log_scale = input$checkbox_log_scale_plot,isRel = input$checkboxRelativeTab2)
r$save(file, standalone = TRUE)
} else {
NULL
}
}
And we're getting:
Error opening file: 2
Error reading: 9
Is there something like validate() function with even information for user "Please load file first"
Thank You a lot.
You are correct that you want a validate statement. Here is a link with descriptions from the RStudio team. This will allow you to have a more informative error message. Your complete downloadHandler function would look something like the following. Note that this assumes your dataset could be null.
output$Download <- downloadHandler(
filename = function() {
paste("test.png",sep="")
},
content = function(file) {
myData <- follow_view_func()
validate(
need(!is.null(myData), "Please select valid dataset")
)
r <- rChart_line_plot(myData,log_scale = input$checkbox_log_scale_plot,isRel = input$checkboxRelativeTab2)
r$save(file, standalone = TRUE)
}
)
Here is a complete reproducible example with the iris dataset.
library(shiny)
library(rCharts)
runApp(
list(
ui = pageWithSidebar(
headerPanel("Using 'validate' for useful error messages"),
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("null", "iris")),
selectInput(inputId = "x",
label = "Choose X",
choices = c('SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth'),
selected = "SepalLength"),
selectInput(inputId = "y",
label = "Choose Y",
choices = c('SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth'),
selected = "SepalWidth"),
downloadButton("Download")
),
mainPanel(
showOutput("myChart", "polycharts")
)
),
server = function(input, output) {
datasetInput <- reactive({
switch(input$dataset,
"iris" = iris,
"null" = NULL)
})
myChart <- reactive({
myData <- datasetInput()
validate(
need(!is.null(myData), "Please select valid dataset")
)
names(myData) = gsub("\\.", "", names(myData))
p1 <- rPlot(input$x, input$y, data = myData, color = "Species",
facet = "Species", type = 'point')
p1$addParams(dom = 'myChart')
return(p1)
})
output$myChart <- renderChart({myChart()})
output$Download <- downloadHandler(
filename = function() {
paste("test.png",sep="")
},
content = function(file) {
p1 <- myChart()
p1$save(file, standalone = TRUE)
}
)
}
)
)
UPDATE
As per the OP request, it may be ideal to have no error whatsoever with the download button. The only solution I could come up with is to make the button a conditionalPanel. This intuitively makes sense to me because why would you download if there is nothing on the screen? The only change in the code above needed for this is to change:
downloadButton("Download")
to
conditionalPanel("output.myChart", downloadButton("Download"))
Now the download button will only be present when a valid chart is created.