2 Basic UI
2.1 Introduction
现在您已经掌握了一个基本的 app,我们可以开始探索让 Shiny 发挥作用的细节。 正如您在上一章中看到的,Shiny 鼓励将生成用户界面(前端)的代码与驱动 app 行为的代码(后端)分离。
在本章中,我们将重点关注前端,并带您快速了解 Shiny 提供的 HTML 输入和输出。 这使您能够捕获多种类型的数据并显示多种类型的 R 输出。 你还没有很多方法将输入和输出拼接在一起,但我们将在 Chapter ?? 中回到这一点。
在这里,我将主要坚持 Shiny 本身内置的输入和输出。 然而,有一个丰富且充满活力的扩展包社区,例如 shinyWidgets、colorpicker、和 sorttable。 您可以在 https://github.com/nanxstats/awesome-shiny-extensions 找到全面的、积极维护的其他软件包列表,由 Nan Xiao 维护。
像往常一样,我们将首先加载 shiny 包:
2.2 Inputs
正如我们在上一章中看到的,您可以使用 sliderInput()
、selectInput()
、textInput()
和 numericInput()
等函数将输入控件插入到 UI 规范中。
现在我们将讨论所有输入函数的通用结构,并快速概述 Shiny 中内置的输入。
2.2.1 Common structure
所有输入函数都有相同的第一个参数:inputId
。
这是用于连接前端和后端的标识符:如果您的 UI 有一个 ID 为 "name"
的输入,server 函数将使用 input$name
访问它。
inputId
有两个约束:
它必须是一个仅包含字母、数字和下划线的简单字符串(不允许包含空格、短划线、句点或其他特殊字符!)。 命名它就像在 R 中命名变量一样。
它必须是独一无二的。 如果它不是唯一的,您将无法在 server 函数中引用此控件!
大多数输入函数都有第二个参数,称为 label
。
这用于为控件创建人类可读的标签。
Shiny 不会对此字符串施加任何限制,但您需要仔细考虑它以确保您的 app 可供人类使用!
第三个参数通常是 value
,在可能的情况下,让您设置默认值。
其余参数对于该控件来说是唯一的。
创建输入时,我建议按位置提供 inputId
和 label
参数,并按名称提供所有其他参数:
sliderInput("min", "Limit (minimum)", value = 50, min = 0, max = 100)
以下部分描述了 Shiny 中内置的输入,根据它们创建的控件类型松散地分组。 目的是让您快速了解您的选择,而不是详尽地描述所有参数。 我将在下面显示每个控件最重要的参数,但您需要阅读文档才能获取完整的详细信息。
2.2.2 Free text
使用 textInput()
收集少量文本,使用 passwordInput()
3 收集密码,使用 textAreaInput()
收集文本段落。
ui <- fluidPage(
textInput("name", "What's your name?"),
passwordInput("password", "What's your password?"),
textAreaInput("story", "Tell me about yourself", rows = 3)
)

如果你想确保文本具有某些属性,你可以使用 validate()
,我们将在 Chapter ?? 中讨论这一点。
2.2.3 Numeric inputs
要收集数值,请使用 numericInput()
创建受约束的文本框或使用 sliderInput()
创建滑块。
如果为 sliderInput()
的默认值提供长度为 2 的数值向量,您将得到一个有两端的“范围”滑块。
ui <- fluidPage(
numericInput("num", "Number one", value = 0, min = 0, max = 100),
sliderInput("num2", "Number two", value = 50, min = 0, max = 100),
sliderInput("rng", "Range", value = c(10, 20), min = 0, max = 100)
)

一般来说,我建议仅在小范围或精确值不太重要的情况下使用滑块。 尝试在小滑块上精确选择数字是一项令人沮丧的练习!
滑块是高度可定制的,并且有很多方法可以调整其外观。有关更多详细信息,请参阅 ?sliderInput
和 https://shiny.rstudio.com/articles/sliders.html。
2.2.4 Dates
使用 dateInput()
收集单个日期,或使用 dateRangeInput()
收集两个日期的范围。
它们提供了一个方便的日历选择器,并且像 datesdisabled
和 daysofweekdisabled
这样的附加参数允许您限制有效输入的集合。
ui <- fluidPage(
dateInput("dob", "When were you born?"),
dateRangeInput("holiday", "When do you want to go on vacation next?")
)

日期格式、语言和一周开始日期默认为美国标准。
如果您要创建面向国际受众的应用程序,请设置 format
、language
、和 weekstart
,以便日期对您的用户来说是自然的。
2.2.5 Limited choices
有两种不同的方法允许用户从一组预先指定的选项中进行选择:selectInput()
和 radioButtons()
。
animals <- c("dog", "cat", "mouse", "bird", "other", "I hate animals")
ui <- fluidPage(
selectInput("state", "What's your favourite state?", state.name),
radioButtons("animal", "What's your favourite animal?", animals)
)

单选按钮有两个很好的功能:它们显示所有可能的选项,使其适合短列表,并且通过 choiceNames
/choiceValues
参数,它们可以显示纯文本以外的选项。
choiceNames
决定向用户显示的内容;choiceValues
决定 server 函数中返回的内容。
ui <- fluidPage(
radioButtons("rb", "Choose one:",
choiceNames = list(
icon("angry"),
icon("smile"),
icon("sad-tear")
),
choiceValues = list("angry", "happy", "sad")
)
)

无论选项数量多少,使用 selectInput()
创建的下拉菜单都会占用相同的空间,这使得它们更适合较长的选项。
您还可以设置 multiple = TRUE
以允许用户选择多个元素。
ui <- fluidPage(
selectInput(
"state", "What's your favourite state?", state.name,
multiple = TRUE
)
)

如果您有大量可能的选项,您可能需要使用“服务器端” selectInput()
,以便完整的可能选项集不会嵌入到 UI 中(这会导致加载速度变慢),而是由 server 根据需要发送。
您可以在 https://shiny.rstudio.com/articles/selectize.html#server-side-selectize 了解有关此高级主题的更多信息。
无法使用单选按钮选择多个值,但有一个概念上类似的替代方案:checkboxGroupInput()
。
ui <- fluidPage(
checkboxGroupInput("animal", "What animals do you like?", animals)
)

如果您想要单个复选框用于单个是/否问题,请使用 checkboxInput()
:
ui <- fluidPage(
checkboxInput("cleanup", "Clean up?", value = TRUE),
checkboxInput("shutdown", "Shutdown?")
)

2.2.8 Exercises
-
当空间非常宝贵时,使用出现在文本输入区域内的占位符来标记文本框非常有用。 如何调用
textInput()
来生成下面的 UI? -
仔细阅读
sliderInput()
的文档,了解如何创建日期滑块,如下所示。 创建一个滑块输入以选择 0 到 100 之间的值,其中滑块上每个可选值之间的间隔为 5。 然后,向 input widget 添加动画,以便当用户按下“播放”时,input widget 会自动滚动该范围。
如果
selectInput()
中有一个相当长的列表,那么创建将列表分成几部分的子标题会很有用。 阅读文档以了解如何操作。 (提示:底层 HTML 称为<optgroup>
。)
2.3 Outputs
UI 中的输出创建占位符,稍后由 server 函数填充。
与输入一样,输出采用唯一的 ID 作为其第一个参数4:如果您的 UI 规范创建 ID 为 "plot"
的输出,您将在 server 函数中使用 output$plot
访问它。
前端的每个 output
函数都与后端的 render
函数耦合。
输出主要有三种类型,对应于报告中通常包含的三种内容:文本、表格和图表。
以下部分向您展示前端输出函数的基础知识,以及后端相应的 render
函数。
2.3.1 Text
使用 textOutput()
输出常规文本,使用 verbatimTextOutput()
输出固定代码和控制台输出。
ui <- fluidPage(
textOutput("text"),
verbatimTextOutput("code")
)
server <- function(input, output, session) {
output$text <- renderText({
"Hello friend!"
})
output$code <- renderPrint({
summary(1:10)
})
}

请注意,仅当需要运行多行代码时,render 函数中才需要 {}
。
正如您很快就会了解到的,您应该在 render 函数中进行尽可能少的计算,这意味着您通常可以忽略它们。
如果写得更紧凑的话,上面的 server 函数会是这样的:
server <- function(input, output, session) {
output$text <- renderText("Hello friend!")
output$code <- renderPrint(summary(1:10))
}
请注意,有两个 render 函数的行为略有不同:
-
renderText()
将结果组合成单个字符串,通常与textOutput()
配对 -
renderPrint()
打印结果,就像在 R console 中一样,并且通常与verbatimTextOutput()
配对。
我们可以看到与 toy app 的区别:
ui <- fluidPage(
textOutput("text"),
verbatimTextOutput("print")
)
server <- function(input, output, session) {
output$text <- renderText("hello!")
output$print <- renderPrint("hello!")
}

2.3.2 Tables
有两种用于在表格中显示 data frames 的选项:
tableOutput()
和renderTable()
渲染静态数据表,一次性显示所有数据。dataTableOutput()
和renderDataTable()
呈现一个动态表,显示固定数量的行以及用于更改哪些行可见的控件。
tableOutput()
对于小型固定摘要最有用(例如模型系数);如果您想向用户公开完整的 data frame,则 dataTableOutput()
最合适。
如果您想更好地控制 dataTableOutput()
的输出,我强烈推荐 Greg Lin 的 reactable 包。
ui <- fluidPage(
tableOutput("static"),
dataTableOutput("dynamic")
)
server <- function(input, output, session) {
output$static <- renderTable(head(mtcars))
output$dynamic <- renderDataTable(mtcars, options = list(pageLength = 5))
}

2.3.3 Plots
您可以使用 plotOutput()
和 renderPlot()
显示任何类型的 R 图形(base、ggplot2 或其他):
ui <- fluidPage(
plotOutput("plot", width = "400px")
)
server <- function(input, output, session) {
output$plot <- renderPlot(plot(1:5), res = 96)
}

默认情况下,plotOutput()
将占据其容器的整个宽度(稍后会详细介绍),高度为 400 像素。
您可以使用 height
和 width
参数覆盖这些默认值。
我们建议始终设置 res = 96
,因为这将使您的 Shiny plots 尽可能匹配您在 RStudio 中看到的内容。
Plots 很特殊,因为它们是输出,也可以充当输入。
plotOutput()
有许多参数,例如 click
、dblclick
和 hover
。
如果您向它们传递一个字符串,例如 click = "plot_click"
,它们将创建一个反应性输入 (input$plot_click
),您可以使用它来处理绘图上的用户交互,例如单击绘图。
我们将在 Chapter ?? 中回到 Shiny 中的 interactive plots。
2.3.4 Downloads
您可以使用 downloadButton()
或 downloadLink()
让用户下载文件。
这些需要 server 函数中的新技术,所以我们将在 Chapter ?? 中回到这一点。
2.3.5 Exercises
-
以下每个 render 函数应该与
textOutput()
和verbatimTextOutput()
中的哪一个配对?renderPrint(summary(mtcars))
renderText("Good morning!")
renderPrint(t.test(1:5, 2:6))
renderText(str(lm(mpg ~ wt, data = mtcars)))
重新创建 Section 2.3.3 中的 Shiny app,这次将高度设置为 300px,宽度设置为 700px。 设置绘图 “alt” 文本,以便视障用户可以看出它是五个随机数的散点图。
-
更新下面对
renderDataTable()
的调用中的选项,以便显示数据,但抑制所有其他控件(即删除搜索、排序和过滤命令)。 您需要阅读?renderDataTable
并查看 https://datatables.net/reference/option/ 中的选项。ui <- fluidPage( dataTableOutput("table") ) server <- function(input, output, session) { output$table <- renderDataTable(mtcars, options = list(pageLength = 5)) }
或者,阅读有关 reactable 的内容,并将上述 app 转换为使用它。