Comparing Shiny with gWidgetsWWW2.rapache
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
(A guest post by John Verzani)
A few days back the RStudio blog announced Shiny, a new product for easily creating interactive web applications (http://www.rstudio.com/shiny/
I don’t want to worry here about deployment of apps, just the writing side. The shiny package uses websockets to transfer data back and forth from browser to server. Though this may cause issues with wider deployment, the industrious RStudio folks have a hosting program in beta for internet-wide deployment. For local deployment, no problems as far as I know – as long as you avoid older versions of internet explorer.
Now, Shiny seems well suited for applications where the user can parameterize a resulting graphic, so that was the point of comparison. Peter Dalgaard’s tcltk package ships with a classic demo tkdensity.R. I use that for inspiration below. That GUI allows the user a few selections to modify a density plot of a random sample.
gWidgetsWWW2
We’ll start with a gWidgets approach and work our way to the shiny code. A basic GUI can be generated with the following code:
width <- 600; height <- 480 ## The plot commands. Uses GUI objects defined below make_plot <- function(...) { y <- get(svalue(distribution))(svalue(size)) plot(density(y, bw=svalue(bandwidth)/100, kernel=svalue(kernel))) points(y, rep(0, svalue(size) )) } update_plot <- function(...) { f <- tempfile() require(canvas) canvas(f, width=width, height=height) make_plot() dev.off() svalue(cnv) <- f ## update canvas widget } ## The layout w <- gwindow("tkdensity example") gstatusbar("Powered by gWidgetsWWW2.rapache and rapache", cont=w) ## containers bl <- gborderlayout(cont=w) fl <- gformlayout(cont=bl, where="west") ## controls distribution <- gcombobox(data.frame(value=c("rnorm", "rexp"), labels=c("Normal", "Exponential"), stringsAsFactors=FALSE), cont=fl, label="distribution") kernel <- gcombobox(c("gaussian", "epanechnikov", "rectangular", "triangular", "cosine"), cont=fl, label="kernel") size <- gcombobox(c(5,50, 100, 200, 300), coerce.with="as.numeric", cont=fl, label="size") bandwidth <- gslider(0.05*100, 2*100, by=0.05*100, value=1*100, width=250, coerce.with="as.numeric", cont=fl, label="bandwidth") refresh <- gbutton("refresh", cont=fl, label="") cnv <- gcanvas(cont=bl, where="center", width=width, height=height) ## connect controls for(i in list(kernel, size, bandwidth, refresh)) addHandlerChanged(i, update_plot) |
All right, that may look like a lot, but lots of people have used gWidgets to write such GUIs. They aren’t that hard. basically there are three parts:
- The definition of the controls and their layout that comprise the
user interface. - The definition of some callback to create the graphic when a control
is updated. - Some means to specify when to invoke this call back.
The gWidgets API is meant to be cross-toolkit, but this is only mostly true. In the above we use a few web-specific features, including the canvas package to provide a JavaScript canvas for drawing R graphics.
Manipulate
This particular example would be really easy were we able to use RStudio’s manipulate package. That is only for RStudio users though. Well, not really. There is a simple map available in the gWidgetsWWW2.rapache package that allows us to easily use that specification. Here is the code:
## load in map for manipulate -> gWidgets source(system.file("demo", "manipulate.R", package="gWidgetsWWW2.rapache"), local=TRUE) w <- gwindow("Manipulate example") gstatusbar("Powered by gWidgetsWWW2.rapache and rapache", cont=w) manipulate( { y <- get(distribution)(size) plot(density(y, bw=bandwidth/100, kernel=kernel)) points(y, rep(0, size)) }, ## distribution=picker("Normal"="rnorm", "Exponential"="rexp"), kernel=picker("gaussian", "epanechnikov", "rectangular", "triangular", "cosine"), size=picker(5, 50, 100, 200, 300), bandwidth=slider(0.05 * 100, 2.00 * 100, step=0.05 * 100, initial=1* 100), # integers needed button=button("Refresh"), ## gWidgetsWWW2.rapache extras container=w, device="svg", # svg or canvas or png delay=1000 # delay to let data load for combo boxes ) |
Okay, I admit. I’m a fan of manipulate and it really is a perfect way to specify an interface as straightforward as this. This one is an example in the gWidgetsWWW2.rapache package and you can see it in action at http://www.math.csi.cuny.edu/
the controls.
Shiny
Now for Shiny. We noted above there are really three pieces to writing this app, with Shiny we only need to worry about two: the definition and layout of the user interface, and the specification of how the resulting graphic is made. For this, we write two functions: ui.R and server.R.
The ui.R files is used to layout the interface. Here is ours:
library(shiny) shinyUI(pageWithSidebar( # Application title headerPanel("tkdensity"), # Sidebar with a slider input for number of observations sidebarPanel( selectInput("distribution", "Distribution:", choices = c("Normal"="rnorm", "Exponential"="rexp")), selectInput("kernel", "Kernel:", choices = c("gaussian", "epanechnikov", "rectangular", "triangular", "cosine")), selectInput("size", "Size:", choices = c(5,50, 100, 200, 300)), sliderInput("bandwidth", "bandwidth:", min = 0.05, max = 2, value = 1) ), # Show a plot of the generated distribution mainPanel( plotOutput("distPlot") ) )) |
There are a few containers (“panels”) and a few controls specified (“inputs”). The basic usage of shiny involves just a handful of each and they are easy to learn. The above code simply makes a header, puts four controls into a side panel and in the main panel readies a place to display our plots. The name distPlot is odd because I stole this basic set up from one of shiny‘s examples and made just minor changes.
The server.R file is not difficult either. We need to make the graphic so that it displays. Here it is:
library(shiny) shinyServer(function(input, output) { make_plot <- function(distribution, size, bandwidth, kernel) { y <- get(distribution)(size) plot(density(y, bw=bandwidth, kernel=kernel)) points(y, rep(0, size)) } output$distPlot <- reactivePlot(function() { make_plot(input$distribution, input$size, input$bandwidth, input$kernel) }) }) |
Again, this is only slightly modified from an example. The input object is not a list or an environment, but we can extract the values associated with the user interface as though it were. In the above we see the distribution, size, bandwidth and kernel values are passed along to the make_plot call. The only new thing here, besides that, is the magic way we create a plot (passing a function as a call back to reactivePlot and assigning it to distPlot in the output object). The shiny package takes care of the third part of our GUI, using this “reactive” programming style to synchronize various parts of
the interface.
Want to see it at work? Well fire up your copy of shiny and run it from a GitHub gist:
require(shiny) shiny:::runGist("4009017") |
Before you gloss over the above lines, think how neat this is. Just as you can install packages from GitHub within an R session through the devtools package, you can run shiny apps within R from GitHub with the runGist function. This is an awesome feature for deploying interactive web apps for local usage.
I think you’ll see, if you compare, for this task the user experience is nicer with shiny than that of gWidgetsWWW2.rapache. The specification of the GUI is easiest with the manipulate style, but using shiny wasn’t difficult. I personally like very much the use of the “bootstrap” CSS libraries (http://twitter.github.com/
About:
John Verzani is the maintainer of the gWidgets set of packages and co-author with Michael Lawrence of the book Programming Graphical User Interfaces in R.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.