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