Dynamic Graphics in R

Phil Spector
Statistical Computing Facility, UC Berkeley

1  Introduction

The tcltk package of the R programming language gives you simple access to the Tcl/Tk toolkit originally developed by John Ousterhout. This toolkit provides a variety of widgets, and has been ported to many computer platforms. Interfaces to the toolkit appear in a number of programming languages including perl and python, so learning some Tcl/Tk commands may prove useful in other settings as well as within R. Since R has its own plotting subsystem, there's no need to worry about actually creating graphics through Tcl/Tk; the toolkit is only used to produce controls, not to draw the actual graphs. There are two approaches to plotting with the tcltk package: the first uses R's usual graphics window, with any controls being displayed in a separate window, while the second displays your plot as an image in a window that can contain other graphical elements.
The remainder of this paper will present some basic concepts which you may find useful when getting started with the tcltk package. The final section contains a complete example of a function that allows you to dynamically vary the span of the super smoother algorithm, and to display that algorithm's default smooth.

2  Some Tk basics

The various controls that might appear as part of a dynamic graphics display are known collectively as "widgets". Some examples of widgets are radiobuttons, scales, simple pushbuttons, listboxes, etc. The basic widget in Tk is the frame, which is essentially a container for any other widgets you wish to use. You can have as many frames as you want, but each individual entity which appears on the screen must be in a "top level" frame, created with the tktoplevel function. When using Tk, you first create a widget (specifying its appearance, the command to be executed when the widget is activated, etc.), and then you specify how it should appear in its parent frame. While Tk provides a number of different schemes for achieving this, we'll discuss the technique known as "packing". The basic idea is to group together widgets (and accompanying labels, if appropriate) into a series of frames, and then to call the tkpack function to put the widgets in the frames, then to finally pack the frames into a main frame which is contained in a top-level widget. The tkpack function takes the parent frame as its first argument and the frame to be packed as the second argument. By default, objects are backed into a frame from top to bottom, with each object being centered within its parent frame. You can control how widgets get packed into a frame by the order in which you call tkpack, and the optional side= argument which can take the values "left", "right", "top" or "bottom". To align packed widgets, you can specify an anchor= argument, which can take values representing compass directions like "n", "e", "sw", etc.
When a button is pressed or a slider is moved, you may want to call a function as a result of the event. Such functions are know as callbacks. To specify a callback, you pass a function as the command= argument when first creating the widget. For simple tasks (like a quit button), you can use an anonymous function, but one useful strategy in preparing dynamic graphics using the tcltk package is to write a set of functions which you want controlled by your widgets, and simply use those function names as the command= argument. Functions created for this do not accept arguments, so you may need to use frame 0 variables, i.e. variables set using the <<- operator in place of <-, to keep track of information. In addition, it may be necessary to use "..." as the argument list of your callback functions.
In addition, there are a wide range of events (key presses, mouse clicks, hitting return, moving the mouse in or out of a widget's space, etc.) which can be associated with commands of your choice. The process of associating an event with an action is known as binding, and is implemented in R through the tkbind function, providing three arguments: the widget, the event, and the action to be performed. Events are specified as a character variable with the event's name surrounded by angle brackets. A small sampling of events is listed below; refer to a manual on the X window system or Tk for more information. (In the list that follows, the letter "x" is used to represent any key on the keyboard.)
<Return>                  <FocusIn>
<Key-x>                   <FocusOut>
<Alt-x>                   <Button1>, <Button2>, etc.
<Control-x>               <ButtonRelease1>, <ButtonRelease2>, etc.
<Destroy>                 <Double-Button1>, <Double-Button2>, etc.
                          <Triple-Button1>, <Triple-Button2>, etc.

Finally, to change properties of widgets after they are created, you can use the tkconfig function. You call this function with the widget whose properties you wish to change, followed by a comma-separated list of named arguments corresponding to the properties you wish to change. In practice, you need to follow a call to tkconfig with a call to tcl('update') in order to actually see the changes.

3  Some Common Widgets

Note:  Each of the widget functions takes, as its first argument, the parent frame into which it will eventually be packed. In addition, most widgets accept height= and width= arguments to specify their size, but, when using the tkpack function, there's usually no need to provide this information. Any arguments mentioned in the following subsections appear after the parent frame.

3.1  Frames: tkframe

The tkframe function creates a frame, which serves as the building block of all Tk applications. You can modify the frame's appearance through arguments such as borderwidth, background (to specify background color), and relief (which takes a value from among "groove", "flat", "raised", "ridge", "solid", or "sunken").

3.2  Labels: tklabel

The tklabel function is used to produce a simple text label. By packing a label into a frame before packing some other widget, you can put an identifying label on a widget. In addition to the argument text, which specifies the text to appear in the label, you can specify font, and size to control the appearance of the text, and, background and foreground to control the color of the label.

3.3  Buttons: tkbutton

To create a pushbutton which, when activated, will carry out some command, use the tkbutton function. This function accepts an argument text, specifying the text to appear on the button, as well as command, which is a function that will be executed when the button is pressed.

3.4  Sliders: tkscale

You can create a slider to change the value of a variable using the tkscale function. Among its arguments are from, to and resolution, which control the values which will be set; showvalue, which, if set to TRUE will display the value of the variable above the slider, and orient which takes values "horizontal" or "vertical". You specify the name of a Tcl variable to be controlled by the slider through the argument variable, and a function to be called each time the variable's value changes through the argument command. Note that the variable controlled by the slider is a Tcl variable, not an R variable. This means that you must use the tclvar function to create the variable (passing an optional initialization value if desired), and the tclvalue function to access its value.

3.5  Radiobuttons: tkradiobutton

To provide a set of choices where only one choice can be active at any given time, use the tkradiobutton function. Each radio button is created and packed separately, but should specify the same variable= argument (similar to the variable argument to tkscale) as other radiobuttons in the same group. The value argument to tkradiobutton provides the value that the specified variable should take when the particular button is selected. If a command argument is specified, it will be executed any time the button is selected; without such an argument, you would need at least one widget somewhere in your application that specified some command to be executed.

3.6  Checkbuttons: tkcheckbutton

Checkbuttons are like radio buttons, but more than one choice among a set of buttons is allowable. Arguments are similar to those of tkradiobutton, but you need to specify a separate Tcl variable for each checkbutton you produce. Since these names are character strings, they can be generated very easily using the paste function. However, the tclvar variable mentioned previously is a psuedo-list, and does not support subscripting. Suppose you wish to access a Tcl variable whose name in stored in a variable called varname. (Note that the Tcl variable's name is not varname; it's name is simply stored in that variable.) You would need to evaluate an expression like eval(parse(text=paste("tclvar$",varname,sep="")) in order to retrieve the value of the variable.

3.7  Text Entry: tkentry

To allow for the entry of arbitrary text, use the tkentry function. The width argument specifies the width of the entry field, and the textvariable argument specifies the name of a Tcl variable to contain the entered text.

4  Example 1: Interaction Counter

This very simple example creates a frame with a label, and updates the label each time an iteration of a loop is carried out.
require(tcltk) || stop("tcltk support is absent")
base <- tktoplevel()
tkwm.title(base,'Label Test')
frm <- tkframe(base)

lab <- tklabel(frm,text="  ",width=20)
tkpack(frm,lab)

res <- matrix(0,10,10)
for(i in 1:10){
  tkconfigure(lab,text=paste(i))
  tcl('update')
  for(j in 1:10){
      z <- rnorm(15000)
      res[i,j] <- mean(z)
    }
}

5  Example 2: Histogram Maker

This example produces a frame with a text entry field to allow entering a data frame name. When the return key is hit inside the entry field, the frame expands to include checkbuttons for each variable, a button to create the histograms, and another button to quit. The appearance of the frame, both before and after hitting return inside the text entry area, is shown in Figure 1
Figure 1: Histogram maker
  require(tcltk) || stop("tcltk support is absent")

  makebuttons <- function(...){
    df <- get(tclvalue(dataframe))
    varfr <- tkframe(infrm)
    k <- 1
    for (n in names(df)){
      assign(paste("X",k,sep=""),tclVar(""),pos=1)
      tkpack(varfr,tkcheckbutton(varfr,text=n,variable=get(paste("X",k,sep="")),anchor='w'))
      k <- k + 1
    }
    tkpack(varfr,infrm)

    bfrm <- tkframe(infrm)
    tkpack(bfrm,tkbutton(bfrm,text='Run',command=doit),side='left')
    tkpack(bfrm,tkbutton(bfrm,text='Quit',command=function()tkdestroy(base)))
    tkpack(bfrm,infrm)
           
    
  }

  doit <- function(...){
    df <- get(tclvalue(dataframe))
    use <- NULL
    for(i in 1:dim(df)[[2]]){
      thevar <- get(paste("X",i,sep=""))
      check <- tclvalue(thevar)
      if(check == '1')use <- c(use,i)
    }
    par(ask=T)
    for(i in use){
      hist(df[,i],main=names(df)[i])
    }
    par(ask=F)
  }

  base <- tktoplevel()
  tkwm.title(base,'Histogram Maker')
  frm <- tkframe(base)

  infrm <- tkframe(frm)
  tkpack(infrm,tklabel(infrm,text="Data Frame: "),side='left',anchor='n')
  dataframe <- tclVar("")
  dfentry <- tkentry(infrm,width=20,textvariable=dataframe)
  tkbind(dfentry,"<Return>",makebuttons)
  tkbind(dfentry,"<Destroy>",function()print('Hello world'))
  tkpack(infrm,dfentry)

  tkpack(frm,infrm)



6  Example 3: Slider Control for Supersmoother

This example consists of a function which creates a slider to change the smoothing parameter sent to the R function supersmu. In addition, a button is provided to display supersmu's default smoothing of the data as a dotted line. Note the order in which graphing commands are executed: by calling plot with arguments which don't vary, we make sure that the axes of the graph don't change. The appearance of the frame containing the slider and the buttons is shown in Figure 2.
Figure 2: Supersmoother Control
supsmufn <- function(x,y){

  library(modreg)
  require(tcltk) || stop("tcltk support is absent")
  supsmufn.auto <<- 0

  chngplt <- function(...){
    span <- as.numeric(tclvalue(span))
    if(span == 0)span <- 1e-6
    smth <- supsmu(x,y,span=span)
    plot(x,y)
    lines(smth$x,smth$y)
    if(supsmufn.auto == 1)dobaseplot()
      
  }

  baseplot <- function(...){
    if(supsmufn.auto == 0)supsmufn.auto <<- 1
    else supsmufn.auto <<- 0

    if(supsmufn.auto)dobaseplot(x,y)
  }

  dobaseplot <- function(...){
    smth <- supsmu(x,y)
    lines(smth$x,smth$y,lty=2)
  }

  base <- tktoplevel()
  tkwm.title(base,"Super Smoother")

  mainfrm <- tkframe(base,borderwidth=2)

  slide <- tkframe(mainfrm,relief="groove",borderwidth=2)
  tkpack(tklabel(slide,text="Span"))

  span <- tclVar(0)
  tkpack(tkscale(slide,command=chngplt,from=0,to=1.0,showvalue=T,
                 variable=span,resolution=.01,orient="horiz"))
  tkpack(mainfrm,slide)
 

  butfrm <- tkframe(mainfrm,borderwidth=2,relief="groove")
  a.but <- tkbutton(butfrm,command=baseplot,text="Auto")
  q.but <- tkbutton(butfrm,text="Quit",command=function()tkdestroy(base))
  tkpack(butfrm,a.but,side="left")
  tkpack(butfrm,q.but,side="right")
  tkpack(mainfrm,butfrm)
}

7  Using tkrplot for plotting

As mentioned in the introduction, a second method for plotting allows placing controls in the same window as the plot itself. The tkrplot package provides the necessary functions to create an image representing your plot, and to automatically redraw it when necessary.
To use the tkrplot library, a tkrplot widget is created using the tkrplot function: this function accepts two arguments: the frame in which the plot is to be displayed, and the name of a function (with ... as its only argument) which will do the actual plotting.
To illustrate, consider a GUI consisting of a plotting window displaying the smoothed density of a vector of data, with a slider directly below the plot window to control the smoothing bandwidth. First, we create a toplevel window, a variable to hold the width, and write a function that will create the appropriate display:
tt = tktoplevel()
width = tclVar(.5)
plotdens = function(...){
   wid = as.numeric(tclvalue(width))
   plot(density(x,bw=wid))
}

Next, we create the tkrplot widget using this function, and create a trivial updating function that simply calls tkrreplot:
img = tkrplot(tt,plotdens)
densplot = function(...)tkrreplot(img)

Finally we create a slider to call the updating function when its value is changed, and pack all the widgets:
scl = tkscale(tt,command=densplot,from=0.1,to=2,showvalue=TRUE,
              variable=width,resolution=0.1,orient='horiz')
tkpack(img,side='top')
tkpack(scl,side='top')

The GUI is illustrated below using a mixture of normal distributions, and two different bandwidths.
Figure 3: Density Smoother



File translated from TEX by TTH, version 3.67.
On 5 Mar 2008, 09:04.