2.4 Quarto 动态仪表盘 · Dynamic Quarto Dashboards
2.4 Quarto 动态仪表盘 · Dynamic Quarto Dashboards
本章目标
完成本章后,你能够:
- locate 在交互光谱(静态 → 参数化 → OJS → Shiny)上为需求定位
- embed 用
server: shiny/server: shinylive把 Shiny 嵌入 Quarto 仪表盘 - wire 用侧边栏输入(
selectInput/sliderInput)驱动响应式输出 - predict 画响应式图(reactive graph),预测输入变化时哪些输出失效
- choose 按数据敏感度与运维条件在 shinylive / shinyapps.io / Connect 中选型
前置自测(≤5 分钟)
能独立完成以下两问再继续;否则先回补 2.3:
- 能不看讲义搭出 2.3 的两页仪表盘(页面 / 行 / value box)。
install.packages("shiny")已成功;听过「input 只能在 server 端读」 吗?没听过没关系,§3 就是讲它。
1. 交互光谱:从静态到 Shiny
交互是仪表盘里最贵的特性:写它、跑它、托管它都要成本。 想加交互前先问:「把 2.3 的静态板每周定时重渲染,能不能解决 90% 的 问题?」多数时候能。剩下的 10%,才值得往下走这条光谱。
| 档位 | 做法 | 读者得到 | 服务器 |
|---|---|---|---|
| 静态 | quarto render 一次 |
固定快照 | 任何静态主机 |
| 参数化 | params: + quarto render -P class:"suv" |
预制变体 | 仍可静态托管 |
| 前端交互 | OJS / htmlwidgets | 浏览器内即时筛选 | 无需服务器 |
| Shiny | server: shiny |
任意筛选、服务端计算 | 需要能跑 R 的地方 |
对应的四档部署模式(源自工作坊讲义):静态(static)、定时(scheduled)、 参数化(parameterized)、交互(interactive)——前三档本质是「渲染时机」 问题,只有第四档需要真正的运行时。
2. 把 Shiny 嵌进 Quarto:server: shiny
Quarto 仪表盘 + Shiny 的全部门槛是一行 YAML:server: shiny。此后文档里出现两类角色:
- UI 块(默认):放
selectInput()这类输入控件,渲染进页面 - server 块(
#| context: server):读input$xx、执行计算、返回render*()输出
server: shiny 的产物不是静态 HTML——双击打开只有空壳。 开发时用 Positron/RStudio 的 Render 预览(背后起了 Shiny 进程); 交付时必须托管到能跑 R 的服务器,或改用 shinylive(§6 决策表)。
变体 server: shinylive:应用编译成 WebAssembly,在读者浏览器里 跑 R(webR),可静态托管——代价与边界见 §6。
3. 侧边栏输入驱动响应式输出
完整可跑的最小动态仪表盘(文件 penguins-live.qmd):
---
title: "企鹅交互速览"
format: dashboard
server: shiny
---
```r
#| context: setup
library(shiny)
library(ggplot2)
library(dplyr)
penguins <- palmerpenguins::penguins
```
## 筛选 {.sidebar}
```r
selectInput("species", "物种",
choices = c("Adelie", "Gentoo", "Chinstrap"), multiple = TRUE)
sliderInput("mass", "体重区间 (g)",
min = 2500, max = 6300, value = c(2500, 6300))
```
## 图表
```r
#| context: server
selected <- reactive({
penguins |>
filter(species %in% input$species,
between(body_mass_g, input$mass[1], input$mass[2]))
})
```
```r
#| context: server
#| title: 嘴峰 × 体重
renderPlot({
ggplot(selected(),
aes(bill_length_mm, body_mass_g, color = species)) +
geom_point()
})
```
```r
#| context: server
#| title: 明细表
renderDataTable(
selected()[, c("species", "island",
"bill_length_mm", "body_mass_g")],
options = list(pageLength = 8))
```.sidebar 把一行变成左侧筛选栏;context: setup 只跑一次;server 块里 裸调用 renderPlot() / renderDataTable(),Quarto 自动在块的位置放好 输出卡片,#| title: 照常命名。
input$species 只在 context: server 块里存在,在普通块里引用 直接报「找不到对象 input」。口诀:摆控件的块不读值,读值的块不放控件。
4. 响应式心智模型:图与失效
Shiny 的全部行为可以画成一张响应式图(reactive graph): 输入是源,reactive() 是中继站,render*() 是终点。输入一变, 下游全部失效(invalidation)、排队重算;上游不受影响。
flowchart LR
A["selectInput<br/>input$species"] --> D["reactive({ filter })"]
B["sliderInput<br/>input$mass"] --> D
D --> P["renderPlot"]
D --> T["renderDataTable"]
对 §3 的例子:拖动体重滑块 → selected 失效 → 图和表一起重算;但输入 控件本身、context: setup 的数据加载都不会重跑——这就是「只算必要的」的全部秘密。
把 §3 的 sliderInput 换成对 bill_length_mm 的区间筛选: ① 响应式图怎么改?画出来;② 哪些块会重算、哪些不会? 先写下预测,再改代码实测对照——预测错的地方就是你模型的缺口。
这里只装「够用的一升」:图、失效、只算必要的。eventReactive、定时器、模块化与调试,2.7 章整章展开。
5. 无服务器交互:OJS 备选
读者只想在浏览器里筛一筛、看一看,不需要服务端统计量时,Observable JS (OJS)是零服务器方案:渲染产物就是静态 HTML。
penguins = FileAttachment("penguins.csv").csv({typed: true})
viewof bill = Inputs.range([30, 60], {step: 0.5, label: "嘴峰长度 ≥"})
Inputs.table(penguins.filter(d => d.bill_length_mm >= bill), {rows: 8})
数据从 R 侧导出:write.csv(palmerpenguins::penguins, "penguins.csv") 放在 .qmd 同目录即可。
viewof 声明「控件 + 它的值」:左边是 UI,右边是可供下方单元格 引用的变量——OJS 版的输入输出自动连线,没有显式的 server 概念。
选型经验:图表筛选类交互优先 OJS(便宜、快、可静态托管); 需要服务端模型、数据库连接、私有凭证时才上 Shiny。
6. 部署决策表
| 方案 | R 在哪跑 | 最适合 | 代价 / 边界 |
|---|---|---|---|
| shinylive(WebAssembly) | 读者浏览器(webR) | 教学演示、公开小数据、无 IT 支持 | 包兼容受限、大数据慢、代码与数据随页公开 |
| shinyapps.io | Posit 云 | 公开应用、小团队快速上线 | 免费额度有限、数据要能出公网 |
| Posit Connect / Shiny Server | 自家服务器 | 企业内部、私有数据、权限与定时 | 需要 IT 运维与预算 |
| 定时重渲染静态板(2.3 §7) | CI(GitHub Actions) | 「伪交互」足够的大多数监控 | 不是真交互,只是刷新快照 |
选型第一问永远是数据能不能出网:能出网才考虑前三行;不能出网直接进 Connect / Shiny Server,或退回定时静态板。
逐字录入 §3 的 penguins-live.qmd 并渲染成功;操作两个控件, 对照 §4 的响应式图验证「哪些输出在动」。交一张截图 + 一行观察。
给 §3 例子加第二个输出:renderText() 卡片,实时显示当前筛选下最重的 企鹅体重。要求只新增 server 块、复用 selected(),并把新终点补进响应式图。
第一轮(禁 AI):用体检数据集做「体检交互看板」:sidebar 两个输入 (性别下拉 + 年龄段滑块)驱动一图(renderPlot)一表(renderDataTable)。 先画响应式图再写代码。 第二轮(开放 AI):把代码贴给 Posit Assistant,只问:「画出这个应用的响应式图,并指出一处会重复计算但不必重复的路径。」 对照你自己画的图,记录差异与一处你会采纳的优化。
Capstone · 压轴项目
任务:把 2.3 的压轴仪表盘升级为动态版(或换数据集重做):侧边栏两个 输入驱动至少「一图一表」两个响应式输出;至少一个 reactive() 中继;从 §6 选定部署路径并写 50 字理由;公开数据集实际部署(shinylive / shinyapps.io), 内部数据交 server: shiny 源码 + 演示截图。
| 维度 | 达到 | 良好 | 卓越 |
|---|---|---|---|
| 交互设计 | 两个输入真实影响输出 | 输入粒度服务阅读任务 | 无一控件是摆设,能说明取舍 |
| 响应式正确性 | 输入输出接通 | 有 reactive 中继避免重复计算 | 主动预测并实测失效范围 |
| 部署决策 | 写明所选路径 | 理由覆盖数据敏感度与运维条件 | 完成实际部署并交链接 |
| 工程与可复现 | 渲染成功、结构清晰 | setup/server 分层、代码有注释 | 他人拿到文件可一键复跑 |
SOURCES · 来源映射
| 讲义节 | 素材 | 性质 |
|---|---|---|
| §1 交互光谱与部署模式 | posit::conf(2024) quarto-dashboards 4-parameters-interactivity-deployment(Mine Çetinkaya-Rundel 及助教团队 · README 标示 CC-BY 4.0;LICENSE.md 为 CC-BY-SA 4.0) |
改编 |
| §2–§3 嵌入式 Shiny 结构 | Quarto 官方 Dashboards: Shiny 文档 + 同工作坊示例 | 引用/改编 |
| §4 响应式心智模型 | posit::conf(2025) shiny-r「Reactivity」讲(Colin Rundel · README 标示 CC-BY 4.0;LICENSE.md 为 CC-BY-SA 4.0) | 改编 |
| §5 OJS | Quarto 官方 Interactivity / Observable 文档 | 引用 |
| 体检看板练习、部署决策表、rubric | 本项目 | 原创 |
本章以 CC-BY-SA 4.0 发布。