之后的文章一般会发于此处了:个人博客

Shiny是基于R的实时计算服务器(serve),并通过CSS,htmlwidge,Javascript来进行拓展的web **交互界面(UI)**展现的构造工具包。

R作为一种以本地会话(local session)为主要使用场景的语言,交互性、可嵌入性和自动化一直是其软肋。Rstudio希望发展基于R构建BI工具,就需要将本地的会话和线上的展示交互结合。因此,这也引出了shiny为回应以上需求,而在结构设计上着墨的三个根本要素:服务器(Serve)、交互界面(UI)和反应连结(Reactivity)。

这也可以引出shiny设计的一个根本思路:反应表达式(reactive expression)。最简洁的理解反应式表达的的示例:

input values => R code => output values/result

当表达式开始执行的时候,将会自动跟踪读取到的反应值以及调用的其他反应表达式。如果反应表达式所依赖的反应值和反应表达式发生了改变,那么该反应表达式的返回值也应该变化,改变一个反应值会自动引发依赖于它的反应表达式重新执行。
——shiny中文教程

具体而言,shiny的构成组件主要是这个样子:

服务器serve用户界面UI

表达抑制(prevent)
isolate()
输入(input$x)
reactiveValues()
*Input()
表达(expression)
reactive()
触发展示(Trigger)
observe()
observeEvent()
输出(output$y)
render*()
延迟表达
(delay reaction)
eventRactive()
  • 此处的serve和ui不等同于shiny中实际的serve和ui函数,仅是指对于app用户来说的最终呈现情况

基于以上对于shiny设计思路的介绍,就能容易理解shiny代码的基本结构,ui部分(对象值),server部分(函数)以及app结合部分(对象值)。

library(shiny)
ui #UI部分 <- fluidPage(
numericInput(inputId = "n","Sample size", value = 25),
plotOutput(outputId = "hist")
)
server #服务器部分 <- function(input, output) {
output$hist <- renderPlot({hist(rnorm(input$n))})
}shinyApp(ui = ui, server = server) #二者结合为shiny

首先,最基本的问题点是shiny app最初的动机需求。这个初始的需求纲要不要求全备,但希望应该对以下几个点有一定的考虑:

  • 数据来源(自带数据、虚拟数据、用户上传数据等)
  • 交互输入(点选、键入、拖拽等)
  • 大致呈现方式类型(图、表、文字等)
  • 交互的数据纬度(交互涉及的数据字段和性质等)

对此有了基本思路之后,就是具体实现的层面。以上纲要也将在具体实现过程中指导具体过程,同时也会在考虑具体实现层面时进一步优化修改。

接下来这篇文章将会以完成一个完整的shiny app的思路顺序。关于更详细的shiny app的构成要件介绍,还是可以是通过shiny的速查表来更好了解。再往后则是根据两个我做的shiny小品来聊聊shiny app构建的一些共通基本思路。

UI

布局(layout)

布局部分根据需求纲要的确定整个app的大致框架,常见的框架大致如下:

flowLayout(),splitLayout()和verticalLayout()适合不同构成要素内容相对均衡的使用场景,并可根据构成要素内容大小多寡具体在三者中选择。

flowRow()和sidebarLayout()适用于构成要素内容差异较大,例如较少的输入要素或需要凸显输出要素等。相对而言,前者适合有一定量的输入要素但输出仍然是需要凸显的情况,后者则是输入要素较少的情况。

输入(input)

输入部分是用户在进行交互时操作的对象,是UI界面的直接体现,在UI部分进行定义和设置,并通过「input$<inputId>」与server部分链接。此外,输入的值都是有反应式的(reactive,最大程度的简化了事件处理代码,从而更专注于应用本身),没有无结果的输入,所以需在server部分写的时候注意每个input都要有对应的反应和输出。

常用的input控件:

输出(output)

输出部分有两个构件组成,在UI部分呈现的output函数,以及在server部分定义的函数计算的对象。二者是使用时候需要一起考虑。

提呈函数 输出函数 生成对象
renderDataTable dataTableOutput DataTable
renderUI htmlOutput/ uiOutput raw HTML
renderImage imageOutput 图片(image)
renderPlot plotOutput 图表(plot)
renderTable tableOutput 表(table)
renderText textOutput 文本(text)
renderPrint verbatimTextOutput 输出值(text,summary()之类的结果)

关于输出部分还需要注意以下几点:

  • server部分的呈现函数(render*/* Output)里是存放最终结果的,若从最开始输入需要经过一系列的计算和赋值等过程,就需要借助到以下将会讲到的server的反应部分;

  • 每个render* 函数部分的R代码需要用花括号{}收纳;

  • 需要将render* 函数的值赋值到output对象,以最开始时候的示例代码为例:

library(shiny)
ui  <- fluidPage(
plotOutput(outputId = "hist") #UI部分的Output函数,shiny标准的函数一般是也成为render*()函数
)
server  <- function(input, output) {
output$hist  <- renderPlot({ #UI部分的输出函数的对象内容在server部分进行定义hist(rnorm(input$n))})
}

运算(server)

该部分将重点讨论反应式(reactivity)。如何理解反应式,可以通过这串代码可以体会:

server <- function(input, output) {
output$plot <- renderPlot({data <- getSymbols(input$symb, src = "yahoo",from = input$dates[1],to = input$dates[2],auto.assign = FALSE)chartSeries(data, theme = chartTheme("white"),type = "line", log.scale = input$log, TA = NULL)
})
}

如上所示,server部分把所有的计算反应都只放到一个函数renderPlot中,但这也意味着每次运行都在重新获取和计算数据,将会降低app的反应速度和不必要的带宽浪费(尤其是对于shiny server免费用户来说,这种浪费更需要仔细考虑和避免)。更好的方式是这样:

  dataInput <- reactive({getSymbols(input$symb, src = "yahoo",from = input$dates[1],to = input$dates[2],auto.assign = FALSE)})output$plot <- renderPlot({   data <- dataInput()if (input$adjust) data <- adjust(dataInput())chartSeries(data, theme = chartTheme("white"),type = "line", log.scale = input$log, TA = NULL)})

本案例来自shiny入门

通过创建对象值(list)dataInput 来隔离两个计算部分。

当然,更重要的是,通过使用不同的反应式,来更多样的控制app的计算和反应过程。

直呈式反应

表达式的反应主要是最终导向输出或输出的过程值为目的的反应式。主要包括reactiveValues()、render*()和reactive()

reactiveValues()

输出所设定值函数,与此相对的是在UI部分提到的由用户通过控件产生的输出值:input$<inputId> 。

library(shiny)
ui <- fluidPage(
textInput("a","")
)
server <-
function(input,output){
rv <- reactiveValues()
rv$number <- 5
}
shinyApp(ui, server)

render*()

输出运算结果对象。

library(shiny)
ui <- fluidPage(
textInput("a","")
)
server <-
function(input,output){
output$b <-
renderText({
input$a
})
}
shinyApp(ui, server)

reactive()

输出运算过程值,作为模块化编程的重要组成。使用运算结果时,需要用函数的可是来调用。具体来说主要有以下三个功能:

  • 缓存运算值,减少运算;
  • 运算值可被方便多处使用;
  • 调试时能够清晰展现问题点
library(shiny)
ui <- fluidPage(
textInput("a",""),
textInput("z", "")
)
server <-
function(input,output){
re <- reactive({
paste(input$a,input$b})
output$b <- renderText({
re()})
}
shinyApp(ui, server)

控制式反应

控制式反应是在正常的输入-运算-输出之外的反应方式。具体来说包含这三个类型:

isolate()

运行代码,但抑制输出结果,返回一个未反应的结果,从而达到避免依赖性(dependency)的目的。理解isolate(),一般可以对比reactive()。reactive()的反馈是实时的、依赖性的,isolate()则是条件性的、非依赖性的。

可在本地运行以下示例app,对比两类反应的结果:

library(shiny)
ui<-fluidPage(titlePanel("isolate example"),fluidRow(column(4, wellPanel(sliderInput("n", "n (isolated):",min = 10, max = 1000, value = 200, step = 10),          textInput("text", "text (not isolated):", "input text"),br(),actionButton("goButton", "Go!"))),column(8,h4("summary"),textOutput("summary")))
)server <- function(input, output) {output$summary <- renderText({# isolate()一般搭配条件性的触发器使用,其触发器可直接置于其前input$goButton# 此处的对于str的赋值,类同于reactive,都是实时性的str <- paste0('input$text is "', input$text, '"')  # isolate()则抑制以下部分的运算进行,从而起到独立性和隔离作用isolate({str <- paste0(str, ', and input$n is ')paste0(str, isolate(input$n))})})}
shinyApp(ui, server)

参考自:isolate-demo

reactive()、observe()、observeEvent()和eventReactive()对比

observeEvent()和eventReactive()两类都属于控制式反应,与直呈式反应的reactive()和observe()的最直接差异在于:前者是延迟性的反应,其输入值(input value)依赖是部分,通过一定的方式(session)触发;后者则是即时计算的,全局性的依赖于输入值。

关于两个大类中的两个小类则在reactive类的是输出对象值的,observe类的是直接作为环境值输出的。

可在本地运行以下示例app,具体对比四类反应的结果:

library(shiny)ui<-fluidPage(fluidRow(column(3,h2("Reactive Test"),textInput("Test_R","Test_R"),textInput("Test_R2","Test_R2"),textInput("Test_R3","Test_R3"),tableOutput("React_Out")),column(3,h2("Observe Test"),textInput("Test","Test"),textInput("Test2","Test2"),textInput("Test3","Test3"),tableOutput("Observe_Out")),column(3,h2("ObserveEvent Test"),textInput("Test_OE","Test_OE"),textInput("Test_OE2","Test_OE2"),textInput("Test_OE3","Test_OE3"),tableOutput("Observe_Out_E"),actionButton("Go","Test")),column(3,h2("eventReactive Test"),textInput("Test_eR1","Test_eR"),textInput("Test_eR2","Test_eR2"),textInput("Test_eR3","Test_eR3"),tableOutput("eventReac_out"),actionButton("Go_event","Test"))))server<-function(input,output,session){# reactive()和observe()在最终呈现上没有区别,都是随着输出值的实时更新计算输出值的;# 二者的区别在于前者输出的Reactive_Var是全局可用的,而后者输出的df则是环境局限的Reactive_Var<-reactive({c(input$Test_R, input$Test_R2, input$Test_R3)})output$React_Out<-renderTable({Reactive_Var()})observe({A<-input$TestB<-input$Test2C<-input$Test3df<-c(A,B,C)output$Observe_Out<-renderTable({df})})# observeEvent()和eventReactive()同样在最终呈现上没有区别,但在环境调用上存在不同。observeEvent(input$Go, {A<-input$Test_OEB<-input$Test_OE2C<-input$Test_OE3df<-c(A,B,C)output$Observe_Out_E<-renderTable({df})})eventReactive_Var <- eventReactive(input$Go_event, {c(input$Test_eR1, input$Test_eR2, input$Test_eR3)})output$eventReac_out<-renderTable(eventReactive_Var())}
shinyApp(ui, server)

本案例参考自:Advantages of reactive vs. observe vs. observeEvent

案例

接下来以我做的一个案例为例,来说一下大致的思路:

北京地铁月度支出模型

做这个shiny app的初衷是看到这篇文章,里面关于如何在考虑优惠政策的前提下,计算每月在地铁上的花费。为了更直观的了解地铁花费变化情况。

这个app实现前考虑几个要素:

  • 无需输入数据,数据通过函数产生;
  • 涉及到的交互:条件选择按钮,文本输入按钮;
  • 输出形式,可交互式的图表(本案选择plotly实现);
  • 涉及的的数据字段有三个,通过控件和二维图表进行变化

预览如下,具体可在本地运行查看:

library(shiny)
library(ggplot2)
library(plotly)
library(markdown)ui <- fluidPage(# shiny可以调用HTML5静态元素来丰富appUI表现tags$style("label{font-family: TT Times New Roman}"),tags$style("body{font-family:TT Times New Roman}"),titlePanel(HTML("北京地铁月度支出模型 <br/>Beijing Subway monthly Fare Model")),  # 该app里的UI元素不复杂,一个条件控件,一个文本输入控件fluidRow(column(4,radioButtons("radio", label = h4(HTML("X轴选择 <br/> Select X Variable")),choiceNames = c("以天数看花费 \n days as X variable","以单日费用看花费 \n day fare as X variable"),choiceValues = c("dayFare","days"),selected = "days")),column(5,uiOutput("Input"))),# 以及最终的结果呈现,同时,最终结果呈现也可进一步在呈现过程中进行定制化plotlyOutput("distPlot", width=800,height = 400)
)server <- function(input, output) {# 生成数据的函数并不需要每次都进行运算,所以通过isolate()进行隔离,从而减少依赖和运算量isolate({feeInMonth <- function(dayFare, days){fee = dayFare * daysif(fee > 662.5){                                        #662.5 = 100 + 50/0.8 + 250/0.5fee = (fee -262.5)} else if(fee > 162.5 & fee <= 662.5){ #162.5 = 100 + 50/0.8   fee = fee/2+68.75 } else if(fee > 100 & fee <= 162.5){#(fee-162.5)/2+150fee = fee*0.8+20 } else { return(fee)}           #(fee-100)*0.8+100return(fee)  } g <- Vectorize(feeInMonth)}) # 通过条件选择呈现不同的按钮output$Input <- renderUI({if(input$radio == "days"){numericInput("Input", label = h4(HTML('每月使用日数<br/> monthly work days')), value = 22, min = 1, max = 31)}else{numericInput("Input", label = h4(HTML('平均每日花费<br/> average each day fare')), value = 10, min = 3, max = 50)}})# 最终生成结果。此处用plotly嵌套ggplot的对象值,可以说将R的特点最大程度的发挥,对于熟悉R的来说,最方便不过output$distPlot <- renderPlotly({if(input$radio == "dayFare"){p <- ggplot(data.frame(dayFare = c(3,50),days = c(0,31)), aes(x = days)) +stat_function(fun = g,args = c(dayFare = input$Input)) + theme(axis.line = element_line(colour = "darkblue", size = 1.5, linetype = "solid"))+ labs(x = HTML("使用日数\n using days"), y = HTML("费用\ fare"))}if(input$radio == "days"){p <- ggplot(data.frame(dayFare = c(3,50),days = c(0,31)), aes(x = dayFare)) +stat_function(fun = g,args = c(days = input$Input)) + theme(axis.line = element_line(colour = "darkblue",size = 1.5, linetype = "solid"))+labs(x = HTML("平均每日花费\n average each day fare"), y = HTML("费用\ fare"))}gg <- plotly_build(p) %>%  style(line = list(color = 'lightblue',width = 3))            })
}shinyApp(ui = ui, server = server,options = list(height = 900))

shiny app制作基本思路相关推荐

  1. 《iVX 高仿美团APP制作移动端完整项目》02 搜索、搜索提示及类别需求分析思路及制作流程

    点击整个专栏查看其它系列文章 (系列文章更新中-):<iVX 高仿美团APP制作移动端完整项目> 项目界面预览: 一.搜索制作 在上一节中我们完成了标题头的制作,接下来我们查看如何制作搜索 ...

  2. 《iVX 高仿美团APP制作移动端完整项目》01 标题需求分析思路及制作流程

    点击整个专栏查看其它系列文章 (系列文章更新中-):<iVX 高仿美团APP制作移动端完整项目> 项目界面预览: 一.创建项目 首先打开在线编辑器地址:https://editor.ivx ...

  3. 利用Nodemcu+Arduino nano+TB6612+点灯科技APP制作简易麦克纳姆轮Wi-Fi遥控小车

    摘要 麦克纳姆轮小车由于车轮本身的特殊结构,可以实现全向行驶,可玩性非常强.麦克纳姆轮原理在这里不做展开,麦克纳姆小车主要是通过控制四个轮胎的转与不转以及转动的方向来实现多方向的运动,其中一种X型车轮 ...

  4. Shiny平台构建与R包开发(七)——Shiny APP部署

    本节展示了如何分享和部署Shiny APP.您可以将开发好的Shiny APP部署在自己的服务器上,或是将其部署在公共的平台(即shinyapps.io)上.这里仅分享后者.对于如何将Shiny AP ...

  5. 手机app软件测试教程,手机app制作软件测试app的技巧和方式(上)

    测试(Testing)是写程序很重要的过程之一.当手机app制作软件一步步完成,过程中你需要不断的测试,随时掌握画面.写出来的功能是否在交到使用者手上时是正常运作.而Thunkable平台上提供了什么 ...

  6. 《iVX 高仿美团APP制作移动端完整项目》03 推介信息及推荐商家分析及制作

    点击整个专栏查看其它系列文章 (系列文章更新中-):<iVX 高仿美团APP制作移动端完整项目> 项目界面预览: 一.推荐信息制作 推荐信息与之前的标题下推荐信息制作类似: 此时依旧创建一 ...

  7. vc6开发一个抓包软件_开发一个软件多少钱?传统app开发与0代码app制作方法对比...

    开发一个软件多少钱?app开发难吗?app制作需要哪些流程? app开发很难:按照传统的开发方式需要最少5名以上的技术人员,团队配合花费3个月左右的时间才能搞定,成本20万以上. app开发也很简单: ...

  8. android 登录界面开源代码_【程序源代码】一个安卓查询类app制作的开源项目

    " 关键字:工作流 框架 springboot" 正文:一个学习安卓查询类app制作的开源项目.可以用来联系查询类app的编写. 01 - android studio最近势头好猛 ...

  9. 简单易用的APP制作软件,KM盒子V6.3版发布

    KM盒子是一款支持文字.表格.图片.音视频混合排版的手机APP制作软件.软件操作简单无需学习专业的手机编程知识,使用图片.文档或网页等方式即可快速制作生成手机APP应用. KM盒子V6.3版下载网址: ...

  10. 制作好的app需要服务器吗,在直播app制作过程中,服务器是如何配置的?

    不论是一对多直播还是一对一直播app制作,关于服务器的配置和成本是大多数运营商比较关心和头疼的问题.一般来说,在直播app运营的每个阶段,所安排的服务器台数和负责的功能都是不一样的.那么如何在有限的成 ...

最新文章

  1. rhcs做HA时的资源释放脚本实现
  2. package.json中dependencies 与devDependencies 的区别
  3. Myeclipse的使用方法-添加,修改,删除JRE,修改项目中的jre不显示问题
  4. 服务器性能估算参考(硬件-应用服务器)
  5. react-native 显示html,react-native-webview加载本地H5
  6. 【设计模式】第六章 观察者模式
  7. html获取边缘元素,JQuery 获取元素到浏览器可视窗口边缘的距离
  8. Android笔记:触摸事件的分析与总结----TouchEvent处理机制
  9. 空军预警学院计算机与网络,空军预警学院学报
  10. 已经通过CMMI/CMM3级评估的企业名单
  11. Android适配64位TBS X5内核
  12. winform DevComponents.DotNetBar2 添加到工具栏方法
  13. linux服务器安装字体,删除字体,详细步骤
  14. jira后台统计数据的一些sql(包含reopen计算,时效)
  15. Android Lint代码检查实践
  16. matlab在生物学中的应用,MATLAB在生物医学信号处理中的应用
  17. usage.txt-1
  18. 转:2013年各大小IT公司待遇,绝对真实,一线数据!
  19. 2021年材料员-通用基础(材料员)考试试题及材料员-通用基础(材料员)作业模拟考试
  20. 全球与中国菱镁矿和水镁石市场现状及未来发展趋势

热门文章

  1. 线性代数-MIT 18.06-汇总
  2. 数学建模论文排版(摘要部分)
  3. 计算机职业核心素养,计算机专业人才核心素养研究-计算机专业论文-计算机论文(10页)-原创力文档...
  4. aspectjweaver.jar 下载地址
  5. [组图]手机病毒组合拳一箭双雕 手机PC均中招(转)
  6. 自适应权重的交叉熵计算
  7. 网页游戏开发基础——网页基础知识
  8. 开机netmeeting已删除_NetMeeting
  9. Git问题解决方案:Pulling without specifying how to reconcile divergent branches
  10. 3dsmax动画六、骨骼调整及蒙皮。