---
title: "Shiny Apps"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{a04_shiny_apps}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
warning = FALSE,
message = FALSE,
out.width = "95%", # figures occupy ~95% of document width
out.height = "auto",
dpi = 320, # ensure figure quality
fig.width = 6, # default aspect ratio (can be overridden per-figure)
fig.height = 3
)
options(rmarkdown.html_vignette.check_title = FALSE)
```
This vignette shows how to render **interactive plots** and **tabular results** in Shiny using the visOmopResults package and functions that build on it. Specifically, we will demonstrate:
- **Small/static tables** → `gt`, for compact, fixed results such as cohort counts or characteristics.
- **Large/dynamic tables** → `DT` or `reactable`, for large results or those that require sorting/filtering to interpret.
- **Plots** → build with `ggplot2` and (optionally) wrap with `plotly::ggplotly()` for interactivity in shiny.
# Set up
Load packages and mock data.
```{r}
library(shiny)
library(bslib)
library(sortable)
library(shinyWidgets)
library(gt)
library(DT)
library(reactable)
library(plotly)
library(dplyr)
library(visOmopResults)
library(IncidencePrevalence)
library(CohortCharacteristics)
library(shinycssloaders)
# Mock results in visOmopResults
data <- visOmopResults::data
# Remove global options (just in case we have them from previous work)
setGlobalPlotOptions(style = NULL, type = NULL)
setGlobalTableOptions(style = NULL, type = NULL)
```
## Mock data
We will use 3 different mock results, which are:
- **Baseline characteristics** from the `CohortCharacteristics` package
- **Incidence** results from the `IncidencePrevalence` package
- **Large scale characterisation**, which is not a ``
# Shiny App: User Inteface
The Shiny app has three panels, one for each result. All allow filtering by sex strata and provide panel-specific visualization options:
**- Baseline characteristics:** shows a `gt` table with controls for headers, groups, and hidden columns..
**- Large Scale characteristics:** renders as a `datatable` or `reactable`, with options to group or hide columns.
**- Incidence:** displays a static `ggplot` or interactive `plotly` plot, with options for colouring, faceting, and ribbons.
## Example UI Code
```{r, eval=FALSE}
ui <- bslib::page_navbar(
title = "visOmopResults for Shiny",
window_title = "visOmopResults • Shiny",
collapsible = TRUE,
# Baseline Characteristics (GT table)
bslib::nav_panel(
title = "Baseline Characteristics",
icon = icon("users-gear"),
bslib::layout_sidebar(
sidebar = bslib::sidebar(
title = "Filters",
shinyWidgets::pickerInput(
inputId = "summarise_characteristics_sex",
label = "Sex",
choices = c("overall", "Male", "Female"),
selected = "overall",
multiple = TRUE
),
width = 320,
position = "left",
open = TRUE
),
bslib::card(
full_screen = TRUE,
bslib::card_header("Table layout"),
bslib::layout_sidebar(
sidebar = bslib::sidebar(
title = "Arrange columns",
sortable::bucket_list(
header = NULL,
group_name = "col-buckets",
orientation = "horizontal",
add_rank_list(
text = "None",
labels = c("variable_name", "variable_level", "estimate_name"),
input_id = "summarise_characteristics_table_none"
),
add_rank_list(
text = "Header",
labels = c("sex"),
input_id = "summarise_characteristics_table_header"
),
add_rank_list(
text = "Group columns",
labels = c("cdm_name", "cohort_name"),
input_id = "summarise_characteristics_table_group_column"
),
add_rank_list(
text = "Hide",
labels = "table_name",
input_id = "summarise_characteristics_table_hide"
)
),
position = "right",
width = 400,
open = FALSE
),
# GT output
gt::gt_output("summarise_characteristics_table") |>
shinycssloaders::withSpinner(type = 4)
)
)
)
),
# Large Scale Characterisation (DT / reactable)
bslib::nav_panel(
title = "Large Scale Characterisation",
icon = icon("table"),
bslib::layout_sidebar(
sidebar = bslib::sidebar(
# title = "Display options",
shinyWidgets::pickerInput(
inputId = "large_scale_sex",
label = "Sex",
choices = c("overall", "Male", "Female"),
selected = "overall",
multiple = TRUE
),
radioButtons(
"large_engine",
"Renderer",
choices = c("DT", "reactable"),
inline = TRUE
),
sortable::bucket_list(
header = NULL,
group_name = "col-buckets",
orientation = "horizontal",
add_rank_list(
text = "None",
labels = c("variable_name", "variable_level", "estimate_name"),
input_id = "large_scale_none"
),
add_rank_list(
text = "Group columns",
labels = c("cdm_name", "cohort_name"),
input_id = "large_scale_group_column"
),
add_rank_list(
text = "Hide",
labels = character(),
input_id = "large_scale_hide"
)
),
width = 320
),
bslib::card(
full_screen = TRUE,
bslib::card_header("Cohort characteristics (large-scale)"),
conditionalPanel(
"input.large_engine == 'DT'",
DTOutput("large_dt") |> shinycssloaders::withSpinner(type = 4)
),
conditionalPanel(
"input.large_engine == 'reactable'",
reactableOutput("large_reactable") |> shinycssloaders::withSpinner(type = 4)
)
)
)
),
# Incidence (ggplot → plotly)
bslib::nav_panel(
title = "Incidence",
icon = icon("chart-line"),
bslib::layout_sidebar(
sidebar = bslib::sidebar(
title = "Plot options",
shinyWidgets::pickerInput(
"incidence_sex",
"Sex strata",
choices = c("overall", "Male", "Female"),
selected = "overall",
multiple = TRUE
),
shinyWidgets::pickerInput(
inputId = "facet",
label = "Facet",
selected = "sex",
multiple = TRUE,
choices = c("cdm_name", "incidence_start_date", "sex", "outcome_cohort_name"),
),
shinyWidgets::pickerInput(
inputId = "colour",
label = "Colour",
selected = "outcome_cohort_name",
multiple = TRUE,
choices = c("cdm_name", "incidence_start_date", "sex", "outcome_cohort_name")
),
checkboxInput("inc_ribbon", "Show ribbon (CI)", TRUE),
checkboxInput("interactive", "Interactive Plot", TRUE),
width = 320
),
bslib::card(
full_screen = TRUE,
bslib::card_header("Incidence over time"),
uiOutput("incidence_plot", height = "520px") |> shinycssloaders::withSpinner(type = 4)
)
)
)
)
```
# Shiny App: Server
### 1) Baseline characteristics
The server filters results by the selected sex and creates a `gt` table using the `tableCharacteristics()` function from the `CohortCharacteristics` package. This function is built on **visOmopResults**, which ensures consistent styling and supports arguments to define headers, group columns, and hide columns.
If you have your own `` table, which don't has a dedicated table function, you can instead use `visOmopTable()` to generate a `gt` table in Shiny. This allows you to group estimates and configure header, group, and hidden column options in a similar way.
### 2) Large Scale characteristics
These results are not in `` format, as shown below:
```{r}
data$large_scale_characteristics
```
In this case, we use `visTable()` to generate tables as either a `datatable` or a `reactable`, depending on the user’s choice in the UI. The table type is specified with the type argument.
For both table types, we pass the UI-selected columns to `groupColumn` and `hide.` We do not generate a header for this result, as it would require restructuring the estimates into a single "estimate_value" column.
The look and behaviour of the tables can be customised through the style argument. Available options can be explored with:
- `tableStyle("datatable")`
- `tableStyle("reactable")`
In this vignette, we modify the `datatable` style in the server code so filters appear at the top of the table instead of the default bottom.
### 3) Incidence
For incidence results, we use the `plotIncidence()` function from the `IncidencePrevalence` package. This function creates a `ggplot` object, which can be rendered as a static plot with `plotOutput` or as an interactive plot with `plotlyOutput`. Users can also select which columns to use for colouring and faceting, and whether to display confidence interval ribbons.
For other results—whether `` class or not—you can generate plots in a similar way by using the plotting functions available in `visOmopResults.`
## Example Server Code
> Note: Both `CohortCharacteristics` and `IncidencePrevalence` functions for plotting and tabulation are built on `visOmopResults`, which means they share a consistent interface and style.
```{r, eval=FALSE}
server <- function(input, output, session) {
# Baseline (GT)
output$summarise_characteristics_table <- gt::render_gt({
data$summarised_characteristics |>
# filter results by sex
filterStrata(sex %in% input$summarise_characteristics_sex) |>
# create GT table
CohortCharacteristics::tableCharacteristics(
header = input$summarise_characteristics_table_header,
groupColumn = input$summarise_characteristics_table_group_column,
hide = input$summarise_characteristics_table_hide,
type = "gt"
)
})
# Large scale characteristics
getLargeScaleResults <- reactive({
data$large_scale_characteristics |>
filter(.data$sex %in% input$large_scale_sex)
})
# To render as DT
output$large_dt <- renderDT({
getLargeScaleResults() |>
visTable(
hide = input$large_scale_hide,
groupColumn = input$large_scale_group_column,
type = "datatable",
style = list(
filter = "top",
searchHighlight = TRUE,
rownames = FALSE
)
)
})
# To render as reactable
output$large_reactable <- reactable::renderReactable({
getLargeScaleResults() |>
visTable(
hide = input$large_scale_hide,
groupColumn = input$large_scale_group_column,
type = "reactable",
style = "default"
)
})
# Incidence
getIncidencePlot <- reactive({
data$incidence |>
filterStrata(sex %in% input$incidence_sex) |>
plotIncidence(
colour = input$colour,
facet = input$facet,
ribbon = input$inc_ribbon
) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
})
output$incidence_plot <- renderUI({
plt <- getIncidencePlot()
if (input$interactive) {
ggplotly(plt)
} else {
renderPlot(plt)
}
})
}
```
# Run App
To run the Shiny app, copy the code chunks provided in this vignette into a script named **`app.R`**, and add the following line at the end:
```{r, eval=FALSE}
shinyApp(ui, server)
```
You can find the complete code run the ShinyApp [here](https://github.com/darwin-eu/visOmopResults/tree/main/inst/app.R).