Chart Configuration

Jeremy Wildfire

2021-09-21

Chart Configuration Vignette

The {safetyGraphics} shiny app can be used to display a wide variety of charts. This vignette provides details about the charting process including step-by-step instructions for adding new charts and technical specifications, but first we need to talk about a 2nd package …

Introducing {safetyCharts}

While this is technically a vignette for {safetyGraphics}, the {safetyCharts} package is just as important here. The roles of the packages can be summarized in just a few words:

The {safetyGraphics} platform displays charts from {safetyCharts}.

This relationship is central to the technical framework for the safetyGraphics app. By itself, the safetyGraphics platform really doesn’t do much! In fact, none of the content on the Charts tab is actually found in the safetyGraphics package; it’s all imported from elsewhere! As you’ve probably guessed, the default charts live in the safetyCharts package. safetyCharts has over a dozen charts that are configured to work with {safetyGraphics}, but can also easily be used independently.

While {safetyGraphics} and {safetyCharts} are designed to work seamlessly together, users can also add charts from other packages. In fact, several charts in {safetyCharts} are just wrappers that load charts from other packages for use in {safetyGraphics}. The rest of this vignette provides a series of step-by-step examples detailing how this process works for different types of charts.

{safetyGraphics} Chart Components

To add a chart to safetyGraphics, two components are required:

  1. A Configuration Object
  2. A Chart Function

The configuration file captures metadata about the chart for use in the app and is typically saved as a YAML file. Several example configuration files are provided in the examples below, and YAML Configuration files for {safetyCharts} are saved here.

The chart function typically takes a list of settings and a list of data as inputs and returns a chart object ready to be displayed in the app. Details of charting functions vary somewhat for different chart types, as explained in the examples below.

A full technical specification of this chart configuration framework is provided in Appendix 1.

Example 1 - Hello World

Once you’ve created the configuration and chart functions, the chart can be added to the app via the charts parameter in safetyGraphicsApp(). Consider this simple “Hello World” example:

# Chart Function
helloWorld <- function(data, settings){
    plot(-1:1, -1:1)
    text(runif(20, -1,1),runif(20, -1,1),"Hello World")
}

# Chart Configuration
helloworld_chart<-list(
    env="safetyGraphics",
    name="HelloWorld",
    label="Hello World!",
    type="plot",
    domain="aes",
    workflow=list(
        main="helloWorld"
    )
)

safetyGraphicsApp(charts=list(helloworld_chart))

It’s also easy to add a custom chart to the default charts provided in {safetyCharts} using the makeChartConfig() function:

charts <- makeChartConfig(packages="safetyCharts") # or just makeChartConfig() since safetyCharts is included by default
charts$helloworld<-helloworld_chart
safetyGraphicsApp(charts=charts)

Here’s our Hello World the chart running in the app:

Example 2 - Static Outlier Explorer

Now let’s consider a more complex example that makes use of the data and settings provided in safetyGraphics. In this section, we use {ggplot2} to create a spaghetti plot for tracking outliers in lab data. First, consider the following code which creates a stand-alone plot for a single data set:

# Use sample clinical trial data sets from the {safetyData} package
library(safetyData) 
library(ggplot2)
library(dplyr)

# Define data mapping using a format similar to a reactive safetyGraphics mapping 
settings <- list(
    id_col="USUBJID",
    value_col="LBSTRESN",
    measure_col="LBTEST",
    studyday_col="LBDY"
)

# Define a plotting function that takes data and settings as inputs 
spaghettiPlot <- function( data, settings ){
    # define plot aes - note use of standard evaluation! 
    plot_aes <- aes_(
        x=as.name(settings$studyday_col), 
        y=as.name(settings$value_col), 
        group=as.name(settings$id_col)
    )

    #create the plot
    p<-ggplot(data = data, plot_aes) +
        geom_path(alpha=0.15) + 
        facet_wrap(
            as.name(settings$measure_col),
            scales="free_y"
        )
    return(p)
}

spaghettiPlot(
    safetyData::sdtm_lb %>% 
        filter(LBTEST %in% c("Albumin","Bilirubin","Calcium","Chloride")), 
    settings
)

Running the code above should create a plot with 4 panels:

With minor modifications, this chart can be added to the {safetyGraphics} shiny app, which allows us to create the chart with any mappings/data combination loaded in the app. The spaghettiPlot() function above is already written to work with safetyGraphics, so we just need to create the chart configuration object. This time we’ll capture the configuration in a YAML file.

env: safetyGraphics
label: Spaghetti Plot
type: plot
domain: 
  - labs
workflow:
  main: spaghettiPlot
links:
  safetyCharts: https://github.com/SafetyGraphics/safetycharts

With the charting function loaded in to our session and the configuration file saved in our working directory as spaghetti.yaml, we can add the chart to the app as follows:

library(yaml)
charts <- makeChartConfig()
charts$spaghetti<-prepareChart(read_yaml('spaghetti.yaml'))
safetyGraphicsApp(charts=charts)

Under the charts tab, you’ll see:

If you look closely at the spaghettiPlot() code above, you’ll noticed some details that make the chart work in the app:

These details allow users to dynamically define data attributes for any labs data set, allowing the chart to be reused across many different types of data.

This example is inspired by safetyCharts::safety_outlier_explorer - the charting function and yaml configuration file on are GitHub.

Example 3 - Shiny Module

{safetyGraphics} also supports defining charts as Shiny Modules. Once you’re familiar with modules, they are relatively straightforward to use with safetyGraphics.

Let’s take a look at a simple module that extends the functionality of the static chart from the example above. Once again, this example is based upon safetyCharts, and you can see the code and config on GitHub.

The config object for a module differs from a static chart is that the workflow section of the YAML file must specify ui and server functions instead of a main charting function. This example defines a simple UI function that allows users to select which lab measurements should be included in the spaghetti plot from example 1:

safetyOutlierExplorer_ui <- function(id) {
    ns <- NS(id) 
    sidebar<-sidebarPanel(
        selectizeInput(
            ns("measures"), 
            "Select Measures", 
            multiple=TRUE, 
            choices=c("")
        )
    )
    main<-mainPanel(plotOutput(ns("outlierExplorer")))
    ui<-fluidPage(
        sidebarLayout(
            sidebar,
            main,
            position = c("right"),
            fluid=TRUE
        )
    )
    return(ui)
}

Next we define a server function that populates the control for selecting measurements and then draws the plot using safetyCharts::safety_outlier_explorer() charting function - which is based on the spaghetti() function ! Note that the server function takes a single reactive params object containing the data (params$data) and settings (param$settings) as input.

safetyOutlierExplorer_server <- function(input, output, session, params) {

    ns <- session$ns
    # Populate control with measures and select all by default
    observe({
        measure_col <- params()$settings$measure_col
        measures <- unique(params()$data[[measure_col]])
        updateSelectizeInput(
            session, 
            "measures",
            choices = measures,
            selected = measures
        )
    })

    # customize selected measures based on input
    settingsR <- reactive({
        settings <- params()$settings
        settings$measure_values <- input$measures
        return(settings)
    })

    #draw the chart
    output$outlierExplorer <- renderPlot({safety_outlier_explorer(params()$data, settingsR())})
}

Finally, the YAML configuration file looks like this - just the workflow and label changes from Example 1:

env: safetyGraphics
label: Outlier Explorer - Module
type: module
package: safetyCharts
domain: 
  - labs
workflow:
  ui: safetyOutlierExplorer_ui
  server: safetyOutlierExplorer_server
links:
  safetyCharts: https://github.com/SafetyGraphics/safetycharts

Initializing the app as usual by adding it to the chart list: charts$outlierMod<-prepareChart(read_yaml('outlierMod.yaml'))

Unselecting a few measures gives the following display:

Example 4 - htmlwidgets and init functions

You can also add custom htmlwidgets to safetyGraphics. In fact, many of the default charts imported from safetyCharts are javascript libraries that are imported as htmlwidgets. Like shiny modules, htmlwidgets are relatively simple to use once you are familiar with the basics.

The biggest differences between widgets and other charts in safetyGraphics are:

  1. The widget must be contained in a package, which must be specified in the YAML file.
  2. The widget expects a widget item giving the name of the widget in the YAML workflow.
  3. By default, the data and settings for a widget are passed in a list (list(data=data, settings=settings)) to the x parameter in htmlwidget::createWidget.

Items 1 and 2 above are simple enough, but #3 is likely to create problems unless the widget is designed specifically for usage with safetyGraphics. That is, if the widget isn’t expecting x$settings to be a list that it uses to configure the chart, it probably isn’t going to work as expected.

Fortunately, there’s a workaround built in to safetyGraphics in the form of init workflow functions. Init functions run before the chart is drawn, and can be used to create custom parameterizations. The init function should take data and settings as inputs and return params which should be a list which is then provided to the chart (see the appendix for more details). The init function for the the interactive AE Explorer is a good example. It starts by merging demographics and adverse event data and then proceeds to create a customized settings object to match the configuration requirements of the javascript chart renderer. This init function is then saved under workflow$init in the chart config object.

The rest of the chart configuration YAML is similar to the examples above, and the chart is once again by passing the chart config object to safetyGraphicsApp()

Appendix #1 - Chart Framework Technical Specifications

Configuration Overview