本节为Shiny平台构建与R包开发教程的第一小节。
Getting Started
初识Shiny时,了解其工作机理非常重要。下面的案例展示了一个最简单的Shiny APP的工作机理:
#DO NOT include any non-English characters in Shiny script
library(shiny)
ui <- fluidPage("Hello World!")
server <- function(input, output){}
shinyApp(ui, server)
其运行方式和其他R语言代码没有太大的差别。直接让光标定位在脚本文件中,单击Run或按快捷键Ctrl+Enter将代码逐行运行。 ui
变量定义了Shiny APP的ui设计,server
函数定义了Shiny APP的服务器行为。input
参数代表用户向服务器中输入的数据,output
代表服务器向用户展示的内容。
当运行shinyApp
函数时,Shiny调用图形用户界面(GUI),打开网页。只要网页不被关闭,shinyApp
就一直处于运行状态,用户也就无法在Rstudio的命令行窗口运行其他命令。
不要马上急着关闭弹出的GUI。点击“Open in Browser”按钮,按下“(Fn)+F12”按钮,进入浏览器调试模式。如果您了解过HTML+CSS+JS的基本知识,请耐心阅读Box1部分内容,以深刻地理解Shiny是如何生成HTML网页的。这对后续ui排版及CSS设计的深入理解有很好地辅助作用。
Box1 Shiny生成HTML网页的内在机制
进入浏览器调试模式后,打开<head>
标签,可以找到<link>
引入了bootstrap的css文件,表明Shiny的CSS是基于bootstrap开发的。展开<body>
标签,发现类名为container-fluid
的一个<div>
标签。这表明Shiny中fluidPage
函数的功能是向网页中插入一个<div class="container-fluid"></div>
。而这一标签的具体样式显然在CSS文件中已被定义了。
退出GUI界面,回到R终端,运行print(ui)
,同样也可以看到ui
变量本质上就是个<div>
。再来看一个更复杂的例子。ui <- fluidPage( titlePanel("title Panel"), sidebarLayout( sidebarPanel("sidebar Panel"), mainPanel("main Panel") ) ) server <- function(input, output){} shinyApp(ui, server)
先运行
print(ui)
,发现在fluidPage
函数构建的<div class="container-fluid">
的基础上,titlePanel
函数在内部插入了<h2>
标签;siderbarLayout
函数插入了<div class="row">
标签;sidebarPanel
和mainPanel
函数分别在row
标签的div区块中分别插入<div class="col-sm-4">
和<div class="col-sm-8" role="main">
。除此之外,很重要的一点是titlePanel
还在网页的<head>
标签中插入了<title>
标签,即<title>title Panel</title>
。因此,对应网页的标签也自动变成了title Panel了。
总之,ui变量在R中存储的仅仅是字符串,但其内部字符的语法遵从HTML语言,在网页端根据HTML语法翻译成对应元素。此后的ui设计案例也可以用同样的方法查看每个函数的作用。
元素布局
最常用的网页内元素布局是将页面划分为不同的“行”和“列”。以Bing为例,若想要“复刻”一个Bing的首页,一种方法是:将网页划分为6行,每一行又可以分为不同数量的列。每一“行”是一个<div class="row">
,每一列是嵌套在“行”<div>
里面的<div class="column">
。因此,在Bing首页中,第一行需要设置6个“列”<div>
,第一个<div>
放Microsoft的logo以及文字“Microsoft Bing”,一直到最后一个<div>
放一个功能选项按钮。第二行只需要一个“列”<div>
,并且是空白的。最后一行也只有一个“列”<div>
,放置许多版权信息和链接。
图1 ui布局—以Bing为例
这一布局落实到Shiny中就变成:
ui <- fluidPage(
fluidRow(column(),column(),column(),column(),column(),column()),
fluidRow(column()),
fluidRow(column()),
fluidRow(column()),
fluidRow(column(),column(),column(),column()),
fluidRow(column())
)
其中每个column
函数填上对应的HTML元素,在R中以遵循HTML语法的字符串形式表示。但问题是,每个“列”<div>
的宽度怎么定义?column
函数中有width
参数,其取值在0~12间,其中12代表与电脑显示屏的宽度相等。若取值为4,相当于这个column
产生的区域为电脑显示屏宽度的4/12=1/3。
关于更多的ui页面内布局方式,可以参考以下博客:
https://blog.csdn.net/weixin_27744023/article/details/112742221
之前演示的sidebarLayout
在这篇博客中定义为“左右布局”,同样也可以用width参数控制side panel和main panel的宽度;这篇博客中涉及的“分页布局”在实际Shiny APP中也应用较多。如果您还不知道Shiny中server变量定义的语法,您可以将这篇博客的案例代码中将与ui布局无关的函数全部去掉,替换成普通字符串,以理解每个案例的运作机理。
页面布局
无论是bibliometrix
包还是Expmeasure
包,其Shiny GUI页面顶部都有导航栏(navigation bar)。但fluidPage
函数并不能实现这一功能。
R Shiny中最常用的两种页面布局函数是fluidPage
(静态页面)和navbarPage
(导航页面)。下面的案例非常典型,介绍了如何搭配使用这两个函数构造整个Shiny数据分析平台的ui。
ui.overall <- navbarPage("expmeasures",
tabPanel("Home", ui.home),
tabPanel("Data", ui.data),
navbarMenu("Trend",
tabPanel("2D Plot", ui.trend.2D_Plot),
tabPanel("3D Plot", ui.trend.3D_Plot),
tabPanel("VioPlot", ui.trend.VioPlot)
),
navbarMenu("Error",
tabPanel("Outlier", ui.error.Outlier),
tabPanel("Leverage", ui.error.Leverage),
tabPanel("Influence", ui.error.Influence)
),
navbarMenu("Significance",
tabPanel("K-function", ui.significance.k),
tabPanel("GLMs", ui.significance.GLMs),
tabPanel("Residue", ui.significance.residue),
tabPanel("Jackknife", ui.significance.jackknife)
),
navbarMenu("Explantory",
tabPanel("ANOVA", ui.explantory.ANOVA),
tabPanel("Muti Comp", ui.explantory.muticomp)
),
navbarMenu("Prediction",
tabPanel("Trend", ui.prediction.trend),
tabPanel("Spline", ui.prediction.spline),
tabPanel("Inverse", ui.prediction.inverse),
tabPanel("Simp Mov", ui.prediction.simmov),
tabPanel("Krigging", ui.prediction.krigging)
),
navbarMenu("Uncertainty",
tabPanel("TNE", ui.uncertainty.TNE),
tabPanel("T/E Simu", ui.uncertainty.TNEsimu)
),
tabPanel("Quit")
)
您可以运行expmeasure
函数,观察Expmeasure
包导航栏结构与上述代码的对应关系。 navbarPage
函数的第一个参数在导航栏的左侧显示; tabPanel
函数构造了导航栏中的一个模块,鼠标点击模块时会跳转到对应页面(对应页面的ui被tabPanel
函数的第二个参数指定); navbarMenu
函数同样也构造导航栏中的一个模块,但鼠标点击该模块时会出现下拉菜单,显示若干子模块。每个子模块又由tabPanel
函数构造,tabPanel
函数的第二个参数定义了对应页面的ui。
那么,所谓对应页面的ui又应该怎么定义?这里以Expmeasure
的HOME页面为例:
ui.home <- fluidPage(
sidebarPanel(
fluidRow(
column(12, align = "center", imageOutput('igem_icon', height = "250px"))
),
fluidRow(
column(12, align = "center", h1("Expmeasure: A tool for part characterization"))
),
fluidRow(
column(12, align = "center", h2("Developed by ZJU-China, 2021"))
),
width = 12)
)
可见,每个对应页面的ui就是一个fluidPage
。至于在fluidPage
中要填什么元素,就是“元素布局”应该要考虑的事情。这里HOME页面只放一个Side Bar,让它的宽度占满整个电脑屏幕(width=12
)。在Side Bar中放置三行,第一行放一个图片,第二行放一行h1标题文字,第三行放一行h2标题文字。column
函数的第一个参数就是width
参数,注意这里width=12
就不是占满整个电脑屏幕,而是占满整个Side Bar的宽度,因为fluidRow
被嵌套在了sidebarPanel
里面(如果学过HTML,我们知道嵌在里面的一个div的长宽不会超过它外面的div)。
至此,您已经通过本教程了解到如何设计网页布局,并能设计导航页面和静态页面,在静态页面中插入标题、文字。但您可能马上就有欲望在页面中插入图片,因为这会使网页更好看。
插入图片
事实上,上述代码块已经初步展示了在Shiny页面中插入图片的语法,下面展示完整代码:
ui <- fluidPage(
sidebarPanel(
fluidRow(
column(12, align = "center", imageOutput('igem_icon', height = "250px"))
),
fluidRow(
column(12, align = "center", h1("Expmeasure: A tool for part characterization"))
),
fluidRow(
column(12, align = "center", h2("Developed by ZJU-China, 2021"))
),
width = 12)
)
server <- function(input, output){
output$igem_icon <- renderImage({
list(src = './expmeasure/R/logo.png')
} , deleteFile = FALSE)
}
shinyApp(ui, server)
上述代码的ui部分中,核心部分是imageOutput
函数。该函数的第一个参数指定了图像输出区域的编号。这里编号为"igem_icon"。在server变量中,由"output$igem_icon"识别相应编号,并利用renderImage
函数在该区域输出本地路径为’./expmeasure/R/logo.png’的图片。
关于数据输出(包括table、plot、text等)的详细机理将在第四节介绍,图片输出只是服务器输出数据的一种形式。了解图片输出方式后,其他的数据输出形式十分类似。例如输出table只需将 imageOutput
– renderImage
函数组合替换成 tableOutput
– renderTable
即可。