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
ListWidgetorg.rosuda.deducer.widgets.ListWidgetA list of items
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)

SingleVariableListWidget

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(SingleVariableListWidget,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")

The Factor Analysis Example 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.

The 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(".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(".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.

Putting it all together we get the folowing 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)
}

getFactorAnalysisDialog <- function(){
	if(!exists(".factorAnalysisDialog")){
		#make factor analysis dialog
		.factorAnalysisDialog <- makeFactorAnalysisDialog()
		assign(".factorAnalysisDialog",.factorAnalysisDialog,globalenv())		
	}
	return(.factorAnalysisDialog)
}

.First.lib <- function(libname, pkgname) { 
	deducer.addMenu("Example")
	deducer.addMenuItem("Factor Analysis",,"getFactorAnalysisDialog()$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')")
	}

}

Responding to user actions

For added flexibility, Deducer has facilities for responding to user actions and dialog events.

Dialogs in R: Adding Listeners

Dialogs in R: Adding A Sub-dialog

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.