2 Basic UI

2.1 Introduction

现在您已经掌握了一个基本的 app,我们可以开始探索让 Shiny 发挥作用的细节。 正如您在上一章中看到的,Shiny 鼓励将生成用户界面(前端)的代码与驱动 app 行为的代码(后端)分离。

在本章中,我们将重点关注前端,并带您快速了解 Shiny 提供的 HTML 输入和输出。 这使您能够捕获多种类型的数据并显示多种类型的 R 输出。 你还没有很多方法将输入和输出拼接在一起,但我们将在 Chapter ?? 中回到这一点。

在这里,我将主要坚持 Shiny 本身内置的输入和输出。 然而,有一个丰富且充满活力的扩展包社区,例如 shinyWidgetscolorpicker、和 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,在可能的情况下,让您设置默认值。 其余参数对于该控件来说是唯一的。

创建输入时,我建议按位置提供 inputIdlabel 参数,并按名称提供所有其他参数:

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)
)

一般来说,我建议仅在小范围或精确值不太重要的情况下使用滑块。 尝试在小滑块上精确选择数字是一项令人沮丧的练习!

滑块是高度可定制的,并且有很多方法可以调整其外观。有关更多详细信息,请参阅 ?sliderInputhttps://shiny.rstudio.com/articles/sliders.html

2.2.4 Dates

使用 dateInput() 收集单个日期,或使用 dateRangeInput() 收集两个日期的范围。 它们提供了一个方便的日历选择器,并且像 datesdisableddaysofweekdisabled 这样的附加参数允许您限制有效输入的集合。

ui <- fluidPage(
  dateInput("dob", "When were you born?"),
  dateRangeInput("holiday", "When do you want to go on vacation next?")
)

日期格式、语言和一周开始日期默认为美国标准。 如果您要创建面向国际受众的应用程序,请设置 formatlanguage、和 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.6 File uploads

通过 fileInput() 允许用户使上传文件:

ui <- fluidPage(
  fileInput("upload", NULL)
)

fileInput() 需要在服务器端进行特殊处理,在 Chapter ?? 中详细讨论。

2.2.7 Action buttons

使用 actionButton()actionLink() 让用户执行操作:

ui <- fluidPage(
  actionButton("click", "Click me!"),
  actionButton("drink", "Drink me!", icon = icon("cocktail"))
)

操作链接和按钮最自然地与 server 函数中的 observeEvent()eventReactive() 配对。 您还没有了解这些重要的功能,但我们将在 Section 3.5 中回顾它们。

您可以使用 class 参数通过使用 "btn-primary""btn-success""btn-info""btn-warning""btn-danger" 其中的一个来自定义外观。 您还可以使用 "btn-lg""btn-sm""btn-xs" 更改大小。 最后,您可以使用 "btn-block" 使按钮跨越它们嵌入的元素的整个宽度。

ui <- fluidPage(
  fluidRow(
    actionButton("click", "Click me!", class = "btn-danger"),
    actionButton("drink", "Drink me!", class = "btn-lg btn-success")
  ),
  fluidRow(
    actionButton("eat", "Eat me!", class = "btn-block")
  )
)

class 参数通过设置底层 HTML 的 class 属性来工作,这会影响元素的样式。 要查看其他选项,您可以阅读 Bootstrap(Shiny 使用的 CSS 设计系统)的文档:<http://bootstrapdocs.com/v3.3.6/docs/css/#buttons>

2.2.8 Exercises

  1. 当空间非常宝贵时,使用出现在文本输入区域内的占位符来标记文本框非常有用。 如何调用 textInput() 来生成下面的 UI?

  2. 仔细阅读 sliderInput() 的文档,了解如何创建日期滑块,如下所示。

  3. 创建一个滑块输入以选择 0 到 100 之间的值,其中滑块上每个可选值之间的间隔为 5。 然后,向 input widget 添加动画,以便当用户按下“播放”时,input widget 会自动滚动该范围。

  4. 如果 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 函数的行为略有不同:

我们可以看到与 toy app 的区别:

ui <- fluidPage(
  textOutput("text"),
  verbatimTextOutput("print")
)
server <- function(input, output, session) {
  output$text <- renderText("hello!")
  output$print <- renderPrint("hello!")
}

这相当于 R 语言中 cat()print() 的区别。

2.3.2 Tables

有两种用于在表格中显示 data frames 的选项:

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 像素。 您可以使用 heightwidth 参数覆盖这些默认值。 我们建议始终设置 res = 96,因为这将使您的 Shiny plots 尽可能匹配您在 RStudio 中看到的内容。

Plots 很特殊,因为它们是输出,也可以充当输入。 plotOutput() 有许多参数,例如 clickdblclickhover。 如果您向它们传递一个字符串,例如 click = "plot_click",它们将创建一个反应性输入 (input$plot_click),您可以使用它来处理绘图上的用户交互,例如单击绘图。 我们将在 Chapter ?? 中回到 Shiny 中的 interactive plots。

2.3.4 Downloads

您可以使用 downloadButton()downloadLink() 让用户下载文件。 这些需要 server 函数中的新技术,所以我们将在 Chapter ?? 中回到这一点。

2.3.5 Exercises

  1. 以下每个 render 函数应该与 textOutput()verbatimTextOutput() 中的哪一个配对?

    1. renderPrint(summary(mtcars))

    2. renderText("Good morning!")

    3. renderPrint(t.test(1:5, 2:6))

    4. renderText(str(lm(mpg ~ wt, data = mtcars)))

  2. 重新创建 Section 2.3.3 中的 Shiny app,这次将高度设置为 300px,宽度设置为 700px。 设置绘图 “alt” 文本,以便视障用户可以看出它是五个随机数的散点图。

  3. 更新下面对 renderDataTable() 的调用中的选项,以便显示数据,但抑制所有其他控件(即删除搜索、排序和过滤命令)。 您需要阅读 ?renderDataTable 并查看 https://datatables.net/reference/option/ 中的选项。

    ui <- fluidPage(
      dataTableOutput("table")
    )
    server <- function(input, output, session) {
      output$table <- renderDataTable(mtcars, options = list(pageLength = 5))
    }
  4. 或者,阅读有关 reactable 的内容,并将上述 app 转换为使用它。

2.4 Summary

本章向您介绍了构成 Shiny app 前端的主要输入和输出函数。 这是一个很大的信息转储,所以不要指望在一次阅读后记住所有内容。 相反,当您正在寻找特定组件时,请返回本章:您可以快速浏览图形,然后找到您需要的代码。

在下一章中,我们将继续讨论 Shiny app 的后端:使用户界面栩栩如生的 R 代码。