Development

Deducer comes with facilities for the easy building of full-featured GUI dialogs, which can be accessed by writing Java code, and including it in your package. For simple dialogs, these facilities can also be accessed from R without the necessity of delving into Java.

This wiki guide will serve as a vignette for the DeducerPlugInExample package, which implements both pure R dialogs, and Dialogs built in Java using Deducer's GUI components.


Additional Resources:

Project JavaDocs | Bugs | Feature Requests


Contents

  1. Working with Java Objects in R
  2. Building a GUI from R
    1. Creating a Dialog Window
    2. The Widgets
    3. Example: Factor Analysis dialog
    4. Check and Run Functions
    5. Adding to the Menu System
    6. Wrapping it up into a Package
    7. Responding to User Actions: Adding Listeners
      1. Available listeners
      2. The RDialogMonitor
    8. Adding a Sub-dialog
  3. Setting Up a Package to Include Java Code
  4. Building a GUI from Java
    1. The RDialog Class
    2. Widgets
    3. Example: Scatter plot dialog
  5. GUI Design Considerations
    1. Thoughts on GUI
    2. Thoughts on Analysis

Working with Java objects in R

The package rJava provides an easy to use mechanism to call Java objects and methods from R. This Chapter will show you how to build, inspect, and manipulate Java objects without leaving the comfort of R. Particular focus will be paid to GUI objects.

Objects in Java

Java is an object oriented language, meaning that almost everything is an object, and all functions (i.e. methods) are linked to objects. This is in contrast to R, which is primarily a function oriented language.

A class is a template used to create a specific type of object. For example I might have a class called Chair. This class is not an object it's self, but rather contains the recipe for creating a chair. This recipe is called a constructor, and when called creates a Chair object. It can be called many times to create a series of chairs (e.g. this chair, and that chair over there). Each one of these chairs is called an 'instance of' the original Chair class.

Classes can also be specific subtypes of other Classes. For example, our Chair class might be a subtype of the Furniture class, because all Chairs are Furniture. So when we create a new Chair object, it is also a Furniture object.

Functions in Java

Functions are called methods in Java, and are tethered to a specific class. Methods can only be used with objects of their own class. For example, a Cheetah class might have a method run which would make the cat run, but we wouldn't be able to call that method to make a Chair run. methods can be called on subclasses though, so I would be able call a Furniture method on a Chair.

A class can also have methods are called on the class (not an instance), and which don't require an instance to run. These are called 'static methods' and can be called using the class even if no objects have been created by that class. For example, our Cheetah class might have a method getPopulation which would return the number of Cheetahs in the wild (i.e. the number of instances). This method relates to Cheetahs, but doesn't relate to any specific Cheetah, so it is a static method.

Working with Java in R

Hopefully the above sounded very familiar to you. R has an object system similar to Java called S4. Though you don't need to know to much about it, we will be using the S4 object system to work with Java classes and objects.

Getting Started

Lets start by making a JDialog object. JDialog is a class used to make a dialog window, and is a part of the Swing GUI library.

The first thing we need to do is create a variable representing the class. This can be done with the J function, which takes as an argument the class location, and returns a reference to that location (an S4 object of class jclassName). JDialog is located in the javax.swing Java package, so we simply need to call.

> JDialog <- J("javax.swing.JDialog")
> print(JDialog)
[1] "Java-Class-Name: javax.swing.JDialog"

Now that we have a reference to the JDialog class, we can a new object by calling the new function. new takes as its first argument a jclassName (e.g. JDialog), and any further arguments are passed to the JDialog constructor.

> myDialog <- new(JDialog)

myDialog is now a reference (of S4 class jobjRef) to an instance of JDialog. We don't see anything yet, because we have not made the dialog visible. There is a JDialog method called setVisible which we can use to make the dialog visible. Set visible takes a boolean (true/false) as a parameter. rJava transparently takes care of the conversion between R logical and Java boolean. We will go into conversions later on.

> myDialog$setVisible(TRUE)

You should now see a small empty dialog window.

looking up constructors and methods


"But wait" you say! This all seems sort of magical.

  1. How did we know that there was a class called javax.swing.JDialog?
  2. How did we know that there was a constructor that took no arguments?
  3. How did we know there was a method called setVisible

Well, number 1 is sort of hard. Java is a big language, with lots of libraries and packages. If you are looking for something in plain Java, you can start by looking through the Java API JavaDocs. For things related to JGR, rJava and Deducer, you can look through the nightly generated Java docs.

Numbers 2 and 3 are easier to answer. we can list the available constructors with the .jconstructors function.

> .jconstructors(JDialog)
 [1] "public javax.swing.JDialog(java.awt.Frame) throws java.awt.HeadlessException"                                                         
 [2] "public javax.swing.JDialog(java.awt.Frame,boolean) throws java.awt.HeadlessException"                                                 
 [3] "public javax.swing.JDialog(java.awt.Frame,java.lang.String) throws java.awt.HeadlessException"                                        
 [4] "public javax.swing.JDialog(java.awt.Frame,java.lang.String,boolean) throws java.awt.HeadlessException"                                
 [5] "public javax.swing.JDialog(java.awt.Frame,java.lang.String,boolean,java.awt.GraphicsConfiguration)"                                   
 [6] "public javax.swing.JDialog() throws java.awt.HeadlessException"                                                                       
 [7] "public javax.swing.JDialog(java.awt.Dialog,boolean) throws java.awt.HeadlessException"                                                
 [8] "public javax.swing.JDialog(java.awt.Dialog,java.lang.String) throws java.awt.HeadlessException"                                       
 [9] "public javax.swing.JDialog(java.awt.Dialog,java.lang.String,boolean) throws java.awt.HeadlessException"                               
[10] "public javax.swing.JDialog(java.awt.Dialog,java.lang.String,boolean,java.awt.GraphicsConfiguration) throws java.awt.HeadlessException"
[11] "public javax.swing.JDialog(java.awt.Dialog) throws java.awt.HeadlessException"  

We can see that previously we called constructor number 6, which takes no arguments. Additionally a JDialog can be created with parameters representing a parent window (Dialog or Frame), a boolean representing whether the dialog is modal (blocking), and/or a String title. It can also take a graphics configuration, but it is rare that you would ever want to do that. So any of the follow commands can make a JDialog:

> JFrame <- J("javax.swing.JFrame")
> aFrame <- new(JFrame)
> anotherDialog <- new(JDialog,aFrame)
> anotherDialog <- new(JDialog,aFrame,TRUE)
> anotherDialog <- new(JDialog,aFrame,"title")
> anotherDialog <- new(JDialog,aFrame,"title",FALSE)
> anotherDialog <- new(JDialog)
> anotherDialog <- new(JDialog,myDialog)
> anotherDialog <- new(JDialog,myDialog,"title")
> anotherDialog <- new(JDialog,myDialog,"title",TRUE)

To look up what methods are available we use the .jmethods function.

> .jmethods(myDialog)
  [1] "public void javax.swing.JDialog.remove(java.awt.Component)"                                                                                 
  [2] "public void javax.swing.JDialog.update(java.awt.Graphics)"                                                                                  
  [3] "public javax.accessibility.AccessibleContext javax.swing.JDialog.getAccessibleContext()"                                                    
  [4] "public void javax.swing.JDialog.setLayout(java.awt.LayoutManager)"                                                                          
  [5] "public javax.swing.JRootPane javax.swing.JDialog.getRootPane()"                                                                             
  [6] "public void javax.swing.JDialog.setContentPane(java.awt.Container)"                                                                         
  [7] "public java.awt.Container javax.swing.JDialog.getContentPane()"                                                                             
  [8] "public void javax.swing.JDialog.setLayeredPane(javax.swing.JLayeredPane)"                                                                   
  [9] "public javax.swing.JLayeredPane javax.swing.JDialog.getLayeredPane()"                                                                       
 [10] "public void javax.swing.JDialog.setGlassPane(java.awt.Component)"                                                                           
 [11] "public java.awt.Component javax.swing.JDialog.getGlassPane()"                                                                               
 [12] "public void javax.swing.JDialog.setDefaultCloseOperation(int)"                                                                              
 [13] "public int javax.swing.JDialog.getDefaultCloseOperation()"                                                                                  
 [14] "public void javax.swing.JDialog.setJMenuBar(javax.swing.JMenuBar)"                                                                          
 [15] "public javax.swing.JMenuBar javax.swing.JDialog.getJMenuBar()"                                                                              
 [16] "public static void javax.swing.JDialog.setDefaultLookAndFeelDecorated(boolean)"                                                             
 [17] "public static boolean javax.swing.JDialog.isDefaultLookAndFeelDecorated()"    
...
...
...      

The first 17 of 297 methods are displayed above.

We can also view the publicly accessible fields of an object.

> .jfields(myDialog)
 [1] "public static final int javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE"
 [2] "public static final int javax.swing.WindowConstants.HIDE_ON_CLOSE"      
 [3] "public static final int javax.swing.WindowConstants.DISPOSE_ON_CLOSE"   
 [4] "public static final int javax.swing.WindowConstants.EXIT_ON_CLOSE"      
 [5] "public static final float java.awt.Component.TOP_ALIGNMENT"             
 [6] "public static final float java.awt.Component.CENTER_ALIGNMENT"          
 [7] "public static final float java.awt.Component.BOTTOM_ALIGNMENT"          
 [8] "public static final float java.awt.Component.LEFT_ALIGNMENT"            
 [9] "public static final float java.awt.Component.RIGHT_ALIGNMENT"           
[10] "public static final int java.awt.image.ImageObserver.WIDTH"             
[11] "public static final int java.awt.image.ImageObserver.HEIGHT"            
[12] "public static final int java.awt.image.ImageObserver.PROPERTIES"        
[13] "public static final int java.awt.image.ImageObserver.SOMEBITS"          
[14] "public static final int java.awt.image.ImageObserver.FRAMEBITS"         
[15] "public static final int java.awt.image.ImageObserver.ALLBITS"           
[16] "public static final int java.awt.image.ImageObserver.ERROR"             
[17] "public static final int java.awt.image.ImageObserver.ABORT" 

Calling methods

We have already seen how to call the setVisible method on out JDialog. Calling other functions is similar. Our Dialog is pretty small right now. In fact, its size is 0. Lets give it some length and width, as well as a title.

> myDialog <- new(JDialog)
> myDialog$setTitle("cool dialog")
> myDialog$setSize(200L,500L)
> 
> JLabel <- J("javax.swing.JLabel")
> label <- new(JLabel,"Hi there, I'm a label")
> myDialog$add(label)
[1] "Java-Object{javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Hi there, I'm a label,verticalAlignment=CENTER,verticalTextPosition=CENTER]}"
> 
> 
> myDialog$setVisible(TRUE)
> myDialog$isVisible()
[1] TRUE

You should now see a window 200 pixels wide and 500 pixels long titled "cool dialog." We've also added a text label to the content of the window. Notice that we used 200L for the argument to setSize. This is because the L in R indicates that the number is an integer. Otherwise it would be numeric, which would then be converted to a Java double which the method doesn't understand.

Static methods can be called either on the class or an object, though it is recommended that the class be used.

> JDialog$isDefaultLookAndFeelDecorated()
[1] FALSE

Fields (variables specific to a particular Java class or object) can also be accessed in a natural way.

> JDialog$EXIT_ON_CLOSE
[1] 3

Constructor and method argument conversion

You may have noticed that we didn't actually pass any Frames or Dialogs to the constructor in the .jconstructors example above. Rather we gave it JFrames and JDialogs. This is okay because they are subclasses of Frame and Dialog. We can check this using the instanceof operator

> myDialog %instanceof% J("java.awt.Dialog")
[1] TRUE
> aFrame %instanceof% J("java.awt.Frame")
[1] TRUE

rJava automatically takes care of the class conversions (called casting) without any need for you to worry about it. Indeed, it works the other way too. If a java method's signature says that it returns an object of class Frame, but the object is actually a JFrame, it is automatically promoted to a JFrame and can be used as such. In some ways this could be considered an improvement on Java which normally requires you to handle the casting yourself.

Some R data types can be automatically converted to Java types when given to a method or constructor.

R vector (length>1)Java
numericdouble[]
integerint[]
characterjava.lang.String[]
logicalboolean[]
R vector (length==1)Java
numericdouble
integerint
characterjava.lang.String
logicalboolean

An R object can be converted to other basic Java data types with .jfloat, .jlong, .jbyte, .jchar and .jshort. If an R vector of length 1 needs to be passed as an array, simply wrap it in a .jarray call.

> .jfloat(1)
An object of class "jfloat"
[1] 1
> .jlong(1)
An object of class "jlong"
[1] 1
> .jbyte(1)
An object of class "jbyte"
[1] 1
> .jchar(1)
An object of class "jchar"
[1] 1
> .jshort(1)
An object of class "jshort"
[1] 1
> .jarray(1)
[1] "Java-Array-Object[D:[D@287a3"

Where to go from here...

You can do quite a bit with the above. A good place to start if/when you run into trouble is the rJava Documentation. If that fails and it is an rJava related problem, try the mailing list.

Building a GUI from R

DeducerPlugInExample includes a factor analysis dialog written completely from within R. Creating simple dialogs is relatively easy, and Deducer provides an easy way to add them to the menu system. While all of the Swing GUI library is available to you when building your interface, Swing is a very general framework, designed to meet all user interface needs. Dialogs for statistical analysis and data manipulation are a very specific type of GUI, and as such, Deducer provides a number of easy to use Dialog windows and components. In the future, more components and features will become available as the system matures.

GUI Classes added by Deducer

Deducer provides the following class definitions

R VariableClassDescription
DeducerMainorg.rosuda.deducer.widgets.DeducerDeducer's main class
RDialogorg.rosuda.deducer.widgets.RDialogA dialog to put widgets in
SimpleRDialogorg.rosuda.deducer.widgets.SimpleRDialogA window for building simple dialogs in R
VariableSelectorWidgetorg.rosuda.deducer.widgets.VariableSelectorWidgetFilterable list of the variables in a data frame
VariableListWidgetorg.rosuda.deducer.widgets.VariableListWidgetA list of variables selected from a VariableSelectorWidget
SingleVariableWidgetorg.rosuda.deducer.widgets.SingleVariableWidgetA list of a single variable selected from a VariableSelectorWidget
ButtonGroupWidgetorg.rosuda.deducer.widgets.ButtonGroupWidgetA group of radio buttons
CheckBoxesWidgetorg.rosuda.deducer.widgets.CheckBoxesWidgetA group of check boxes
SliderWidgetorg.rosuda.deducer.widgets.SliderWidgetA slider
TextAreaWidgetorg.rosuda.deducer.widgets.TextAreaWidgetAn area to input text
TextFieldWidgetorg.rosuda.deducer.widgets.TextFieldWidgetAn integer, numeric, or text field
ListWidgetorg.rosuda.deducer.widgets.ListWidgetA list of items
ComboBoxWidgetorg.rosuda.deducer.widgets.ComboBoxtWidgetA combo box
ObjectChooserWidgetorg.rosuda.deducer.widgets.ObjectChooserWidgetSelect an object from the workspace, possibly of a specific class
JLabeljavax.swing.JLabelA text label

Java Documentation

Creating a Dialog Window

The first step in creating a GUI is to make a window. The SimpleRDialog class will take care of many of the details for you, and can be used without the need to jump into Java.

	testDialog <- new(SimpleRDialog)
	testDialog$setSize(300L,600L)

The above code, made a new window, then set its size 300 pixels wide and 600 pixels long. Notice that we used 300L instead of 300, this is because setSize is expecting an integer. If we then called dialog@setVisible(TRUE), we would see:

Adding a widget

Now let's add some slider widgets to the dialog. There are many ways to layout components in a window, and each of them performs differently when the window is resized. SimpleRDialog uses AnchorLayout by default. If we want to specify the vertical position of part of a component, this is measured by the distance from the top of the window. with 1 being at the top, 500 the middle, and 1000 the bottom. Similarly, horizontal distance is measured from the left, with 1 being on the left edge, and 1000 being on the right edge.

The addComponent function is used to add a component to a window.

	slider1 <- new(SliderWidget, "Right: REL")
	addComponent(testDialog, slider1, 1, 500, 200, 1)

This code creates a new slider, and adds it to the window. The top of the slider is set to 1 (i.e. the top of the window). Its right side is set to 500 (i.e. halve way across the window). Its bottom is set to 200 (i.e. 20% of the way down). Its left hand side is set to 1 (i.e. on the left edge of the window).

addComponent also has 4 optional parameters (topType ,rightType, bottomType, leftType) which control the behavior of the component under resizing. By default these are set to "REL" which indicates that components will be scaled relative to the size of the window. "ABS" indicates that the side should preserve the actual distance between it and the side of the window. "NONE" indicates that the position of the side should be determined by the preferred size of the component.

Let us add two new sliders identical to the first except for the scaling behavior of the right edge.

slider2<- new(SliderWidget, "Right: ABS")
addComponent(testDialog, slider2, 250, 500,450, 1,rightType="ABS")

slider3<- new(SliderWidget, "Right: NONE")
setSize(slider3,150,100)
addComponent(testDialog, slider3, 500, 500,700, 1,rightType="NONE")

The sliders all look the same size, except when we resize the window we get different results.

The top component gets rescaled proportional to the size of the dialog. The second one preserves the absolute distance between its right side and the right side of the window. The third one doesn't get rescaled because its size is determined by its own preferred size (set with the setSize function).

Help button

A help button referencing the wiki manual can also be added to the bottom left hand corner of the dialog.

testDialog$addHelpButton("pmwiki.php?n=Main.NewWikiPageNotYetCreated")

This option should be used if you are including the dialog that you are creating into a package. The page can be added to the wiki by creating a link for it on a page in the wiki and then editing it. Currently the help button must point to the wiki, rather than a general web address. That said, it is probably a good thing to have all the documentation in a single location. Pages on the wiki can be edited using the super secret code of 571113 .

Running the dialog

Though setVisible can make the dialog appear, the best way to run a dialog is with the run function:

testDialog$run()

Unlike setVisible, run won't cause problems in non-JGR consoles.

The Widgets

Deducer has a number of built in GUI components (called widgets).

A number of methods are available for all widgets. Some important ones are:

	widget$setTitle("a title")	#sets the title of a widget
	widget$setTitle("a title",TRUE)	#sets the title of a widget and makes a titled border
	widget$getRModel()		#makes a string which if evaled is an R object representing the state of the widget
	setSize(widget,100,100)		#sets the size

When used in conjunction with SimpleRDialog, the widgets make keeping track of user selections simple. One of the necessary components to a useful statistical GUI is that the dialogs remember the users selections when he/she reenters the dialog after successful completing it. This is because statistical analysis is an iterative process. It is rare that one selects exactly the correct or most appropriate options in a dialog the first time. When the user open the dialog again to change the analysis slightly, he/she should not have to reenter every single option. Therefore, dialog memory is of primary importance in GUI design. the widgets (and SimpleRDialog) remember the user's selections, and automatically takes care of setting the dialog state to the last successful completion of the dialog (or the default selections if the dialog has not been completed).

VariableSelectorWidget

This widget is used to select the variables of interest for analysis. Usually the user moves variables from the selector, to either a SingletVariableListWidget, or a VariableListWidget. Sometimes not all variables are valid selections for an analysis, so there is the option to only show particular types of variables. For example, if you only wished the selector to show factors, you can use:

	variableSelector <- new(VariableSelectorWidget)
	variableSelector$setRFilter("is.factor")

VariableListWidget

This widget is used to take items from a VariableSelectorWidget and put them in a list. It must be linked to a selector upon creation:

	variableList<- new(VariableListWidget,variableSelector)

SingleVariableWidget

This widget is used to take items from a VariableSelectorWidget and put them in a list. unlike VariableListWidget, this widget is limited to taking only a single item.

	variableList<- new(SingleVariableWidget,variableSelector)

ButtonGroupWidget

A group of radio buttons. Only one may be selected at a time.

	bg <- new(ButtonGroupWidget,c("a","b","c"))

CheckBoxesWidget

A group of check boxes. An additional parameter can be passed to indicate the number of columns to use in laying out the boxes. For example the following creates a group of 4 check boxes in two columns:

	boxes <- new(CheckBoxesWidget,c("box 1","box 2","box 3","box 4"), 2L)

We can also set some of the boxes to be selected by default:

	boxes$setDefaultModel(c("box 1","box 3"))

ComboBoxWidget

A drop down combo box.

	combo <- new(J("org.rosuda.deducer.widgets.ComboBoxWidget"),c("a","b","c"))
	combo$setDefaultModel("b")

note: In future versions of Deducer, J("org.rosuda.deducer.widgets.ComboBoxWidget") will be given the shortcut ComboBoxWidget.

SliderWidget

A continuous slider.

	slider <- new(SliderWidget,c("left endpoint","right endpoint"))

TextAreaWidget

A place for the user to enter text.

	textArea <- new(TextAreaWidget,"title of widget")

ListWidget

A list

	lis <- new(ListWidget,"title of widget")

Example: Factor Analysis Dialog

Bringing all these points together we can make a dialog for factor analysis with very few lines of code.

	#make dialog
	dialog <- new(SimpleRDialog)
	dialog$setSize(500L,400L)
	dialog$setTitle("Factor Analysis")

	#add variable selector
	variableSelector <- new(VariableSelectorWidget)
	variableSelector$setTitle("data")
	addComponent(dialog,variableSelector,10,400,850,10)

	#add a list for the variables
	variableList<- new(VariableListWidget,variableSelector)
	variableList$setTitle("variables")
	addComponent(dialog, variableList,100,900,450, 420)

	#options for transforming the variables
	transBoxes <- new(CheckBoxesWidget,"Transformation",c("Center","Scale"))
	addComponent(dialog, transBoxes,500,900,670, 540)
	transBoxes$setDefaultModel(c("Scale"))

	#output options
	outBoxes <- new(CheckBoxesWidget,"Output",c("Summary","Scree Plot"))
	addComponent(dialog, outBoxes,680,900,850, 540)

	dialog$run()

This looks perfectly pretty, but doesn't actually do anything. We need to be able to translate GUI selections into runnable R code. To do this we register two functions with the dialog.

Check and Run Functions

The check and run functions are R functions available in the global environment that take one parameter representing the state of the widgets upon hitting the "run" button. The check function makes sure that the selections are valid for the particular analysis. If all the options are valid, the check function should return an empty string (i.e. ''), otherwise it should return a string representing a message to be displayed to the user. The run function constructs the R call and executes it.

The state, as passed to the check and run functions is a named list. The names of each of items of the list are the titles of the widgets.

In our factor analysis example, it doesn't make sense to do a factor analysis on less than two variables, so we will write a function that will throw up a message dialog when less than two variables are in the variableList.

.factorAnalysisCheckFunction <- function(state){
	#make sure at least two variables are selected
	if(length(state$variables)<2)
		return("Please select at least two variables")
	return("")
}

Then we need to tell the dialog to call this function when it is checking validity.

dialog$setCheckFunction(toJava(.factorAnalysisCheckFunction))

Next we need to create the function which will take the GUI state and translate it into an R call.

.factorAnalysisRunFunction <- function(state){
	#print(state) #a print statement is useful for debugging

	#make formula
	form <-paste( " ~ " , state$variables[1])
	for( var in state$variables[-1])
		form <- paste(form,"+",var)

	#make prcomp call
	cmd <- paste("pr.model <-prcomp(", form, ",", state$data)
	if("Center" %in%state$Transformation)
		cmd <- paste(cmd,", center=TRUE")
	if("Scale" %in%state$Transformation)
		cmd <- paste(cmd,",scale=TRUE")
	cmd <- paste(cmd,")")

	#always print model
	cmd <- paste (cmd,"\n","print(pr.model)")

	#output summary and plot if asked for
	if("Summary" %in% state$Output)
		cmd <- paste(cmd,"\n","summary(pr.model)")
	if("Scree Plot" %in% state$Output)
		cmd <- paste(cmd,"\n","screeplot(pr.model)")

	#execute command as if typed into console
	execute(cmd)
}

Note the use of the execute function. This takes a string and executes it as if it had been typed into the console. Finally the run function should be registered with the dialog:

dialog$setRunFunction(toJava(.factorAnalysisRunFunction))

Adding to the menu system

deducer.addMenu and deducer.addMenuItem add menu and menu items to the command line menu system. We then need to add items to the GUI menu bars. If we are in the windows Rgui, we use winMenuAdd and winMenuAddItem. If we are in JGR we use jgr.addMenu and jgr.addMenuItem.

deducer.addMenu("Example")
deducer.addMenuItem("Factor Analysis",,"dialog$run()","Example")
if(.windowsGUI){
	winMenuAdd("Example")
	winMenuAddItem("Example", "Factor Analysis", "deducer('Factor Analysis')")
}else if(.jgr){
	jgr.addMenu("Example")
	jgr.addMenuItem("Example", "Factor Analysis", "deducer('Factor Analysis')")
}

One thing to note is that winMenuAddItem calls deducer('Factor Analysis') and not dialog$run(). This is done so that the deducer function can handle the graphics device details. If you don't call your dialogs through the deducer function on non-JGR consoles, plotting to graphics devices may not function correctly.

Wrapping it up into a package

We are now ready to put our dialog into a package and distribute it to the world. As a good coding practice, the dialog creation process should be put into a function, and we shouldn't go through the process of making the dialog until the user calls it. To do this we create a new function (getFactorAnalysisDialog) that returns the dialog if it is already made, otherwise is makes a new one. Besides, to add the dialog to the menu system automatically, we will define the .onLoad function with the corresponding instructions.

Putting it all together we get the following fully functional package script for making a factor analysis dialog with no Java code.

makeFactorAnalysisDialog <- function(){
	#make dialog
	dialog <- new(SimpleRDialog)
	dialog$setSize(500L,400L)
	dialog$setTitle("Factor Analysis")

	#add variable selector
	variableSelector <- new(VariableSelectorWidget)
	variableSelector$setTitle("data")
	addComponent(dialog,variableSelector,10,400,850,10)

	#add a list for the variables
	variableList<- new(VariableListWidget,variableSelector)
	variableList$setTitle("variables")
	addComponent(dialog, variableList,100,900,450, 420)

	#options for transforming the variables
	transBoxes <- new(CheckBoxesWidget,"Transformation",c("Center","Scale"))
	addComponent(dialog, transBoxes,500,900,670, 540)
	transBoxes$setDefaultModel(c("Scale"))

	#output options
	outBoxes <- new(CheckBoxesWidget,"Output",c("Summary","Scree Plot"))
	addComponent(dialog, outBoxes,680,900,850, 540)
	dialog$setCheckFunction(".factorAnalysisCheckFunction")
	dialog$setRunFunction(".factorAnalysisRunFunction")
	return(dialog)
}

.factorAnalysisCheckFunction <- function(state){
	#make sure at least two variables are selected
	if(length(state$variables)<2)
		return("Please select at least two variables")
	return("")
}

.factorAnalysisRunFunction <- function(state){
	#print(state) #a print statement is useful for debugging

	#make formula
	form <-paste( " ~ " , state$variables[1])
	for( var in state$variables[-1])
		form <- paste(form,"+",var)

	#make prcomp call
	cmd <- paste("pr.model <-prcomp(", form, ",", state$data)
	if("Center" %in%state$Transformation)
		cmd <- paste(cmd,", center=TRUE")
	if("Scale" %in%state$Transformation)
		cmd <- paste(cmd,",scale=TRUE")
	cmd <- paste(cmd,")")

	#always print model
	cmd <- paste (cmd,"\n","print(pr.model)")

	#output summary and plot if asked for
	if("Summary" %in% state$Output)
		cmd <- paste(cmd,"\n","summary(pr.model)")
	if("Scree Plot" %in% state$Output)
		cmd <- paste(cmd,"\n","screeplot(pr.model)")

	#execute command as if typed into console
	execute(cmd)
}



.onLoad <- function(libname, pkgname) { 
	.registerDialog("Factor Analysis", makeFactorAnalysisDialog)
	deducer.addMenu("Example")
	deducer.addMenuItem("Factor Analysis",,".getDialog('Factor Analysis')$run()","Example")
	if(.windowsGUI){
		winMenuAdd("Example")
		winMenuAddItem("Example", "Factor Analysis", "deducer('Factor Analysis')")
	}else if(.jgr){
		jgr.addMenu("Example")
		jgr.addMenuItem("Example", "Factor Analysis", "deducer('Factor Analysis')")
	}

}

Notice that .registerDialog is used to tell Deducer how to create the dialog, and .getDialog is called by the menu functions in order to call the dialog.

Obviously, in order to achieve this the exported objects of Deducer's own namespace must be visible by our package whe it is being loaded. Therefore, Deducer should be loaded before the new package. Or preferably, the DESCRIPTION and NAMESPACE files of our package's source should define Deducer as an import.

Responding to user actions: Adding listeners

Listeners allow you to respond to a user's interaction with your dialog. If the user selects an item, presses a button, or moves their mouse, you can make your GUI respond to that event.

As a simple example, let's start with the first few components of the "Factor Analysis" dialog that we have been working with.


	#make dialog
	dialog <- new(SimpleRDialog)
	dialog$setSize(500L,400L)
	dialog$setTitle("Factor Analysis")

	#add variable selector
	variableSelector <- new(VariableSelectorWidget)
	variableSelector$setTitle("data")
	addComponent(dialog,variableSelector,10,400,850,10)

	#add a list for the variables
	variableList<- new(VariableListWidget,variableSelector)
	variableList$setTitle("variables")
	addComponent(dialog, variableList,100,900,450, 420)

The above code makes our dialog, and adds the VariableSelector and VariableList. Instead of adding the various options below the VariableList, we will add a button.

	#Add an 'Options' button
	JButton <- J("javax.swing.JButton")
	button <- new(JButton,"Options")
	addComponent(dialog,button,500,800,600,600)

javax.swing.JButton is part of the standard Swing Library. We wish to perform an action when this button is pressed. We can do this by creating an ActionListener, and adding it to the button. First, let's create the listener:

	#Listen for the button to be pressed
	ActionListener <- J("org.rosuda.deducer.widgets.event.RActionListener")
	listener <- new(ActionListener)

All component listeners are located under org.rosuda.deducer.widgets.event , later we will enumerate all of the implemented listeners, but for now, it suffices to know that the RActionListener class responds to user actions such as button pressing.

Next we need to tell the listener what to do when the button is pressed. The actionFunction is a function that takes two parameters, and displays a message to the user with a JOptionPane.

	JOptionPane <- J("javax.swing.JOptionPane")
	actionFunction <- function(cmd,ActionEvent){
		JOptionPane$showMessageDialog(dialog,paste("Two things:\n  1. Hello from R, it's nice to see you\n  2. You pressed: ", cmd))
	}
	listener$setFunction(toJava(actionFunction))
	button$addActionListener(listener)
	dialog$run()

The first line loads the JOptionPane class. actionFunction like all R functions to be called by a listener takes two parameters, the first is the type of action. in this case it will be the name of the button pressed. The second is an Java object which provides a full description of the event. Next we call setFunction on the listener and pass it our R function. It is necessary to wrap this function in toJava because the listener needs a Java representation of the function. Finally, we add the listener to the button, so that when it is pressed, we see:

Available listeners

The following types of listeners can call back to R functions.

ClassDescription
org.rosuda.deducer.widgets.event.RActionListenerUser Actions
org.rosuda.deducer.widgets.event.RCaretListenercursor movements in text fields
org.rosuda.deducer.widgets.event.RChangeListenerA change in the component's state
org.rosuda.deducer.widgets.event.RComponentListenerGeneral component events
org.rosuda.deducer.widgets.event.RDocumentListenerDocument Actions
org.rosuda.deducer.widgets.event.RFocusListenergaining or losing focus
org.rosuda.deducer.widgets.event.RKeyListenerkeyboard button pressing
org.rosuda.deducer.widgets.event.RListSelectionListeneritems selected in a JList
org.rosuda.deducer.widgets.event.RMouseListenermouse events (e.g. mousing over a component)
org.rosuda.deducer.widgets.event.RMouseMotionListenermouse movement
org.rosuda.deducer.widgets.event.RMouseWheelListenerwheel actions
org.rosuda.deducer.widgets.event.RWindowListenerwindow closing, opening, etc.

Each of these classes has setFunction and getFunction methods which allow you to set and get the R function which you wish it to call. The R function must take two parameters. The fist one is a string describing the event, the second is a Java object inheriting from Event.

These listeners can be registered with any Java Component by adding it with the appropriate function (e.g. object$addActionListener(actLis) or object$addMouseListener(MLis)). Additionally, DeducerWidgets provide a general method AddListener which will register the listener if it is appropriate for the class, and do nothing otherwise.

The RDialogMonitor

The RDialogMonitor allows you to run an R function at pre-specified intervals while an RDialog is open. For example, instead of displaying a message when the number of items in the 'Factor Analysis' variable list is less than two, we might wish to simply disable the 'RUN' button. This can be accomplished by repeatedly checking whether the list contains more than two items, and setting the run button accordingly.

First we create a new RDialogMonitor

	monitor <- new(RDialogMonitor,dialog,500L)

The monitor will do nothing if dialog is not visible. Otherwise it will run a function which we specify. 500L indicates that the function should be executed every 500ms.

	runButton <- dialog$getOkayCancel()$getApproveButton()
	monitorFunction <-function(){
		if(length(variableList$getVariables())<2)
			runButton$setEnabled(FALSE)
		else
			runButton$setEnabled(TRUE)
	}
	monitor$setFunction(toJava(monitorFunction))

monitorFunction checks the number of items in the list, and disables the Run button if that number is less than two. If there are two or more, the button is enabled.

Lastly, we need to tell monitor to start running

	monitor$start()

When the dialog is no longer needed, call monitor$stop() to stop the thread from running.

For example if there is only one item in the list, it will look like this:

Note of caution: The RDialogMonitor starts a new thread and executes R commands from this new thread. Since R is not thread safe, you need to make sure that the REPL is blocked while the dialog is visible (i.e. users can not enter commands directly into the console while the dialog is open). This is taken care of if you open the dialog with dialog$run(), and is also not an issue in the JGR console as it is completely Java based.

Adding a Sub-dialog

The SimpleRSubDialog class is an easy way to add lesser used or advanced options to your GUI.

SimpleRSubDialog can be used in much the same way as SimpleRDialog. let us continue with the 'Factor Analysis' example. In the listeners page we saw how to add an option button to the dialog, which gave us the following code:

	#make dialog
	dialog <- new(SimpleRDialog)
	dialog$setSize(500L,400L)
	dialog$setTitle("Factor Analysis")

	#add variable selector
	variableSelector <- new(VariableSelectorWidget)
	variableSelector$setTitle("data")
	addComponent(dialog,variableSelector,10,400,850,10)

	#add a list for the variables
	variableList<- new(VariableListWidget,variableSelector)
	variableList$setTitle("variables")
	addComponent(dialog, variableList,100,900,450, 420)

	#Add an 'Options' button
	JButton <- J("javax.swing.JButton")
	button <- new(JButton,"Options")
	addComponent(dialog,button,500,800,600,600)

Now lets make a new SimpleRSubDialog and set dialog as it's owner (or parent). All sub-dialogs should be linked to another dialog, which can be an RDialog, SimpleRDialog, or another SimpleRSubDialog. When a widget is added to a SimpleRSubDialog, its owner will keep track of it, so that the widget states are remembered and handled correctly.

	#make Options Dialog
	subDialog <- new(SimpleRSubDialog,dialog,"Factor Analysis: Options")
	setSize(subDialog,250,300)

Now we can add some widgets to the dialog:

	#options for transforming the variables
	transBoxes <- new(CheckBoxesWidget,"Transformation",c("Center","Scale"))
	addComponent(subDialog, transBoxes,1,900,300, 100)
	transBoxes$setDefaultModel(c("Scale"))

	#output options
	outBoxes <- new(CheckBoxesWidget,"Output",c("Summary","Scree Plot"))
	addComponent(subDialog, outBoxes,350,900,700, 100)

Then we add an action listener to the options button to open the sub-dialog when it is pressed:

	#Listen for the button to be pressed
	ActionListener <- J("org.rosuda.deducer.widgets.event.RActionListener")
	actionFunction <- function(cmd,ActionEvent){
		subDialog$setLocationRelativeTo(button)
		subDialog$run()
	}
	listener <- new(ActionListener)
	listener$setFunction(toJava(actionFunction))
	button$addActionListener(listener)

finally we can add a run function to the original dialog.

.factorAnalysisRunFunction <- function(state){
	#print(state) #a print statement is useful for debugging

	#make formula
	form <-paste( " ~ " , state$variables[1])
	for( var in state$variables[-1])
		form <- paste(form,"+",var)

	#make prcomp call
	cmd <- paste("pr.model <-prcomp(", form, ",", state$data)
	if("Center" %in%state$Transformation)
		cmd <- paste(cmd,", center=TRUE")
	if("Scale" %in%state$Transformation)
		cmd <- paste(cmd,",scale=TRUE")
	cmd <- paste(cmd,")")

	#always print model
	cmd <- paste (cmd,"\n","print(pr.model)")

	#output summary and plot if asked for
	if("Summary" %in% state$Output)
		cmd <- paste(cmd,"\n","summary(pr.model)")
	if("Scree Plot" %in% state$Output)
		cmd <- paste(cmd,"\n","screeplot(pr.model)")

	#execute command as if typed into console
	execute(cmd)
}
dialog$setRunFunction(toJava(.factorAnalysisRunFunction))

Notice that the state of the 'Transformation' and 'Output' widgets are passed to the 'state' parameter. The final result looks as follows:

Where to go from here

You are now prepared to make simple dialogs to perform statistical analyses without leaving the comfort of R. For more complicated GUIs it may become necessary to move into Java in order to realize your vision. The next section will illustrate the inclusion of Java into R packages, and more specifically building off of Deducer and JGR.

Setting up your package to include Java code

Java code can be built and run using the package rJava. JRI.jar in the rJava package includes the necessary classes for interacting with R from within Java. In fact rJava comes with two systems for doing this. Rengine is a lower level interface, and is therefore less safe to use. REngine provides much of the same functionality, but has better error checking. This article will focus on REngine, as it is the recommended API. Note that JGR.jar (from the JGR package) also includes the REngine classes, so if that is the only part of JRI that you need, you may build your Java project against it.

Package structure

The basic structure for a Java package is much the same as any other package.

-package_name
    DESCRIPTION
    -inst
        -java
            package_name.jar
    -man
    -R
        zzz.R

Like and R package. There are 'man' and 'R' directories which contain the R documentation and R code respectively. The DESCRIPTION file is the same as for any other package, and should have rJava in the Required list.

Unlike many R packages there is an 'inst' directory. This indicates to R that when the package is built, anything in the 'inst' folder should be copied verbatim into the package root directory. When rJava looks for java code, it will try to load any .jar files in the 'java' directory. These files may be named anything.

Loading your .jar file

To add your jar to the classpath, two statements should be added to the .First.lib (or onLoad) function in zzz.R.

.First.lib <- function(libname, pkgname) { 
    .jpackage(pkgname)  
    .jengine(TRUE) 
} 

The .jpackage call adds all .jar files in the 'java' directory of your package to the classpath. The '.jengine' call starts the REngine, so that your Java code can call R functions. If your Java code does not import REngine (or Rengine), then this statement is not needed. Also, if your package requires Deducer, it is also not needed, as the engine is already started.

R from within Java

R commands can be executed from Java using the the JRIEngine class.

JRIEngine en; 
try{ 
    en = new JRIEngine(org.rosuda.JRI.Rengine.getMainEngine()); 
catch(Exception e){} 
REXP oneToTenR = en.parseAndEval(“1:10”);

This evaluates the R statement 1:10 and then assigns it to oneToTenR which is of class REXP. REXP objects are Java representations of R expressions. REXP objects can be converted to something that Java can more readily manipulate using a series of conversion methods. For example, we can convert oneToTenR into and array of doubles using the asDoubles method.

double[] oneToTenJava; 
try{ 
   oneToTen = oneToTenR.asDoubles(); 
catch(Exception e){} 

The following table enumerates the conversion functions, and what they convert too.

FunctionConverts toNote
REXP.asInteger()intfactors/logicals* ok
REXP.asDouble()doublefactors/integers/logical** ok
REXP.asString()Stringfactors ok
REXP.asStrings()String[]factors ok
REXP.asDoubles()double[]factors/integers/logical** ok
REXP.asBytes()byte[]
REXP.asIntegers()int[]factors/logicals* ok
REXP.asList().at(int index)REXPfor R lists and data.frames

*NA = -2147483648
**NA=Double.longBitsToDouble(0x7ff00000000007a2L);

With Deducer

If your package uses Deducer, there is a convenience method (Deducer.eval) for executing R statements.

//using Deducer wrapper 
REXP rVariable = Deducer.eval(“names(Prestige)”); 

String[] prestigeNames; 
try{ 
   prestigeNames = rVariable.asStrings() 
} catch (REXPMismatchException e) {} 

Also there is a useful method for executing R commands as if the user typed them into the console:

Deducer.execute(“print(‘I like ponies’)”); 

Compiling and JARing the code

You will need to compile your Java code into package_name.jar. You may need to compile it against JRI.jar, JGR.jar and/or Deducer.jar (found in the rJava, JGR and Deducer packages respectively), depending on which classes you use in your code. This can be accomplished in any number of ways using any Java IDE. Below is a desciption of how to do it using the command line.

1. Make a new directory with the following structure:

-src
    -package_name
        *.java          <------All of your java source code
    Deducer.jar
    JGR.jar
    JRI.jar

2. Then, in the src directory execute

javac -target 1.4 -source 1.4 -d . -classpath JRI.jar:JGR.jar:deducer.jar package_name/*.java
jar fc package_name.jar package_name/*.class

If you are using Windows, you may need to change the first line to:

javac -target 1.4 -source 1.4 -d . -classpath JRI.jar;JGR.jar;deducer.jar package_name/*.java

Then copy the newly created package_name.jar to the java directory of your package.

Where to go from here

Auto generated Documentation on REngine and the REXP class can be found Here.Now that you have the basics of dealing with R objects in Java, the next step is to build a GUI dialog using the Swing toolkit.

Building a GUI from Java

There are two main toolkits for GUI development in Java, SWT and Swing. While either can be used for dialog creation, Deducer has a number of specialized Swing components specifically focused on statistical GUI creation. This page assumes a basic familiarity with Swing, and will take you through the process of building a dialog to create a scatter plot. There are many Swing tutorials available, including one written by Sun.

RDialog

RDialog extends Swing's JDialog, and makes it easy to create a dialog with memory. Memory is an important aspect of a statistical analysis dialog, because it is rare that the user has specified all of the analysis options correctly the first time. Anytime a Deducer widget is added to the dialog via add, it's state is tracked, and saved when the dialog is successfully completed. Then when the dialog is re-opened, it sets the widgets' states to their saved value. This is all done transparently. The only thing that the programmer needs to do is inform RDialog when it is successfully competed.

Important Methods:

run()
This is the preferred way of opening up the dialog. It sets the widget states to their appropriate values, makes the dialog visible, and handles compatibility with non-JGR consoles.
reset()
Sets widgets to their default states.
setToLast()
Sets widgets to the last completed state of the dialog
completed()
Dialog completed.
setOkayCancel(boolean showReset,boolean isRun,ActionListener lis)
Adds Okay/Run, Cancel, and Reset buttons to the bottom right of the dialog
addHelpButton(String pageLocation)
Adds a help button to the botom left. pageLocation is the URL relative to http://www.deducer.org/pmwiki/
initGUI()
Called upon construction. Override this to add in GUI widgets/components

By default RDialog uses AnchorLayout as it's layout manager. Though this can be changed. The classes are located at:

import org.rosuda.JGR.layout.AnchorConstraint;
import org.rosuda.JGR.layout.AnchorLayout;

Widgets

Widgets are easy to use Swing components provided by Deducer.

ClassDescription
org.rosuda.deducer.widgets.VariableSelectorWidgetFilterable list of the variables in a data frame
org.rosuda.deducer.widgets.VariableListWidgetA list of variables selected from a VariableSelectorWidget
org.rosuda.deducer.widgets.SingleVariableWidgetA list of a single variable selected from a VariableSelectorWidget
org.rosuda.deducer.widgets.ButtonGroupWidgetA group of radio buttons
org.rosuda.deducer.widgets.CheckBoxesWidgetA group of check boxes
|org.rosuda.deducer.widgets.ComboBoxWidgetA drop down list
org.rosuda.deducer.widgets.SliderWidgetA slider
org.rosuda.deducer.widgets.TextAreaWidgetAn area to input text

Auto-generated Documentation for each of these is available: Widget JavaDocs.

Adding a Widget

Widgets can be added just as any other component.

		SliderWidget slider = new SliderWidget("Alpha level",new String[]{"Transparent","Opaque"});
		anRDialog.add(slider, new AnchorConstraint(610, 978, 840, 460, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL));

In the above code, a new slider is added to the RDialog anRDialog. In this case, anRDialog uses AnchorLayout, so the slider is added with an AnchorConstraint. Each of the numbers represents the distance from either the left or the top of the dialog for each side of the slider (top, right, bottom and left). Because all of the constraints are AnchorConstraint.ANCHOR_REL, the numbers represent relative positions going from 1 (left/top edge) to 1000 (right/ bottom edge). When the dialog is resized, the slider will be resized proportionally. The constraint can also be AnchorConstraint.ANCHOR_ABS, in which case the number associated with that side is the absolute number of pixels between the component's edge, and that of the dialog.

		variableSelector = new VariableSelectorWidget();
		anRDialog.add(variableSelector, new AnchorConstraint(12, 428, 900, 12, 
				AnchorConstraint.ANCHOR_ABS, AnchorConstraint.ANCHOR_REL, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_ABS));

The above code creates a new variable selector. Because the top and left constraints are AnchorConstraint.ANCHOR_ABS, there are 12 pixels between the left side of the dialog, and the left side of the selector. Similarly, there are 12 pixels between the top of the selector and the top of the dialog, regardless of how the dialog is resized.

Finally, the constraint can be AnchorConstraint.ANCHOR_NONE, in which case the position of the side is determined by the preferred size of the component (set with setPreferredSize).

Example: Scatter plot dialog

We can make a scatter plot dialog easily using the above tools. We will extend RDialog. We will also implement ActionListener so that we can respond to the user pressing the buttons at the bottom of the dialog.

package example;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;

import org.rosuda.JGR.layout.AnchorConstraint;
import org.rosuda.deducer.Deducer;
import org.rosuda.deducer.widgets.*;


public class PlotRDialog extends RDialog implements ActionListener{

	private VariableSelectorWidget variableSelector;
	private SingleVariableWidget yaxis;
	private SingleVariableWidget xaxis;
	private SliderWidget slider;



	public void initGUI(){
		super.initGUI();


		variableSelector = new VariableSelectorWidget();
		this.add(variableSelector, new AnchorConstraint(12, 428, 900, 12, 
				AnchorConstraint.ANCHOR_ABS, AnchorConstraint.ANCHOR_REL, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_ABS));
		variableSelector.setPreferredSize(new java.awt.Dimension(216, 379));
		variableSelector.setTitle("Data");

		yaxis = new SingleVariableWidget("y axis",variableSelector);
		this.add(yaxis, new AnchorConstraint(121, 978, 327, 460, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL));
		yaxis.setPreferredSize(new java.awt.Dimension(276, 63));

		xaxis = new SingleVariableWidget("x axis",variableSelector);
		this.add(xaxis, new AnchorConstraint(337, 978, 540, 460, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL));
		xaxis.setPreferredSize(new java.awt.Dimension(276, 63));

		slider = new SliderWidget("Alpha level",new String[]{"Transparent","Opaque"});
		this.add(slider, new AnchorConstraint(610, 978, 840, 460, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL, 
				AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL));
		slider.setPreferredSize(new java.awt.Dimension(187, 44));
		slider.setDefaultModel(new Integer(100));

		this.setTitle("Scatter Plot");

		setOkayCancel(true,true,this);   //put Run, Reset, Cancel buttons in place, and register this as it's listener
		addHelpButton("pmwiki.php");     //Add help button pointing to main manual page
		this.setSize(555, 445);
	}

	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();

		if(cmd=="Run"){
			String xvar = xaxis.getSelectedVariable();
			String yvar = yaxis.getSelectedVariable();
			String data = variableSelector.getSelectedData();
			String alpha = new Double(((double) slider.getValue())/100.0).toString();
			if(yvar==null || xvar==null || data==null){
				JOptionPane.showMessageDialog(this, "You must specify both an x and y variable");
				return;
			}

			String command = "qplot("+xvar+", "+yvar+", data="+data+",alpha=I("+alpha+"))";

			Deducer.execute(command);		//execute command as if it had been entered into the console
			this.setVisible(false);
			completed();	//dialog completed
		}else if(cmd=="Cancel")
			this.setVisible(false);
		else if(cmd=="Reset")
			reset();
	}

}

The first method (initGUI) overrides the the RDialog method, and is used to set up the four widgets. The actionPerformed method is called when the "Run", "Cancel" or "Reset" buttons are pressed. When "Run" is pressed, an call to qplot in the ggplot2 package is constructed. It is then executed using Deducer.execute.

The above Class is present in the DeducerPlugInExample package, and can be viewed with:

R> library(DeducerPlugInExample)
R> scatter <- new(J("example.PlotRDialog"))
R> scatter$run()

GUI Design considerations

Below are some thoughts about the design of statistical dialogs. They are by no means definitive, or even correct.

GUI should be as simple as possible, but no simpler.

There is definite value in resisting the urge to include every possible eventuality or user need into a single dialog. You certainly don't want this to happen. A UI should as simple and streamlined as possible, with commonly used elements at the forefront, and little used tweaks hidden (perhap in a sub-dialog).

That said, going too simple can paradoxically increase complexity. If you narrow the use of a single dialog to include only a very specific function, for example loading am SPSS dataset, then you will be forced, later down the road, to create dialogs for each specific function that was not included (i.e. for Stata, SAS, and csv data files). This can lead to a proliferation of menu items, where only one was needed (Load Data).

Organize implemented procedures by task.

Think about a user sitting down to use your software. What are they trying to accomplish? What is his/her goal?

A function call is not a goal. Users don't sit down to do a t-test. A t-test is the chosen procedure to accomplish the task of comparing two distributions. t-tests, Mann-Whitney, Kolmogorov-Smirnov, etc., all relate to the same task of comparing two distributions, so they should be put in the same dialog.

Don’t cover all tasks, but covered tasks should be (almost) comprehensively covered.

It is better to cover one task very well then to cover 10 tasks poorly. It is the matter of 10 minutes to create a dialog to create a scatter plot of two variables. But it is something quite different to add options for:

  • Plotting symbols
  • Color
  • Paneling (grid and wrap)
  • Regression lines (smooth, polynomial and linear)
  • Labels / Title
  • Transparency
  • Preview

Don’t restrict the user to analyzing one variable at a time

If the user is likely to want to do the same action on several variables. Don't make them go through the dialog once for every variable. Make the dialog such that the action can be applied to multiple variables. If the action is an analysis format the results into a nice table (see multi.test).

Make output human readable

Format results into easy to read tables.

Don’t make the user come to you

Test your GUI on multiple platforms in multiple consoles.

  • Cross-platform is good
  • All consoles should be supported

Don’t hide the console or otherwise get in the way

You are not the owner of R, the user is.

Help should be easy to find.

Add help buttons. Feel free to add pages to this manual for your dialogs. The password for editing is the primes between 4 and 12 with no spaces. I hate spammers.

Try resizing your dialog.

Don't be SPSS prior to 2006. Let the user resize your dialog.

Dialogs MUST have memory

If a dialog does not remember it's settings the last time it was run, it is nearly useless. Data analysis is an iterative process, it is very rare that that the user will specify exactly the right set of options the first time.



Analysis design considerations

In the creation of a statistical GUI the analysis philosophy of the author is necessarily imposed on the software. By choosing what to include, what options to make default, and where to place those options, the author guides the default behavior of the user. Below are some decisions that I made that have a bearing on how Deducer is used. Many of these are open to debate.

All analyses should have a visualization.

Humans are visual creatures and best understand data when it is presented in a visual manner. This helps both in the understanding of results, and in the diagnosis of possible assumption violations.

Mid p-values are better than standard p-value for exact and monte carlo tests

Standard 'exact' p-values are slightly conservative. The mid p-value is a minor modification that maintains an alpha level closer to the nominal level.

Type II SSQ > Type III SSQ

R uses type II sum of squares where as most other packages use type III (SAS, SPSS).

You probably don’t want to use a hypothesis test to detect assumption violation

If you have a small sample size, you have no power to detect even major violations. If you have a large sample size you will almost surely find a statistically significant violation, even if the magnitude is small.