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.
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
##
##
##
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).
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
})
}
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
}
})
}
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!