Putting everything together to create an interactive dashboard

Introduction

In this tutorial we’ll put everything together we’ve learned in the previous five tutorials to create an interactive dashboard. We learn how we can set up an interactive filter system that uses a brushable timeline component and the button groups developed in the previous tutorial. In addition, we learn how to send events from JavaScript to R at different rates such that we don’t overflow R with computations when we slide our brush over the timeline. Finally, in order to control the complexity of our app, we’ll use Shiny modules to create a modular code base for our dashboard.

Module setup

The dashboard layout is based on the navbarPage layout. This layout creates a page with a top level navigation bar and has several tabPanels. In addition, it contains a button on the right hand side to launch the interactive help system developed in tutorial 4.

navbar layout

Each tabpanel, however, only holds a single Shiny module (see below). You can think of a module as a piece of a Shiny app. Furthermore, a module usually has reactive inputs and outputs. More in depth discussions on Shiny modules are available here and here. From a module perspective, our dashboard essentially has three main pillars:

  1. A front panel module with a brushable timeline to filter the data on the front panel, combined with a filter module with a set of buttongroups (not shown) to filter the data of the whole app.
  2. A filter processing reactive that depends on the filter module that outputs a reactive data frame (rdf).
  3. A set of other modules that take the reactive data as their input.

The figure below provides a graphical overview of this setup:

Passing a reactive into a module

For the sake of this tutorial it doesn’t really matter what the modules x, y, and z do in step 3. It only matters that they all take the same reactive dataset as their input and that the data module reacts to the filter module, which in turn depends on the brush events in the timeline and the button group filter controls.

Capturing timeline brush events

In tutorial 3) we’ve created a C3LineBarChart, that has a brushable timeline, based on C3 subchart method.

Here’s what the timeline part of our C3LineBarChart looks like:

timeline with a brush

To add a brushable subchart in C3 we simply call the subchart method inside c3.generate:

c3.generate({
  ...
  subchart: {
    show: true,
    onbrush: function (domain) { ... }
  }
})

This code snippet tells C3 we want to add a subchart and call a function when an onbrush event occurs.

In our case, domain refers to the timeline domain of our chart i.e. the brush start and end dates. Inside the onbrush callback i.e. function (domain) { ... }, we can easily tell JavaScript to send the domain information to Shiny, using the Shiny.onInputChange method (see tutorial 3).

However, this may trigger a large number of events when we slide the brush around. Therefore, we introduce a rate policy, to limit the number of events.

Controling the number of events

Event listeners such as a brush may fire many events. If these events are coupled with expensive computations, e.g. filtering a big data set, this can result in a poor user experience. Therefore, we sometimes might want to limit the number of events that get sent. Two popular ways of controlling the number of events are throttling and debouncing.

Throttling means no more than one event will be sent per x milliseconds, while debouncing means all of the events will be ignored until no events have been received for x milliseconds, at which time the most recent event will be sent. Luckily, using underscore.js, applying throttling or debouncing is quite straightforward.

Suppose we want to debounce events using a 250 millisecond interval and then send the domain info to Shiny. Using underscore and Shiny.onInputChange the relevant code snippet becomes:

subchart: {
  show: true,
  onbrush: _.debounce( function (domain) {Shiny.onInputChange(el.id + "-domain", domain)} , 250)
}

Note that we wrapped the anonymous function function (domain) {Shiny.onInputChange(el.id + "-domain", domain)} inside a call to _.debounce( ... , 250) and that is it. In the code above, el.id is simply the id of our chart. For example, say the id equals id1, then in R we can capture the start and end times in a vector via input$"id1-domain". Now we can rest assured that when we move the brush we don’t flood Shiny with events since only after we’ve stopped moving the brush for at least 250 milliseconds an event gets sent!

Putting it all together: a complete example

A complete example application involving all aspects from the previous tutorials can be found can be downloaded here.

This repo contains the complete code base for a small dashboard application, that has a front page module using a number of C3 based HTMLWidgets, a brushable timeline component, our custom input binding switch component, as well as the intro.js based help system developed in tutorial 4.

The example app also involves a small filter module using the custom input binding button groups. Finally, the example contains two simple modules that consume the filtered data by visualizing a subset of the data via a Sankey chart from the googleVis package and a datatable from the DT package.

The screenshots below give an impression of the various parts of the dashboard demo app:

Dashboard front page