mwshiny: Creating a Multi-Window Shiny App

Hannah De los Santos

2019-03-04

The mwshiny provides a simple function, mwsApp(), which allows you to create shiny apps that span multiple windows. It uses all the same conventions and applications as a normal shiny function. To learn more about shiny and find some tutorials, please visit their website. I’ll assume basic knowledge of shiny throughout this vignette.

For now, I’m going to set up a basic multi-window app using the iris dataset, a dataset of several plant species. Note that mwshiny works best when apps are run in external browser windows.

Load Libraries and Datasets

As in any typical shiny app, you start by loading any necessary libraries and data that you want to be globally accessed. I’ll start by doing that with visualization libraries and the iris dataset.

# load libraries
# note that attaching mwshiny also attaches shiny
library(mwshiny) # our multiwindow app
## Loading required package: shiny
library(ggplot2) # cool visualizations
library(datasets) # contains the iris dataset

data(iris) # load iris data
summary(iris) # just to get a look at what we're dealing with here
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 

Set Up UI for Each Window

Now that I’ve loaded my data and packages, I need to set up what my windows are going to look like. For this mwshiny app, I’m going to have one window be my “controller”, where I change all my inputs, and the other one be a window where my output is going to be. My output is going to be scatter plots of the data, colored by species.

# vector of strings that are the names of my windows
win_titles <- c("Controller","Scatter")

ui_win <- list()

# first we add what we want to see in the controller to the list
ui_win[[1]] <- fluidPage(
  titlePanel("Iris Dataset Explorer: Controller"),
  sidebarLayout(
    sidebarPanel(
      # choose what goes on the x axis
      selectInput(inputId = "x_axis",
                  label = "What would you like to see on the x axis?",
                  choices = colnames(iris)[colnames(iris)!="Species"]),
      # choose what goes on the y axis
      selectInput(inputId = "y_axis",
                  label = "What would you like to see on the y axis?",
                  choices = colnames(iris)[colnames(iris)!="Species"]),
      # choose which groups you want to see
      checkboxGroupInput(inputId = "spec",
                         label = "Which species would you like to see?",
                         choices = as.character(unique(iris$Species)),
                         selected = as.character(unique(iris$Species))),
      # only build the scatter plot when this is clicked
      actionButton(inputId = "go",
                   label = "Build!")
    ),
    # just an empty main panel
    mainPanel()
  )
)

# then we add what we want to see in the scatter section
ui_win[[2]] <- fluidPage(
  titlePanel("Iris Dataset Explorer: Scatter"),
  plotOutput(outputId = "iris_scatter")
)

Note that win_titles and ui_win are the same length. Also, the UI that I want to see in each window matches up to their title (i.e., the UI I want for the controller window is first, then the scatter UI is second in the list).

Set Up the Calculations

Now that my UI is all set up, I want to set up a derived dataframe that I’ll use in my output, created when I click my “Build!” button. Note that this isn’t explicitly necessary to do if you would prefer to do such things in render functions – but if you’re using any observe()-type capabilities, this is the variable to do it in.

serv_calc is a list of functions of the form function(input,calc), where any calculations based on input are done. Accessing variables through input is done with input(), i.e. input()$x. Any variables that you’ve made that you want to pass to input should be added to calc with a name, which is a named list of reactive variables. This is done in the traditional manner, i.e. calc[[“y”]] <- y. Note that these functions follow the traditional shiny restrictions (reactive variables can only be used in a reactive context, etc.)! Also note that since you’re accessing input as a function here, restrictions on functions apply.

It’s recommended for clarity that you make one function entry for each observe()-type function you’re using, but it’s not explicitly necessary.

# setting up the list of calculations I want to do
serv_calc <- list()

# I only want to build a scatterplot when I click my build button, so my list will be of length 1
serv_calc[[1]] <- function(input,calc){
  # this is going to activate any time I press "build!"
  observeEvent(input()$go, {
    # create our data frame for visualizing
    sub.df <- data.frame("x" = iris[iris$Species %in% input()$spec,input()$x_axis],
                         "y" = iris[iris$Species %in% input()$spec,input()$y_axis],
                         "species"=iris[iris$Species %in% input()$spec,"Species"])
    
    # add this to calc, since we want to use this in our rendering
    calc[["sub.df"]] <- sub.df
  })
}

Set Up the Output

Now that I’ve calculated based on input and set that up to work only when I click “build!”, I want to actually render my plot for output.

serv_out is a named list of functions, where each function in the list corresponds to the output that it renders. Functions are of the form function(input,calc), where input is traditional shiny input, and calc is the list of calculated variables that we added to previously. Entries in both lists can be accessed in a normal manner (i.e. input$x or calc[[“y”]]). These functions also follow the standard shiny restrictions.

# set up our serv_out list
serv_out <- list()

# we're just rendering our scatter plot based on the iris dataset
# note the name is the same as the outputid
serv_out[["iris_scatter"]] <- function(input,calc){
  renderPlot({
    if (!is.null(calc$sub.df)){
      # build scatterplot
      ggplot(calc$sub.df, aes(x, y, color = factor(species)))+
        geom_point()+ # make scatter
        ggtitle("Iris Comparisons")+ # add title
        xlab(input$x_axis)+ # change x axis label
        ylab(input$y_axis)+ # change y axis label
        labs(color="species")+ # change legend label
        NULL
    }
  })
}

Run Multi-Window Shiny App!

Now that we have all our pieces, we can just plug these in and run our app!

#run!
mwsApp(win_titles, ui_win, serv_calc, serv_out)

Note that multi-windowed apps work better when run in an external browser. Since I can’t show a multi-window shiny app in a vignette form, here are some images of the final result.

When you initially run mwsApp(), you’ll first be greeted by the selection window, where links to your separate windows are displayed:

If I click on each of my two windows (opening these windows in new tabs), I see the inital states of my controller and scatter windows:

Once I change some inputs and click “build!”, I can see that my scatter window updates accordingly:

It’s as simple as that! Now that you know make a multi-window shiny app, feel free to make some of your own!