Geworkbench Development Tips

From Informatics

Jump to: navigation, search

See potential changes for areas of improvement.

Contents

Creating an Analysis Component

  • Create a class, FooAnalysis, that extends AbstractGridAnalysis
  • Create a panel for the class to house the input parameters, FooParamPanel.
  • Set this in the constructor of FooAnalysis:
public FooAnalysis() {
   setLabel("Foo");
   setDefaultPanel(new FooParamPanel());
}
  • Add an entry to all.xml that looks like:
<plugin id="foo" name="Foo Analysis" class="org.geworkbench.components.foo.FooAnalysis" source="foo"/>
  • in order to have the analysis component for a microarray data set appear in the analysis panel you have to "extends AbstractAnalysis implements ClusteringAnalysis". (added note by BJ)

Component descriptor

(BJ: need explaination for that, I suppose this is to connect help pages to a component)

File system access

All file related activities should be done using org.geworkbench.util.FilePathnameUtils class. This class is part of the API for accessing file system by geworkbench components.

File system related properties will be handled by this class. User can change properties but all properties processing will be done here. If user didn't provide file related properties - defaults will be used instead.

File related properties are read when class is loaded and they can't be changed at run time. To specify geworkbench location for read access "components.dir" property should be used.

System related file separator is created as a const so user don't have to use system property or File.separator, this const should be rarely used, if at all - as path creating activity should be refactored into this API.

Each time this API is used, before the String returned to the user, it will be checked if directory structure exist and if it doesn't - directory structure will be created with all subdirectories. Methods checkDirExist(dir) and checkParentDirExist are made public as in many places in geworkbench similar code exist. User can call these methods directly if directory structure is created without using this API. Using this API will guarantee that directory structure exist. It will allow creating new directory structure by just changing application.properties file,

Currently this API is about String manipulations and creating absolute path. When creating java.io.File user should use only File(String pathname) constructor and use this API to create pathname string.

NOTE: relative path shouldn't be used as a pathname.

It is also recommended not to hardcode file names but use constants or properties instead.

  • Example:

First create absolute path using FilePathnameUtils class:

String	filename = FilePathnameUtils.getTemporaryFilesDirectoryPath() + DEFAULTRESOURCEFILE;

where DEFAULTRESOURCEFILE could be defined like this:

private static final String DEFAULTRESOURCEFILE = "defaultResources.csv";

and then String filename could be used to construct File like this:

new File(filename);

Creating a GUI Area for the Analysis Component

The gui area of an analysis component is an area with the most user interaction. Typically, users will not only just view the results of an analysis in this area, but also do things like make marker selections, add marker selections to a set, use sliders to change the contrast of images.

To allow users to fully interact with the gui area of a component, component developers should do the following.

Create a visual component. This class serves as gui area of the geworkbench component, and is used to communicate with the framework. That is, all event passing to and from the framework should be done from here. When creating a visual component, do the following:

  • Implement VisualPlugin
    • Register this visual component in all.xml with an entry like
<plugin id="medusaViewer" name="MEDUSA Viewer" class="org.geworkbench.components.medusa.gui.MedusaVisualComponent" source="medusa">
 <gui-area name="VisualArea"/>
</plugin>
  • Implement a receive method to receive project events.
    • This method receives a ProjectEvent, like the one published by the AnalysisPanel for the selected analysis.
    • This must have the annotation @Subscribe to receive the ProjectEvent.
    • When implementing this method, you should create the panels to contain your visualizations and add it to the component. When creating these panels, pass this into it to allow those panels to call back the visual component. An example is:
@Subscribe
public void receive(ProjectEvent projectEvent, Object source) {
 log.debug("MEDUSA received project event.");
 DSDataSet data = projectEvent.getDataSet();
 if ((data != null) && (data instanceof MedusaDataSet)) {
  ProgressBar pBar = Util.createProgressBar("Medusa Analysis");
  pBar.setMessage("Rendering images");
  pBar.start();
 if (dataSet != data) {
  dataSet = ((MedusaDataSet) data);
  component.removeAll();
  medusaVisualizationPanel = new MedusaVisualizationPanel(this,dataSet.getData());
  component.add(medusaVisualizationPanel, BorderLayout.CENTER);
  component.revalidate();
  component.repaint();
 }
 pBar.stop();
 }
}
  • Implement the method publishSubpanelChangedEvent.
    • This must have the annotation @Publish.
    • Allows events to be communicated with the framework (published) from this VisualPlugin. For example, this method can be called back from a panel contained in the VisualPlugin.
  • Implement the method publishImageSnapshot.
    • This must have the annotation @Publish.

Microarray Sets

See the MedusaAnalysis.getRegulators() in geworkbench to see how we pull information from a microarray set.

  • DSMicroarraySetView is the starting point.
  • microarraySetView.getUniqueMarkers() returns the selected markers (rows) in the microarray set.
  • microarraySetView.getItemsPanel() returns a DSPanel<DSMicroarray>, giving you access to each microarray (column) in the microarray set.

Potential Changes

Naming

  • microarraySetView.markers() returns an ItemsList<DSGeneMarker> - this name is a little ambiguous since a DSItemsPanel refers to microarrays.
  • microarraySetView.allMarkers versus microarraySet.markers()?
  • In CSMicroarray, the method getMarkerNo() should be getNumMarkers().
  • In CSMicroarray, the method getSerial should be getMicroarrayIndex, which is backed by a getColIndex.
  • Naming - This is currently a source of confusion. While this may not be considered a big deal, it slows down development time.
    • Classes that are named as panels should in fact be panels and extend JPanel. For example, EVDPanel is not a panel. It contains a panel, but is not a panel itself.
    • MarkerPanel is not a panel either. It extends ArrayList. If anything, this should be named as MarkerList.
  • microarraySetView.getUniqueMarkers() gives you the selected markers. Change the name to getSelectedMarkers.

Implementation

As a component developer, you want the framework to handle as many of the tasks a component will need as possible. Specific examples (and solutions) of what a component developer would want are given below:

  • The developer of a VisualPlugin, should be forced to implement:
@Subscribe
public void receive(ProjectEvent projectEvent, Object source)

(Allows this component to receive a ProjectEvent, which can trigger the start of the visualizations.)

Similarly, the following methods can be made generic enough that they should be in a base class:

@Publish
public SubpanelChangedEvent publishSubpanelChangedEvent(org.geworkbench.events.SubpanelChangedEvent event)

(Allows user changes made by the user in the GUI area to be communicated to the framework, like an "Add to set" button.)

@Publish
public org.geworkbench.events.ImageSnapshotEvent publishImageSnapshot()

(Allows component to take an image snapshot.)

To accomplish these reusable tasks, I propose an AbstractVisualComponent which contains (among other things):

public abstract class AbstractVisualComponent{

@Subscribe
public void receive(ProjectEvent projectEvent, Object source);

/**
 * Publish a subpanel changed event. An example is creating a selection set
 * when an "Add to set" button is clicked.
 * 
 * @param event
 * @return SubpanelChangedEvent
 */
@Publish
public SubpanelChangedEvent publishSubpanelChangedEvent(
 org.geworkbench.events.SubpanelChangedEvent event) {
 return event;
}

/**
 * Publish a snapshot. When taking an image snapshot, the {@link Image} is
 * first created from the {@link JComponent} of interest. The
 * {@link Graphics} context is then retrieved from the {@link Image} to
 * allow off-screen painting to occur.
 * 
 * @param component The JPanel you want to take the snapshot of, etc.
 * @return {@link ImageSnapshotEvent}
 */
 @Publish
 public org.geworkbench.events.ImageSnapshotEvent publishImageSnapshot(JComponent component) {
 Image image = null;
 try {
  /* set up the image width, height, and type */
  image = new BufferedImage(component.getWidth(),
  component.getHeight(),BufferedImage.TYPE_INT_RGB);
 /*
  * get the Graphics context from the image so we can paint it off
  * screen
  */
  Graphics g = image.getGraphics();
  component.paint(g);
 } catch (Exception e) {
  throw new RuntimeException(e);
 }
 ImageIcon icon = new ImageIcon(image, "My Component");
 org.geworkbench.events.ImageSnapshotEvent event = new org.geworkbench.events.ImageSnapshotEvent(
 "My Component Snapshot", icon, org.geworkbench.events.ImageSnapshotEvent.Action.SAVE);
 return event;
 }

}

Personal tools