0%

Shiny用法整理(三)

继续整理shiny的几个用法

Download two or more plots in one download button

最近遇到的一个问题:如何在Shiny上通过download按钮同时下载多张图片。网上也有人提了相同的问题,但是没有给出合适的解决办法 Shiny的download本质是可以下载任何形式的文件,如表格、图片等等;shiny的官网介绍http://shiny.rstudio.com/articles/download.html中只提了常规的用法。经过我多次尝试,基本猜测shiny的downloadHandler一次点击只能下载一个对象(因为对于网页设计中,一次点击确实只能下载一个对象)

因此转化下思路,我可以将多个图片先保存到一个临时文件夹中,然后压缩后再通过shiny下载

网上查了下,也有人用这个方法来下载多个文件压缩后的压缩包,如Shiny: download zip archive

现在模仿下就很简单了(将图片先存在p列表中,接着用tempdir()建个临时文件夹,然后用ggsave保存图片,最后用zip压缩):

output$download_plot <- downloadHandler(
  filename = function(){
    paste0("plots", ".zip")
  },
  # contentType = "image/png",
  content = function(file){
    fs <- c()
    tmpdir <- tempdir()
    setwd(tempdir())

    for(i in 1:length(var_name)) {
      filelist <- paste0(var_name[i], ".png")
      g <- p[[i]]
      fs <- c(fs, filelist)
      ggsave(filename = filelist, plot = g, device = "png", width = 16, height = input$plotheight, units = "cm", dpi = 600)
    }
    zip(zipfile = file, files = fs)
  }
)

由于downloadHandler是下载你指定的对象,所以如果不设置contentType,shiny程序会更具对象的拓展名来确定数据类型,这样通过压缩多张图片的方式,绕过了无法用downloadHandler来循环下载多张图片的问题

如果很N张图片需要下载,而且速度比较慢的话,可以考虑用Shiny的Progress indicators,参考之前的Shiny用法整理(一),然后对上面代码修改下(指定个progress,然后设置每出一张图,进度条就增加1/N,并给出提示信息~):

output$download_plot <- downloadHandler(
  filename = function(){
    paste0("plots", ".zip")
  },
  # contentType = "image/png",
  content = function(file){
    fs <- c()
    tmpdir <- tempdir()
    setwd(tempdir())

    progress <- shiny::Progress$new()
    on.exit(progress$close())

    progress$set(message = "Begin to download boxplots:", value = 0)

    for(i in 1:length(var_name)) {
      filelist <- paste0(var_name[i], ".png")
      g <- p[[i]]
      fs <- c(fs, filelist)
      ggsave(filename = filelist, plot = g, device = "png", width = 16, height = input$plotheight, units = "cm", dpi = 600)

      progress$inc(1/length(var_name), detail = "Please wait...")
    }
    zip(zipfile = file, files = fs)
  }
)

但是对于这个进度条的位置不太满意(默认是右下角),如果下个对这个进度条进行修改,则需要重新设置.shiny-notification,这个是notification的类,至于如何查到shiny的notification相关类有哪些,则可以查看shiny的CSS代码https://github.com/rstudio/shiny/blob/master/inst/www/shared/shiny.css,搜索下shiny-notification即可,其默认的CSS如下:

.shiny-notification {
  background-color: #e8e8e8;
  color: #333;
  border: 1px solid #ccc;
  border-radius: 3px;
  opacity: 0.85;
  padding: 10px 8px 10px 10px;
  margin: 2px;
}

而我们需要在Shiny的UI部分加入指定的CSS代码,可参考Adjust size of Shiny progress bar and center it ,主要是修改notification的位置(当然也可以设置宽度和高度等等),如下:

tags$head(
    tags$style(
      HTML(".shiny-notification {
             position:fixed;
             top: calc(50% + 250px);;
             left: calc(50% + 500px);;
             }"
      )
    )
  )

至于为什么是修改notification,是因为进度条的style选的就是notification,因此上述用法在showNotification中也是通用的

Shiny App Usage Tracking

对于如何监控你的shiny app,shiny官方给出了一整套完整的方法,如果你是用shinyapps.io、RStudio Connect以及Shiny Server Pro,那么无需考虑这个,如果是使用free shiny,则需要借助第三方工具Google Analytics / Piwik,可参考官方文档Shiny App Usage Tracking

出于在墙内的原因,Google Analytics工具我是无法使用的,而Piwik是基于PHP+Mysql的,由于其开源又功能的强大,所以想立马使用不太现实,所以我想寻找一种基于shiny或者R就能使用的简单监控系统,因为我只需要知道访问量和点击量就行

我先找到一个作者使用其自己的写的R代码来监听他在服务器上运行的所有shiny程序,其实是R代码来调用netstat命令来 实现的,这样可以简单统计用户访问量了,参考文章How to monitor app usage on Shiny Server Open Source

但是这个方法并不能很好的解决我的需求,因此我还找了另一个作者提供的方法,使用shiny的一些插件来获取网页用户填写的信息,参考文章Mimicking a Google Form with a Shiny app

按照作者的意思,其是mimicks a Google Form,将用户填写的提交的信息收集并存储起来,然后再可视化用于展示用户信息,那么在UI中只需要增加几个框用于用户填写信息

fluidRow(
      column(
        3, selectInput("usertype", h4("User Type", span("*", class = "mandatory_star")), choices = list("技术支持", "学术顾问", "其他"))
      ),
      column(
        3, textInput("userid", h4("User Name", span("*", class = "mandatory_star")), placeholder = "...")
      ), 
      column(
        3, textInput("projectid", h4("Project Id", span("*", class = "mandatory_star")), placeholder = "For example P20180100001")
      )
    )

然后再server中将填写的信息记录下来(包括日期等信息),然后存储到指定文件中

log_con <- data.frame("boxplot_count", input$usertype, input$userid, input$projectid, format(Sys.time(), "%Y-%m-%d-%H-%M"))
write.table(log_con, file = "log/tools.log", sep = "\t", row.names = F, col.names = F, quote = F, append = T)

关于shiny存储数据可以查看其官方文档Persistent data storage in Shiny apps,包括如果调用各个类型数据库等,我这只是简单使用Local storage

那么如何统计点击量呢,如果是一般按钮的点击,可以粗暴点用shiny基本用法中的,点击一次就计数一次,但是当我的情况是需要统计图片超链接的点击量,那么上述方法就不可行了

这是得借助shinyJS这个JS包来实现,其实这个需求对于正常的前端IT来说,就是一个JS命令的问题,那么用shinyJS则要其onclick函数来实现,现在在UI中设定好图片超链接的id,也就是类

div(
    tags$a(id = "count_id", tags$img(src="plot.png", width = "100%"), href = "http://xxx:3838/plot/"),
    tags$h3("Plot Tools", style = "color:#47a3da; font-family: sans-serif"),
)

然后再server中对于这个ID使用onclick事件,并记录点击相关信息

shinyjs::onclick(id = "count_id", {
    log_con <- data.frame("boxplot_count", input$usertype, input$userid, input$projectid, format(Sys.time(), "%Y-%m-%d-%H-%M"))
    write.table(log_con, file = "log/tools.log", sep = "\t", row.names = F, col.names = F, quote = F, append = T)
  })

这样结合上述,就能简单的实现用户的登录的记录以及超链接点击的记录等需求

本文出自于http://www.bioinfo-scrounger.com转载请注明出处