Graphical User Interfaces

1  Graphical User Interfaces

While using R through the command line provides a great deal of flexibility, there are times when a simpler interface would be useful. For example, you may develop an R program that performs a complex analysis, and you'd like others to be able to run the program by, say just specifying a file to be read, and choosing a couple of options through checkboxes or a drop down list. There may be a set of operations that your routinely do when first studying a data set, and you'd like to be presented with a list of available data, and just click on the one that you want.
There are several different options available in R to produce graphical user interfaces (GUIs); we are going to focus on a simple, portable one based on the Tcl/Tk library originally developed by John Ousterhout when he was in the Computer Science department here at Berkeley. Tcl is a scripting language (like perl or python), and Tk is a graphical toolkit designed to work with that language. Since the Tk toolkit is portable, easy-to-use, and provides most of the basic elements needed to create GUIs, interfaces to it are available in many languages, so if you understand the basics of Tk GUIs, you may see the same techniques used later on in your career by some other program. To use the Tcl/Tk library in R, you must load the library with the library(tcltk) command. This library is part of the base R distribution, so you don't have to download anything in order to use it (although you may choose to download some optional components later on.)
If you look at the help pages for the functions in the tcltk library, you'll probably be surprised to see how little information or guidance there is. However, once you work with the library for a while, you'll get accustomed to the way Tk functions are mapped to R functions, and you'll be able to use any documentation about Tk that you can find on the web or elsewhere to get more information. There are a wide variety of sample programs at http://www.sciviews.org/_rgui/tcltk/, but they use a different geometry method and some require external packages. I'll try to provide examples here to help you get started, but don't hesitate to look around the web for more examples and ideas.
The process of creating a GUI can be broken down into two parts: choosing the elements (often refered to as widgets) that will make up the GUI, and arranging for the elements to be placed in the appropriate positions and displayed. This second step is sometimes known as geometry management. Tk actually offers several different geometry management methods; we are going to focus on one known as packing. In the packing geometry model, the GUI is considered a collection of separate frames, each containing some of the widgets that make up the final GUI. The widgets are loaded into the frames in a carefully chosen order, and they "spread out" to fill the frame. When you pack a widget into a frame, you can specify the side to which to pack it, choosing among left, right, top or bottom. With a little practice and experimentation, you can usually get the widgets to line up the way you want them to.
Most of the widgets that are available through the Tk toolbox should look familiar, as common applications like email clients and web browser use these same elements in their GUIs. Some examples of available widgets are:
Since the Tk toolbox is not integrated entirely into R, it's necessary to use special techniques to get information out of the Tcl/Tk environment and into R. Many of the widgets, for example a text entry widget, will need a Tcl variable associated with them to hold necessary information. All of the variables that will be captured by the GUI will be character variables. The tclVar function can be used to create an empty tcl variable which can then be used in call to functions in the tcltk library as follows:
   myvar = tclVar('')

Later, when you need to get the information that was put into the Tk variable back into the R environment, you can use the tclvalue function:
   rmyvar = tclvalue(myvar)

Another aspect of GUI programming that is different from regular programs has to do with associating actions with certain GUI elements. An obvious example is a button, which when pressed needs to do something. Widgets like this will have an argument, usually called command= which will accept the name of a function that will be called when the button (or other action) is recognized. Such functions, often called callbacks, should be written using the ... notation for their argument lists, and should not have any returned values. It sometimes becomes necessary to "remember" things over the course of a GUI's execution. For example, you may want to count the number of times a button is pressed, or display a different message based on the value of a previous response. Since all of the actions which the GUI will initiate are carried out in functions, any modifications to variables that are made in those functions will be lost when the function returns. While not usually a good practice, you can use the special assignment operator <<- inside a function to modify global variables, which will retain their values after the function exits.
To make some of these concepts clearer, let's consider the construction of a GUI to accept input to the coin.power program that we developed earlier. Recall that the program accepts the number of simulations, number of coin tosses, probability of getting heads for the coin, and returns the power. We want to make a GUI with three entries, one for each of the parameters, a place to display the answer, and buttons to run the simulation or close the window. The first step in creating a GUI with the tcltk library in R is loading the library and calling the tktoplevel function, which creates an initial window into which we will pack the frames containing the widgets. Since we can control the order of packing into individual frames, each set of related elements can be put into a separate frame. For this example, we'll create a separate frame for each entry and its associated label, as well as a frame to hold those entries and another frame for the two buttons; the area for the answer can be packed into the toplevel frame.
In the code that follows, you'll notice that usually widgets are packed directly into their enclosing frames, but the label widget that will hold the answer is assigned to a variable before being packed. If you'll need to communicate with the widget at some point in the running of the program, it's vital to store the widget in a variable. In this example, we'll create a blank label, and use the tkconfigure function when we have an answer to change what the label displays. Here's the code to create the GUI:
require(tcltk)
coin.power = function(ntoss=100,nsim=1000,prob=.5){
     lower = qbinom(.025,ntoss,.5)
     upper = qbinom(.975,ntoss,.5)
     rr = rbinom(nsim,ntoss,prob)
     sum(rr < lower | rr > upper) / nsim
 }

destroy = function(...)tkdestroy(base)

tst = function(...){
   nsim = as.numeric(tclvalue(nsim_))
   ntoss = as.numeric(tclvalue(ntoss_))
   prob  = as.numeric(tclvalue(prob_))
   res = coin.power(ntoss=as.numeric(ntoss),nsim=as.numeric(nsim),prob=as.numeric(prob))
   tkconfigure(ans,text=paste(res))
}

base = tktoplevel()
tkwm.title(base,'Coin Toss')

# create a frame to hold the three entries
nfrm = tkframe(base)

# create tcl variables to associate with the 
# entry fields -- to include starting values
# replace the '' with the desired value
nsim_ = tclVar('')
ntoss_ = tclVar('')
prob_ = tclVar('')

f1 = tkframe(nfrm)
tkpack(tklabel(f1,text='Nsim',width=8),side='left')
tkpack(tkentry(f1,width=10,textvariable=nsim_),side='left')

f2 = tkframe(nfrm)
tkpack(tklabel(f2,text='Ntoss',width=8),side='left')
tkpack(tkentry(f2,width=10,textvariable=ntoss_),side='left')

f3 = tkframe(nfrm)
tkpack(tklabel(f3,text='Prob',width=8),side='left')
tkpack(tkentry(f3,width=10,textvariable=prob_),side='left')

# the widgets were packed in the frames, but the frames have
# not yet been packed.  Remember that nothing can actually be
# displayed unless it's been packed.
tkpack(f1,side='top')
tkpack(f2,side='top')
tkpack(f3,side='top')

tkpack(nfrm)

# now we can repeat the process for the label to hold the 
# answer, and the frame containing the buttons
ans = tklabel(base,text=' ')
tkpack(ans,side='top')

bfrm = tkframe(base)
tkpack(tkbutton(bfrm,text='Run',command=tst),side='left')
tkpack(tkbutton(bfrm,text='Quit',command=destroy),side='right')

tkpack(bfrm,side='bottom')

When we run the program (for example by using the source command inside of R), we see the left hand image; after making some entries and hitting the Run button, we see the image on the right.
        

2  Making a Calculator

The basic idea behind writing a calculator in R is very simple: create a set of buttons which will build up a text string that represents the calculation we wish to perform, and then use the eval and parse functions in R to perform the calculation. These two functions allow us to evaluate a character string as if we typed it into the R console. For example, consider the string "29*43". We could calculate the value of that expression as follows:
> txt = "29*43"
> eval(parse(text=txt))
[1] 1247

Using this as the core of the calculator, we can set up rows of buttons arranged in the usual calculator fashion, each of which will add a character to a character string through the command= option, and then call parse and eval to do the calculation when the "=" key is pressed. We need to use a few of the concepts we've already seen to make the calculator work. First, we will need to create a global variable that represents the input to the calculator, and have each of the keys add its input using the "<<-" operator. To change the value of the calculator's display, we'll use the tkconfigure function as in the previous example.
We could define the callback functions separately for each key, but most of the keys will do exactly the same thing - add a character to the string that we'll eventually calculate. The only difference is that different keys will naturally add different characters. So it would make sense to write a function to generate the different callbacks. Let's call the calculator's input calcinp. We initialize calcinp to an empty string, and then define a function which will take a symbol and add it to the calculator's input:
calcinp = ''

mkput = function(sym){
       function(...){
          calcinp <<- paste(calcinp,sym,sep='')
	  tkconfigure(display,text=calcinp)
          }
}

Notice that we're refering to an object called display, even though we haven't defined it yet. R uses a technique called lazy evaluation which means that an object doesn't have to exist when we use it in a function definition - it only needs to be available when the function is actually called. Keeping this in mind, we can define two more functions, one to clear the calculator's display and one to do the actual calculation:
clearit = function(...){
      tkconfigure(display,text='')
      calcinp <<- ''
      }

docalc = function(...){
      result = try(eval(parse(text=calcinp)))
      if(class(result) == 'try-error')calcinp <<- 'Error' else calcinp <<- result
      tkconfigure(display,text=calcinp)
      calcinp <<- ''
      }

A new concept that's introduced in the docalc function is the try function. This function allows us to try any operation in R, without worrying that an error will stop our program. If there's an error when we pass an R statement to the try function, it will return an object of class try-error. Thus we can decide what to when an error occurs by examining the class of the returned value.
Now we can build the calculator itself. The approach is to first pack the label which will serve as the calculator's display, then to create a separate frame for each row of keys, packing them as they are created:
base = tktoplevel()
tkwm.title(base,'Calculator')

display = tklabel(base,justify='right')
tkpack(display,side='top')
row1 = tkframe(base)
tkpack(tkbutton(row1,text='7',command=mkput('7'),width=3),side='left')
tkpack(tkbutton(row1,text='8',command=mkput('8'),width=3),side='left')
tkpack(tkbutton(row1,text='9',command=mkput('9'),width=3),side='left')
tkpack(tkbutton(row1,text='+',command=mkput('+'),width=3),side='left')
tkpack(row1,side='top')

row2 = tkframe(base)
tkpack(tkbutton(row2,text='4',command=mkput('4'),width=3),side='left')
tkpack(tkbutton(row2,text='5',command=mkput('5'),width=3),side='left')
tkpack(tkbutton(row2,text='6',command=mkput('6'),width=3),side='left')
tkpack(tkbutton(row2,text='-',command=mkput('-'),width=3),side='left')
tkpack(row2,side='top')

row3 = tkframe(base)
tkpack(tkbutton(row3,text='1',command=mkput('1'),width=3),side='left')
tkpack(tkbutton(row3,text='2',command=mkput('2'),width=3),side='left')
tkpack(tkbutton(row3,text='3',command=mkput('3'),width=3),side='left')
tkpack(tkbutton(row3,text='*',command=mkput('*'),width=3),side='left')
tkpack(row3,side='top')

row4 = tkframe(base)
tkpack(tkbutton(row4,text='0',command=mkput('0'),width=3),side='left')
tkpack(tkbutton(row4,text='(',command=mkput('('),width=3),side='left')
tkpack(tkbutton(row4,text=')',command=mkput(')'),width=3),side='left')
tkpack(tkbutton(row4,text='/',command=mkput('/'),width=3),side='left')
tkpack(row4,side='top')

row5 = tkframe(base)
tkpack(tkbutton(row5,text='.',command=mkput('.'),width=3),side='left')
tkpack(tkbutton(row5,text='^',command=mkput('^'),width=3),side='left')
tkpack(tkbutton(row5,text='C',command=clearit,width=3),side='left')
tkpack(tkbutton(row5,text='=',command=docalc,width=3),side='left')
tkpack(row5,side='top')

Here's what the calculator looks like in several stages of a computation:

3  Improving the Calculator

While the calculator is capable of doing basic operations, it's easy to incorporate any function in R which takes a single argument, by creating a callback using this function:
mkfun = function(fun){
       function(...){
          calcinp <<- paste(fun,'(',calcinp,')',sep='')
          tkconfigure(display,text=calcinp)
          }
}

So, for example, we could add the sqrt, exp, and qnorm functions to our calculator like this:
row6 = tkframe(base)
tkpack(tkbutton(row6,text='sqrt',command=mkfun('sqrt'),width=3),side='left')
tkpack(tkbutton(row6,text='exp',command=mkfun('exp'),width=3),side='left')
tkpack(tkbutton(row6,text='qnorm',command=mkfun('qnorm'),width=3),side='left')
tkpack(row6,side='top')

Here are screenshots of a qnorm calculation:

4  Appearance of Widgets

The previous example was a very minimalistic approach, with no concern with the actual appearance of the final product. One simple way to improve the appearance is to add a little space around the widgets when we pack them. The tkpack function accepts two arguments, padx= and pady= to control the amount of space around the widgets when they are packed. Each of these arguments takes a scalar or a vector of length 2. When a scalar is given, that much space (in pixels) is added on either side of the widget in the specified dimension. When a list is given the first number is the amount of space to the left (padx) or the top (pady), and the second number is the amount of space to the right (padx) or the bottom (pady). Like most aspects of GUI design, experimentation is often necessary to find the best solution. The following code produces a version of the coin power GUI with additional padding:
base = tktoplevel()
tkwm.title(base,'Coin Toss')

nfrm = tkframe(base)

nsim_ = tclVar('')
ntoss_ = tclVar('')
prob_ = tclVar('')

f1 = tkframe(nfrm)
tkpack(tklabel(f1,text='Nsim',width=8),side='left',pady=c(5,10))
tkpack(tkentry(f1,width=10,textvariable=nsim_),side='left',padx=c(0,20),pady=c(5,10))

f2 = tkframe(nfrm)
tkpack(tklabel(f2,text='Ntoss',width=8),side='left',pady=c(5,10))
tkpack(tkentry(f2,width=10,textvariable=ntoss_),side='left',padx=c(0,20),pady=c(5,10))

f3 = tkframe(nfrm)
tkpack(tklabel(f3,text='Prob',width=8),side='left',pady=c(5,10))
tkpack(tkentry(f3,width=10,textvariable=prob_),side='left',padx=c(0,20),pady=c(5,10))

tkpack(f1,side='top')
tkpack(f2,side='top')
tkpack(f3,side='top')

tkpack(nfrm)

ans = tklabel(base,text=' ')
tkpack(ans,side='top')

bfrm = tkframe(base)
tkpack(tkbutton(bfrm,text='Run',command=tst),side='left')
tkpack(tkbutton(bfrm,text='Quit',command=destroy),side='right')

tkpack(bfrm,side='bottom',pady=c(0,10))

Here are side-by-side pictures showing the difference between the two versions:
        
Another way to modify a GUI's appearance is through three-dimensional effects. These are controlled by an argument called relief=, which can take values of "flat", "groove", "raised", "ridge", ßolid", or ßunken". The following code produces a (non-functioning) GUI showing the different relief styles applied to labels and buttons:
require(tcltk)
types = c('flat','groove','raised','ridge','solid','sunken')

base = tktoplevel()
tkwm.title(base,'Relief Styles')

frms = list()

mkframe = function(type){
    fr = tkframe(base)
    tkpack(tklabel(fr,text=type,relief=type),side='left',padx=5)
    tkpack(tkbutton(fr,text=type,relief=type),side='left',padx=5)
    tkpack(fr,side='top',pady=10)
    fr
}

sapply(types,mkframe)

Here's a picture of how it looks:

5  Fonts and Colors

Several arguments can be used to modify color. The background= argument controls the overall color, while the foreground= argument controls the color of text appearing in the widget. (Remember that things like this can be changed while the GUI is running using the tkconfigure command. Each of these arguments accepts colors spelled out (like "lightblue", "red", or "green") as well as web-style hexadecimal values (like "#add8e6", "#ff0000" or "#00ff00").
To change from the default font (bold Helvetica), you must first create a Tk font, using the tkfont.create function. This function takes a number of arguments, but only the font family is required. The available font families will vary from platform to platform; the as.character(tkfont.families()) command will display a list of the available fonts. Some of the other possible arguments to tkfont.create include:
  1. size=, the size of the font in points.
  2. weight=, either "normal" or "bold"
  3. slant=, either "roman" or ïtalic"
  4. underline=, either TRUE or FALSE
  5. overstrike=, either TRUE or FALSE
As mentioned earlier, colors can be specified as either names or web-style hexadecimal values. Generally, the argument to change the color of a widget is background=; the argument to change the color of any text appearing in the widget is foreground=. If these choices don't do what you expect, you may need to check the widget-specific documentation.
Here's a simple program that chooses some randomly selected colors and fonts, and displays them as labels.
require(tcltk)

allfonts = as.character(tkfont.families())
somefonts = sample(allfonts,9)

somecolors = c('red','blue','green','yellow','lightblue','tan',
'darkred','lightgreen','white')

txt = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

base = tktoplevel()
tkwm.title(base,'Fonts')
mkfonts = function(font,color){
      thefont = tkfont.create(family=font,size=14)
      tkpack(tklabel(base,text=paste(font,':',txt),
                    font=thefont,background=color),
                    side='top')
}

mapply(mkfonts,somefonts,somecolors)

Here's how it looks:
Note that only the specific widget for which background= was set changes - if you want to change the background for the entire GUI, you'll probably have to pass the background= argument to every widget you use.


File translated from TEX by TTH, version 3.67.
On 8 Apr 2011, 15:13.