2.4 Dynamic Quarto Dashboards
2.4 Dynamic Quarto Dashboards
Learning objectives
By the end of this chapter, you can:
- locate a requirement on the interaction spectrum: static → parameterized → OJS → Shiny.
- embed Shiny in a Quarto dashboard using
server: shiny/server: shinylive. - wire sidebar inputs, including
selectInputandsliderInput, to reactive outputs. - predict which outputs are invalidated by an input change using a reactive graph.
- choose among shinylive, shinyapps.io, and Connect according to data sensitivity and operational resources.
Prerequisite check (≤5 minutes)
Complete these checks independently before continuing; otherwise revisit Chapter 2.3.
- Rebuild the two-page Chapter 2.3 dashboard, including pages, rows, and value boxes, without referring to the text.
- Confirm that
install.packages("shiny")has succeeded. Have you heard that inputs must be read on the server side? If not, §3 introduces the rule.
1. The interaction spectrum: From static to Shiny
Interaction is a dashboard’s most expensive feature: it costs effort to write, run, and host. Before adding it, ask whether rerendering your Chapter 2.3 dashboard on a weekly schedule would solve 90% of the problem. Often it would. Only the remaining needs justify moving further along this spectrum.
| Level | Approach | What readers receive | Server requirements |
|---|---|---|---|
| Static | One quarto render |
A fixed snapshot | Any static host |
| Parameterized | params: + quarto render -P class:"suv" |
Prepared variants | Still compatible with static hosting |
| Browser interaction | OJS / htmlwidgets | Immediate browser-side filtering | No computation server needed |
| Shiny | server: shiny |
Flexible filtering and server-side computation | A host that runs R |
The workshop distinguishes four deployment modes: static, scheduled, parameterized, and interactive. The first three principally concern when rendering happens; the fourth requires a runtime.
2. Embedding Shiny in Quarto: server: shiny
The entry point for Quarto dashboards with Shiny is one YAML line: server: shiny. Blocks then take two roles:
- UI blocks (default) place controls such as
selectInput()on the page. - Server blocks (
#| context: server) readinput$xx, compute results, and producerender*()outputs.
The output of server: shiny is not static HTML. Double-clicking a file gives you only a shell. During development, use Render preview in Positron/RStudio, which starts a Shiny process. For delivery, use a host that runs R or choose shinylive; see §6.
The server: shinylive variant uses WebAssembly to run R through webR in the reader’s browser, allowing static hosting. Costs and limits are discussed in §6.
4. The reactive mental model: Graphs and invalidation
Shiny’s behavior can be represented as a reactive graph. Inputs are sources, reactive() expressions are intermediate nodes, and render*() functions are endpoints. An input change invalidates downstream dependents and queues recomputation; upstream nodes are unaffected.
flowchart LR
A["selectInput<br/>input$species"] --> D["reactive({ filter })"]
B["sliderInput<br/>input$mass"] --> D
D --> P["renderPlot"]
D --> T["renderDataTable"]
In §3, moving the mass slider invalidates selected, and both plot and table recompute. The input controls and the data-loading code in context: setup do not rerun. This is how the app computes only what is needed.
Change the §3 slider to filter a range of bill_length_mm. ① Draw the revised reactive graph. ② Which blocks will recompute, and which will not? Write your predictions before modifying and running the code. Incorrect predictions reveal gaps in your mental model.
This chapter introduces only the essentials: the graph, invalidation, and necessary computation. Chapter 2.7 develops eventReactive, timers, modules, and debugging.
5. Server-free interaction: The OJS alternative
When readers need only browser-side filtering and inspection, without server-side statistics, Observable JS (OJS) offers an option without a computation server. Its rendered output is static HTML.
penguins = FileAttachment("penguins.csv").csv({typed: true})
viewof bill = Inputs.range([30, 60], {step: 0.5, label: "Bill length ≥"})
Inputs.table(penguins.filter(d => d.bill_length_mm >= bill), {rows: 8})
Export the data from R with write.csv(palmerpenguins::penguins, "penguins.csv") and place the CSV beside the .qmd file.
viewof declares a control and its value. It connects the UI to a variable that subsequent cells can reference: OJS automatically connects inputs and outputs without an explicit server.
A practical selection rule: prefer OJS for chart filtering because it is inexpensive, fast, and compatible with static hosting. Use Shiny when you need server-side models, database connections, or private credentials.
6. Deployment decisions
| Option | Where R runs | Best suited to | Costs / limits |
|---|---|---|---|
| shinylive (WebAssembly) | Reader’s browser via webR | Teaching demos, small public datasets, no IT support | Limited package compatibility; large data are slow; code and data accompany the page |
| shinyapps.io | Posit’s cloud | Public apps and small teams needing a quick launch | Limited free quota; data must be permitted on the public internet |
| Posit Connect / Shiny Server | Your own server | Internal enterprise use, private data, access control, scheduling | IT operations and budget |
| Scheduled static rerendering (Chapter 2.3 §7) | CI, such as GitHub Actions | Monitoring that can work with refreshed snapshots | No live interaction; only updated snapshots |
Always begin with whether the data may leave the organization. Public-facing options require permission to expose the data; otherwise use internal Connect / Shiny Server hosting or return to a scheduled static dashboard.
Enter the complete penguins-live.qmd example from §3 and render it successfully. Operate both controls and compare the changing outputs with the §4 graph. Submit a screenshot and one observation.
Add another output to §3: a renderText() card showing the heaviest penguin under the current filters. Add only a server block, reuse selected(), and include the new endpoint in the reactive graph.
Round 1 (AI prohibited): Build an interactive health-check dashboard with two sidebar inputs: a sex dropdown and an age-range slider. They should drive one plot (renderPlot) and one table (renderDataTable). Draw the reactive graph before coding. Round 2 (AI allowed): Give Posit Assistant the code and ask only: “Draw this app’s reactive graph and identify one path that repeats a computation unnecessarily.” Compare its graph with yours, recording differences and one optimization you would adopt.
Capstone
Task: Upgrade your Chapter 2.3 capstone dashboard to a dynamic version, or rebuild it with another dataset. Two sidebar inputs must drive at least a plot and a table, with at least one intermediate reactive() expression. Choose a §6 deployment route and write a 50-word rationale. Actually deploy public data through shinylive / shinyapps.io; for internal data, submit server: shiny source and demonstration screenshots.
| Dimension | Meets expectations | Strong | Excellent |
|---|---|---|---|
| Interaction design | Both inputs affect outputs | Input granularity supports the reading task | Every control has a purpose and its tradeoffs are explained |
| Reactive correctness | Inputs and outputs connect | Intermediate reactive avoids repeated computation | Predicts and verifies invalidation scope |
| Deployment decision | Names a deployment route | Covers data sensitivity and operational resources | Completes deployment and submits a link |
| Engineering and reproducibility | Renders; clear structure | Separates setup/server and comments code | Another person can rerun the file in one step |
SOURCES
| Chapter section | Material | Use |
|---|---|---|
| §1 interaction spectrum and deployment modes | posit::conf(2024) quarto-dashboards, 4-parameters-interactivity-deployment (Mine Çetinkaya-Rundel and teaching assistant team; README: CC-BY 4.0; LICENSE.md: CC-BY-SA 4.0) |
Adaptation |
| §2–§3 embedded Shiny structure | Official Quarto Dashboards: Shiny documentation and workshop examples | Reference / adaptation |
| §4 reactive mental model | posit::conf(2025) shiny-r, “Reactivity” (Colin Rundel; README: CC-BY 4.0; LICENSE.md: CC-BY-SA 4.0) | Adaptation |
| §5 OJS | Official Quarto Interactivity / Observable documentation | Reference |
| Health-check dashboard exercise, deployment decision table, and rubric | This project | Original |
This chapter is published under CC-BY-SA 4.0.