6206
Comment:
|
29268
|
Deletions are marked like this. | Additions are marked like this. |
Line 12: | Line 12: |
SVN repository: http://chianti.ucsd.edu/svn/core3/support/trunk/samples/ | SVN repository: {{{http://chianti.ucsd.edu/svn/core3/samples/trunk/}}} |
Line 17: | Line 17: |
1. Compile with “mvn clean install” 1. Copy the jar in the project target directory to Cytoscape installation: bundles/apps |
1. Compile with {{{mvn clean install}}} 1. Copy the jar in the project target directory to Cytoscape installation: deploy directory |
Line 27: | Line 27: |
This tutorial will show some code snippets about how to use Cytoscape APIs on plugin development. Each feature is included in a sample plugin, which can serve as a starting point or template for new plugin developers. The code snippets can also be used as reference for experienced developers. Cytoscape plugin is usually packaged in a jar file and deployed to the plugins directory of Cytoscape. When Cytoscape starts up and initializes, its plugin manager will look up all the plugins in plugins directory. A plugin should have following three components. A class, which extends CytoscapePlugin. This is the entry point to Cytoscape. A manifest file, explicitly list the class, which extended CytoscapePlugin class A plugin.props file, which list the information about the plugin, such as plugin name, author, release date and more. Although this file is not absolutely required, it is recommended to have one, since plugin manager will need the information to handle the plugin properly. See the page at http://www.cytoscape.org/cgi-bin/moin.cgi/Cytoscape_Plugin_Tutorial for detail about the definition of a plugin.props. If plugin developer will share and publish their plugins, they are encouraged to submit their plugins to Cytoscape plugin website at http://cytoscape.org/plugins/index.php . |
This tutorial will show some code snippets about how to use Cytoscape APIs on app development. Each feature is included in a sample app, which can serve as a starting point or template for new app developers. The code snippets can also be used as reference for experienced developers. Cytoscape app is usually packaged in a jar file and deployed to the deploy directory of Cytoscape. When Cytoscape starts up and initializes, its app manager will look up all the apps in deploy directory. There are two types of Cytoscape App, Bundle app and Simplified App. In this cook book, we provide sample for both types. If app developer will share and publish their Apps, they are encouraged to submit their Apps to Cytoscape app store website at http://apps.cytoscape.org/ . |
Line 40: | Line 36: |
It takes three steps to add a tabbed panel to the control panel. {{{ #!java //1. Define a CytoPanel class |
Here's how to add a tabbed panel to the control panel. {{{ #!java // Define a CytoPanel class |
Line 54: | Line 49: |
// 2. Create an instance of MyCytoPanel |
}}} {{{ #!java // In the start method of your CyActivator class: // Create an instance: |
Line 57: | Line 56: |
//3. Register myPanel as a service registerService(bc,myCytoPanel,CytoPanelComponent.class, new Properties()); }}} Download a sample plugin, here. |
// Register it as a service: registerService(bc,myCytoPanel,CytoPanelComponent.class, new Properties()); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
Line 87: | Line 85: |
{{{ #!java // Register the CyAction as service |
{{{ #!java // In the start method of your CyActivator class: // Create an instance: |
Line 92: | Line 90: |
// Register it as a service: | |
Line 95: | Line 94: |
The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
|
Line 97: | Line 98: |
{{{ #!java // Define a CyAction class public class Sample04 extends AbstractCyAction { ... public Sample04(CySwingApplication desktopApp){ // Add a sub-menu item -- Apps->Sample04->sample04 super("sample04..."); setPreferredMenu("Apps.Sample04"); //Specify the menuGravity value to put the menuItem in the desired place setMenuGravity(2.0f); ... } }}} {{{ #!java // In the start method of your CyActivator class: // Create an instance: Sample04 Sample04Action = new Sample04(cytoscapeDesktopService); // Register it as a service: registerService(bc,Sample04Action,CyAction.class, new Properties()); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
|
Line 98: | Line 126: |
To create a network, get a reference to the `CyNetworkFactory` service, and tell it to create a network. With the new network, nodes and edges can be created through the CyNetwork interface. {{{ #!java // To get a reference of CyNetworkFactory at CyActivator class of the App CyNetworkFactory cyNetworkFactoryServiceRef = getService(bc,CyNetworkFactory.class); ... // To create a new network CyNetwork myNet = cnf.createNetwork(); ... // Add two nodes to the network CyNode node1 = myNet.addNode(); CyNode node2 = myNet.addNode(); // set name for new nodes myNet.getDefaultNodeTable().getRow(node1.getSUID()).set("name", "Node1"); myNet.getDefaultNodeTable().getRow(node2.getSUID()).set("name", "Node2"); // Add an edge myNet.addEdge(node1, node2, true); }}} Destroying networks is done through the `CyNetworkManager` service. First, in the `start` method of your `CyActivator` class: {{{ #!java // Get a CyNetworkManager CyNetworkManager netMgr = getService(bc,CyNetworkManager.class); }}} Now you can use `CyNetworkManager` in your code: {{{ #!java // Destroy a network with NetworkManager netMgr.destroyNetwork(myNet); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
|
Line 100: | Line 171: |
To create a network, get a reference to the `CyNetworkViewFactory` service, and tell it to create a network view. First, in the `start` method of your `CyActivator` class: {{{ #!java // Get a CyNetworkViewFactory CyNetworkViewFactory cyNetworkViewFactoryServiceRef = getService(bc,CyNetworkViewFactory.class); }}} Now you can use `CyNetworkViewFactory` in your code: {{{ #!java // Create a new network view CyNetworkView myView = cnvf.createNetworkView(myNet); }}} Destroying networks is done through the `CyNetworkViewManager` service. First, in the `start` method of your `CyActivator` class: {{{ #!java // get a CyNetworkViewManager CyNetworkViewManager cyNetworkViewManagerServiceRef = getService(bc,CyNetworkViewManager.class); }}} Now you can use `CyNetworkViewManager` in your code: {{{ #!java // destroy a network view through NetworkViewManager networkViewManager.destroyNetworkView(myView); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to determine which nodes are currently selected on a network? == We can use `CyTableUtil` to get the list of selected nodes in a network {{{ #!java //Get the selected nodes List<CyNode> nodes = CyTableUtil.getNodesInState(myNetwork,"selected",true); }}} == How to handle events from a network (and discuss each event type)? == To handle Cytoscape events, implement the listener interface and register it. {{{ #!java // Define a class, which implements a listener interface public class MyListenerClass implements NetworkAddedListener { .... public void handleEvent(NetworkAddedEvent e){ // do something here } } }}} In the `start` method of your `CyActivator` class: {{{ #!java // Register the listener in the CyActivator class registerService(bc,myListenerClass, NetworkAddedListener.class, new Properties()); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to change the background color of a view? == Network background color is changed as a _visual property_. {{{ #!java // Set the background of current view to RED view.setVisualProperty(BasicVisualLexicon.NETWORK_BACKGROUND_PAINT, Color.red); view.updateView(); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to zoom a network view? == {{{ #!java // Get the scale and adjust double newScale = view.getVisualProperty(NETWORK_SCALE_FACTOR).doubleValue() * scale; view.setVisualProperty(NETWORK_SCALE_FACTOR, newScale); ... view.updateView(); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to load attribute data? == There are two step to load attribute to a table. (1) Create a global table and populate it. (2) Map the global table to specific table based on a key attribute, i.e. create virtual column for the table. {{{ #!java // Define a task public class CreateTableTask extends AbstractTask { .... @Override public void run(TaskMonitor tm) throws IOException { // Step 1: create a new table CyTable table = tableFactory.createTable("MyAttrTable " + Integer.toString(numImports++), "name", String.class, true, true); // create a column for the table String attributeNmae = "MyAttributeName"; table.createColumn(attributeNmae, Integer.class, false); // Step 2: populate the table with some data String[] keys = {"YLL021W","YBR170C","YLR249W"}; //map to the the "name" column CyRow row = table.getRow(keys[0]); row.set(attributeNmae, new Integer(2)); row = table.getRow(keys[1]); row.set(attributeNmae, new Integer(3)); row = table.getRow(keys[2]); row.set(attributeNmae, new Integer(4)); // We are loading node attribute Class<? extends CyTableEntry> type = CyNode.class; // Step 3: pass the new table to MapNetworkAttrTask super.insertTasksAfterCurrentTask( new MapNetworkAttrTask(type,table,netMgr,appMgr,rootNetworkManager) ); } .... } }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to remove attributes? == 1. get the CyTable through the network {{{ #!java // case for Node table CyTable nodeTable = network.getDefaultNodeTable(); }}} 2. Find the column and delete it {{{ #!java if(nodeTable.getColumn(columnName)!= null){ nodeTable.deleteColumn(columnName); } }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to use a web service client? == == How to write a web service client? == Define a client class, which must implements WebServiceClient {{{ #!java public class MyWebserviceClient extends AbstractWebServiceGUIClient implements TableImportWebServiceClient { ... } }}} // Define a UI class for the setting of the client, may listen to Cytoscape events {{{ #!java public class MyWebserviceClientPanel extends JPanel implements ColumnCreatedListener, ColumnDeletedListener, SetCurrentNetworkListener { ... } }}} After the client is registered as service, the client may be find under File-->Import->Table-->Public databases... == How to use the VizMapper programmatically? == Cytosape provides services for using visual mapping programming. There services are VisualMappingManager, VisualStyleFactory and VisualMappingFunctionFactory. App should get references to these services in CyActivator class, the entry point of the app. We can create new visualStyle with VisualStyleFactory and create mapping function with VisualMappingFunctionFactory very easily. After a new visual style is created, it should register with the VisualMappingManger, in this way the new visual style will be available throughout Cytoscape. {{{ #!java // To get references to services in CyActivator class VisualMappingManager vmmServiceRef = getService(bc,VisualMappingManager.class); VisualStyleFactory visualStyleFactoryServiceRef = getService(bc,VisualStyleFactory.class); VisualMappingFunctionFactory vmfFactoryC = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=continuous)"); VisualMappingFunctionFactory vmfFactoryD = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=discrete)"); VisualMappingFunctionFactory vmfFactoryP = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=passthrough)"); // To create a new VisualStyle object and set the mapping function VisualStyle vs= this.visualStyleFactoryServiceRef.createVisualStyle("My visual style"); //Use pass-through mapping String ctrAttrName1 = "SUID"; PassthroughMapping pMapping = (PassthroughMapping) vmfFactoryP.createVisualMappingFunction(ctrAttrName1, String.class, attrForTest, BasicVisualLexicon.NODE_LABEL); vs.addVisualMappingFunction(pMapping); // Add the new style to the VisualMappingManager vmmServiceRef.addVisualStyle(vs); // Apply the visual style to a NetwokView vs.apply(myNetworkView); myNetworkView.updateView(); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to apply a continuous color gradient to nodes according to their degree? == {{{ #!java // Set node color map to attribute "Degree" ContinuousMapping mapping = (ContinuousMapping) this.continuousMappingFactoryServiceRef.createVisualMappingFunction("Degree", Integer.class, BasicVisualLexicon.NODE_FILL_COLOR); // Define the points Double val1 = 2d; BoundaryRangeValues<Paint> brv1 = new BoundaryRangeValues<Paint>(Color.RED, Color.GREEN, Color.PINK); Double val2 = 12d; BoundaryRangeValues<Paint> brv2 = new BoundaryRangeValues<Paint>(Color.WHITE, Color.YELLOW, Color.BLACK); // Set the points mapping.addPoint(val1, brv1); mapping.addPoint(val2, brv2); // add the mapping to visual style vs.addVisualMappingFunction(mapping); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to load a visual properties file? == Cytoscape provide a service 'LoadVizmapFileTaskFactory' for loading visual styles definded in a property file. {{{ #!java // get a reference to Cytoscape service -- LoadVizmapFileTaskFactory LoadVizmapFileTaskFactory loadVizmapFileTaskFactory = getService(bc,LoadVizmapFileTaskFactory.class); }}} {{{ #!java // Use the service to load visual style, 'f' is the File object to hold the visual properties Set<VisualStyle> vsSet = loadVizmapFileTaskFactory.loadStyles(f); }}} == How to write a layout algorithm? == First define a layout class, which implements CyLayoutAlgorithm interface or extends AbstractLayoutAlgorithm class. Then register the layout class as a service. {{{ #!java // Define a layout class public class MyLayout extends AbstractLayoutAlgorithm { ... } // Define a layout task class public class MyLayoutTask extends AbstractLayoutTask { ... //Perform actual layout task final protected void doLayout(final TaskMonitor taskMonitor) { ... } ... } // Register the layout class as a service in CyActivator class Properties myLayoutProps = new Properties(); myLayoutProps.setProperty("preferredMenu","My Layouts"); registerService(bc,myLayout,CyLayoutAlgorithm.class, myLayoutProps); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to write a Group Viewer? == {{{ #!java }}} == How to add components to the node view, edge view, and attribute browser context menus? == To add a context menu to a NodeView, define a class, which extends AbstractNodeViewTaskFactory, and register the NodeViewTaskFactory as service. We can add the title of menu item by setting the service property when we register the nodeViewTaskFactory. To add context menu to the table browser, define a class, which extends AbstractTableCellTaskFactory. Create an instance and register it as service (Note register as TableCellTaskFactory). {{{ #!java // Define a class MyNodeViewTaskFactory public class MyNodeViewTaskFactory extends AbstractNodeViewTaskFactory { ... } // Register myNodeViewTaskFactory as a service in CyActivator Properties myNodeViewTaskFactoryProps = new Properties(); myNodeViewTaskFactoryProps.setProperty("title","My context menu title"); registerService(bc,myNodeViewTaskFactory,NodeViewTaskFactory.class, myNodeViewTaskFactoryProps); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
|
Line 102: | Line 490: |
== How to determine which nodes are currently selected on a network? == == How to handle events from a network (and discuss each event type)? == == How to change the background color of a view? == == How to zoom a network view? == == How to load attribute data? == == How to remove attributes? == == How to use a web service client? == == How to write a web service client? == == How to use the VizMapper programmatically? == == How to apply a continuous color gradient to nodes according to their degree? == == How to load a visual properties file? == == How to write a layout algorithm? == == How to write a Group Viewer? == == How to add components to the node view, edge view, and attribute browser context menus? == == How to save/restore plugin states? == |
There are two events, which are important for saving/restoring App state. The two evetns are SessionAboutToBeSavedEvent and SessionLoadedEvent. App should implement the two listeners and register them. {{{ #!java // Implements the session event listeners public class MyClass implements SessionAboutToBeSavedListener, SessionLoadedListener { // Save app state in a file public void handleEvent(SessionAboutToBeSavedEvent e){ // save app state file "myAppStateFile" ... } // restore app state from a file public void handleEvent(SessionLoadedEvent e){ if (e.getLoadedSession().getAppFileListMap() == null || e.getLoadedSession().getAppFileListMap().size() ==0){ return; } List<File> files = e.getLoadedSession().getAppFileListMap().get("myAppStateFile"); ... } } // Register the two listeners in the CyActivator class registerService(bc,myClass,SessionAboutToBeSavedListener.class, new Properties()); registerService(bc,myClass,SessionLoadedListener.class, new Properties()); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
Line 140: | Line 526: |
== How to add new attribute functions via a Cytoscape plug-in? == |
Get a Cytoscape service DialogTaskManager and execute the task through the taskManager. {{{ #!java // Get a Cytoscape service 'DialogTaskManager' in CyActivator class DialogTaskManager dialogTaskManager = getService(bc, DialogTaskManager.class); // Define a task and set the progress in the run() method public class MyTask extends AbstractTask { ... public void run(final TaskMonitor taskMonitor) { // Give the task a title. taskMonitor.setTitle("My task"); ... taskMonitor.setProgress(0.1); // do something here ... taskMonitor.setProgress(1.0); } // Execute the task through the TaskManager DialogTaskManager.execute(myTaskFactory); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to add new attribute functions via a Cytoscape App? == Here we will go through all the steps necessary to create a new built-in function IXOR(). The complete example code and can be downloaded from here. The easiest part is writing the actual plug-in class, which look similar to this: {{{ #!java import org.cytoscape.equations.EquationCompiler; import org.cytoscape.equations.Interpreter; import org.cytoscape.equations.EquationParser; public class Sample23 { public Sample23(EquationCompiler eqCompilerRef, Interpreter interpreterRef){ final EquationParser theParser = eqCompilerRef.getParser(); theParser.registerFunction(new IXor()); } } }}} Here we register a new built-in function called IXor. You can also register multiple built-in functions if you prefer. The next part is creating one class each for every new built-in. Each such class has to implement the org.cytoscape.equations.Function interface usually via org.cytoscape.equations.AbstractFunction. The easiest way to get started is to peruse the existing built-ins in equations Cytoscape core library and look for a function with a similar or identical argument list. If you can't find one it may still be instructive to read through the implementation of a couple of existing functions. First you have to create the constructor where you describe the arguments that your function will take: {{{ #!java public IXor() { super(new ArgDescriptor[] { new ArgDescriptor(ArgType.INT, "arg1", "A quantity that can be converted to an integer."), new ArgDescriptor(ArgType.INT, "arg2", "A quantity that can be converted to an integer."), }); }}} The most trivial to implement methods are getName(), and getFunctionSummary(). {{{ #!java /** * Used to parse the function string. This name is treated in a case-insensitive manner! * @return the name by which you must call the function when used in an attribute equation. */ public String getName() { return "IXOR"; } /** * Used to provide help for users. * @return a description of what this function does */ public String getFunctionSummary() { return "Returns an integer value that is the exclusive-or of 2 other integer values."; } }}} The next method is still simple but already touches on the one area that is somewhat complicated in attribute functions: data types and data type conversions! {{{ #!java public Class getReturnType() { return Long.class; } }}} Our example function is supposed to return an integer value. Integers in equation functions are represented as instances of class java.lang.Long. Therefore it is imperative not to return Integer.class! The user of the function can be blissfully unaware of this distinction. Just remember to always substitute Long for Integer and you should be fine. The next method evaluateFunction() is the one that gets called when an expression is being evaluated. No argument type-checking is needed here because the compiler already took care of that for us. But, we need to handle all possible valid argument types and counts. (N.B., functions may be overloaded and/or variadic.) In this example the arguments can be any combination of Long and Double. {{{ #!java public Object evaluateFunction(final Object[] args) { long arg1; try { arg1 = FunctionUtil.getArgAsLong(args[0]); } catch (final Exception e) { throw new IllegalArgumentException("IXOR: can't convert the 1st argument to an integer!"); } long arg2; try { arg2 = FunctionUtil.getArgAsLong(args[0]); } catch (final Exception e) { throw new IllegalArgumentException("IXOR: can't convert the 2nd argument to an integer!"); } final long result = arg1 ^ arg2; return (Long)result; } }}} See Also [[http://wiki.cytoscape.org/Implementing_Attribute_Equations_Functions|This tutorial]] on how to write attribute functions may also be helpful. The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. |
Line 145: | Line 648: |
{{{ #!java }}} == How to add NetworkViewTaskFactories to the right click or double click menus on network view? == First the customized TaskFactory must implement NetworkViewTaskFactory interface or extend AbstractNetworkViewTaskFactory. Secondly, the service property "preferredAction" should be set to be "OPEN" for double click, or "NEW" for right click menu. {{{ #!java MyNetworkViewTaskFactory myNetworkViewTaskFactory = new MyNetworkViewTaskFactory(applicationManagerManagerServiceRef); // Add double click menu to the network view Properties myNetworkViewTaskFactoryProps = new Properties(); myNetworkViewTaskFactoryProps.setProperty("preferredAction","OPEN"); myNetworkViewTaskFactoryProps.setProperty("title","my title"); // Register the service registerService(bc,myNetworkViewTaskFactory,NetworkViewTaskFactory.class, myNetworkViewTaskFactoryProps); }}} This also applies to add menu item to the double click / right click of nodeView or edgeView. {{{ #!java // To add a right click menu item on node view, set "preferredAction" to "NEW" MyNodeViewTaskFactory myNodeViewTaskFactory = new MyNodeViewTaskFactory(); // Add double click menu item to the node view Properties myNodeViewTaskFactoryProps = new Properties(); myNodeViewTaskFactoryProps.setProperty("preferredAction","NEW"); myNodeViewTaskFactoryProps.setProperty("title","my node action"); // Register the service registerService(bc,myNodeViewTaskFactory,NodeViewTaskFactory.class, myNodeViewTaskFactoryProps); }}} The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == XXXXXX????? == {{{ #!java }}} == How to build a network reader to support my own format? == First, we should define the format of my network file and its file extension. Let's say, each line in our network file has two columns, tab-delimited. And we define file extension '.tc', stands for 'two columns'. {{{ #!java //1. define a file filter (BasicCyFileFilter), to support the reader to read the file with extension '.tc' HashSet<String> extensions = new HashSet<String>(); extensions.add("tc"); HashSet<String> contentTypes = new HashSet<String>(); contentTypes.add("txt"); String description = "My test filter"; DataCategory category = DataCategory.NETWORK; BasicCyFileFilter filter = new BasicCyFileFilter(extensions,contentTypes, description, category, swingAdapter.getStreamUtil()); //2. Create an instance of the ReaderFactory // Note that extends TCReaderFactory must implement the interface InputStreamTaskFactory or extends the class AbstractInputStreamTaskFactory. // And the defined task must implement CyNetworkReader TCReaderFactory factory = new TCReaderFactory(filter, swingAdapter.getCyNetworkFactory(), swingAdapter.getCyNetworkViewFactory()); //3. register the ReaderFactory as an InputStreamTaskFactory. Properties props = new Properties(); props.setProperty("readerDescription","TC file reader"); props.setProperty("readerId","tcNetworkReader"); swingAdapter.getCyServiceRegistrar().registerService(factory, InputStreamTaskFactory.class, props); }}} Compile [http://chianti.ucsd.edu/svn/core3/samples/trunk/sample27a|this sample code], and install the app in Cytoscape. When we try to import a network from a file (File-->Import-->Network-->File..), we will find file type '.tc' is listed as one of the file types supported by Cytoscape. The source code of sample Apps can be find [[http://chianti.ucsd.edu/svn/core3/samples/trunk/|here]]. == How to build a plugin with dependency on 3rd party library? == {{{ #!java }}} |
Contents
- About the cookbook
- How to use the cookbook?
-
The list of app examples in the cookbook
- Introduction
- How to add a tabbed Panel to Control panel?
- How to add an image icon (menu item) to the toolbar?
- How to create a submenu?
- How to create, modify, and destroy a network, nodes, and edges?
- How to create, modify, and destroy a network view?
- How to determine which nodes are currently selected on a network?
- How to handle events from a network (and discuss each event type)?
- How to change the background color of a view?
- How to zoom a network view?
- How to load attribute data?
- How to remove attributes?
- How to use a web service client?
- How to write a web service client?
- How to use the VizMapper programmatically?
- How to apply a continuous color gradient to nodes according to their degree?
- How to load a visual properties file?
- How to write a layout algorithm?
- How to write a Group Viewer?
- How to add components to the node view, edge view, and attribute browser context menus?
- How to save/restore app states?
- How to use the Cytoscape task monitor to show the progress of my job?
- How to add new attribute functions via a Cytoscape App?
- How to add plug-in specific help to the Cytoscape main help system?
- How to add NetworkViewTaskFactories to the right click or double click menus on network view?
- XXXXXX?????
- How to build a network reader to support my own format?
- How to build a plugin with dependency on 3rd party library?
- Trouble shooting
- Recommendations
- App Porting Hints
- Questions, suggestions
About the cookbook
To help app developers to develop their own app, the Cytoscape developer team developed the cookbook, a collection of small apps. The cookbook could be used to
- get your feet wet
- find a template project to extend
- find out how to do something in Cytoscape.
How to use the cookbook?
SVN repository: http://chianti.ucsd.edu/svn/core3/samples/trunk/
Steps:
- Check out sample project from SVN repository with the above URL
Compile with mvn clean install
- Copy the jar in the project target directory to Cytoscape installation: deploy directory
- Start Cytoscape 3.0
The list of app examples in the cookbook
The list of sample apps is basically the same as in the cookbook for Cytoscape 2.X. They are mostly Cytoscape Bundle apps, but also include a few Simple apps. Some of them are,
Introduction
This tutorial will show some code snippets about how to use Cytoscape APIs on app development. Each feature is included in a sample app, which can serve as a starting point or template for new app developers. The code snippets can also be used as reference for experienced developers.
Cytoscape app is usually packaged in a jar file and deployed to the deploy directory of Cytoscape. When Cytoscape starts up and initializes, its app manager will look up all the apps in deploy directory.
There are two types of Cytoscape App, Bundle app and Simplified App. In this cook book, we provide sample for both types.
If app developer will share and publish their Apps, they are encouraged to submit their Apps to Cytoscape app store website at http://apps.cytoscape.org/ .
How to add a tabbed Panel to Control panel?
Here's how to add a tabbed panel to the control panel.
1 // Define a CytoPanel class
2 public class MyCytoPanel extends JPanel implements CytoPanelComponent {
3
4 ...
5 @Override
6 public CytoPanelName getCytoPanelName() {
7 return CytoPanelName.WEST;
8 }
9 ...
10 }
11
1 // In the start method of your CyActivator class:
2 // Create an instance:
3 MyCytoPanel myPanel = new MyPanel();
4 // Register it as a service:
5 registerService(bc,myCytoPanel,CytoPanelComponent.class, new Properties());
6
The source code of sample Apps can be find here.
How to add an image icon (menu item) to the toolbar?
1 // Define a CyAction class
2 public class AddImageIconAction extends AbstractCyAction {
3
4 public AddImageIconAction(CySwingApplication desktopApp){
5 ...
6 ImageIcon icon = new ImageIcon(getClass().getResource("/images/tiger.jpg"));
7
8 putValue(LARGE_ICON_KEY, icon);
9 ...
10 }
11
12 public boolean isInToolBar() {
13 return true;
14 }
15 ...
16 }
17
1 // In the start method of your CyActivator class:
2 // Create an instance:
3 AddImageIconAction addImageIconAction = new AddImageIconAction(cytoscapeDesktopService);
4 // Register it as a service:
5 registerService(bc,addImageIconAction,CyAction.class, new Properties());
6
The source code of sample Apps can be find here.
How to create a submenu?
1 // Define a CyAction class
2 public class Sample04 extends AbstractCyAction {
3 ...
4 public Sample04(CySwingApplication desktopApp){
5 // Add a sub-menu item -- Apps->Sample04->sample04
6 super("sample04...");
7 setPreferredMenu("Apps.Sample04");
8 //Specify the menuGravity value to put the menuItem in the desired place
9 setMenuGravity(2.0f);
10 ...
11 }
12
1 // In the start method of your CyActivator class:
2 // Create an instance:
3 Sample04 Sample04Action = new Sample04(cytoscapeDesktopService);
4 // Register it as a service:
5 registerService(bc,Sample04Action,CyAction.class, new Properties());
6
The source code of sample Apps can be find here.
How to create, modify, and destroy a network, nodes, and edges?
To create a network, get a reference to the CyNetworkFactory service, and tell it to create a network. With the new network, nodes and edges can be created through the CyNetwork interface.
1 // To get a reference of CyNetworkFactory at CyActivator class of the App
2 CyNetworkFactory cyNetworkFactoryServiceRef = getService(bc,CyNetworkFactory.class);
3
4 ...
5
6 // To create a new network
7 CyNetwork myNet = cnf.createNetwork();
8
9 ...
10
11 // Add two nodes to the network
12 CyNode node1 = myNet.addNode();
13 CyNode node2 = myNet.addNode();
14
15 // set name for new nodes
16 myNet.getDefaultNodeTable().getRow(node1.getSUID()).set("name", "Node1");
17 myNet.getDefaultNodeTable().getRow(node2.getSUID()).set("name", "Node2");
18
19 // Add an edge
20 myNet.addEdge(node1, node2, true);
21
Destroying networks is done through the CyNetworkManager service.
First, in the start method of your CyActivator class:
1 // Get a CyNetworkManager
2 CyNetworkManager netMgr = getService(bc,CyNetworkManager.class);
3
Now you can use CyNetworkManager in your code:
1 // Destroy a network with NetworkManager
2 netMgr.destroyNetwork(myNet);
3
The source code of sample Apps can be find here.
How to create, modify, and destroy a network view?
To create a network, get a reference to the CyNetworkViewFactory service, and tell it to create a network view.
First, in the start method of your CyActivator class:
1 // Get a CyNetworkViewFactory
2 CyNetworkViewFactory cyNetworkViewFactoryServiceRef = getService(bc,CyNetworkViewFactory.class);
3
Now you can use CyNetworkViewFactory in your code:
1 // Create a new network view
2 CyNetworkView myView = cnvf.createNetworkView(myNet);
3
Destroying networks is done through the CyNetworkViewManager service.
First, in the start method of your CyActivator class:
1 // get a CyNetworkViewManager
2 CyNetworkViewManager cyNetworkViewManagerServiceRef = getService(bc,CyNetworkViewManager.class);
3
Now you can use CyNetworkViewManager in your code:
1 // destroy a network view through NetworkViewManager
2 networkViewManager.destroyNetworkView(myView);
3
The source code of sample Apps can be find here.
How to determine which nodes are currently selected on a network?
We can use CyTableUtil to get the list of selected nodes in a network
1 //Get the selected nodes
2 List<CyNode> nodes = CyTableUtil.getNodesInState(myNetwork,"selected",true);
3
How to handle events from a network (and discuss each event type)?
To handle Cytoscape events, implement the listener interface and register it.
1 // Define a class, which implements a listener interface
2 public class MyListenerClass implements NetworkAddedListener {
3 ....
4 public void handleEvent(NetworkAddedEvent e){
5 // do something here
6 }
7 }
8
In the start method of your CyActivator class:
1 // Register the listener in the CyActivator class
2 registerService(bc,myListenerClass, NetworkAddedListener.class, new Properties());
3
The source code of sample Apps can be find here.
How to change the background color of a view?
Network background color is changed as a _visual property_.
1 // Set the background of current view to RED
2 view.setVisualProperty(BasicVisualLexicon.NETWORK_BACKGROUND_PAINT, Color.red);
3 view.updateView();
4
The source code of sample Apps can be find here.
How to zoom a network view?
1 // Get the scale and adjust
2 double newScale = view.getVisualProperty(NETWORK_SCALE_FACTOR).doubleValue() * scale;
3 view.setVisualProperty(NETWORK_SCALE_FACTOR, newScale);
4 ...
5 view.updateView();
6
The source code of sample Apps can be find here.
How to load attribute data?
There are two step to load attribute to a table. (1) Create a global table and populate it. (2) Map the global table to specific table based on a key attribute, i.e. create virtual column for the table.
1 // Define a task
2 public class CreateTableTask extends AbstractTask {
3 ....
4 @Override
5 public void run(TaskMonitor tm) throws IOException {
6 // Step 1: create a new table
7 CyTable table = tableFactory.createTable("MyAttrTable " + Integer.toString(numImports++),
8 "name", String.class, true, true);
9
10 // create a column for the table
11 String attributeNmae = "MyAttributeName";
12 table.createColumn(attributeNmae, Integer.class, false);
13
14 // Step 2: populate the table with some data
15 String[] keys = {"YLL021W","YBR170C","YLR249W"}; //map to the the "name" column
16 CyRow row = table.getRow(keys[0]);
17 row.set(attributeNmae, new Integer(2));
18
19 row = table.getRow(keys[1]);
20 row.set(attributeNmae, new Integer(3));
21
22 row = table.getRow(keys[2]);
23 row.set(attributeNmae, new Integer(4));
24
25 // We are loading node attribute
26 Class<? extends CyTableEntry> type = CyNode.class;
27
28 // Step 3: pass the new table to MapNetworkAttrTask
29 super.insertTasksAfterCurrentTask( new MapNetworkAttrTask(type,table,netMgr,appMgr,rootNetworkManager) );
30 }
31 ....
32 }
33
The source code of sample Apps can be find here.
How to remove attributes?
1. get the CyTable through the network
1 // case for Node table
2 CyTable nodeTable = network.getDefaultNodeTable();
3
2. Find the column and delete it
1 if(nodeTable.getColumn(columnName)!= null){
2 nodeTable.deleteColumn(columnName);
3 }
4
The source code of sample Apps can be find here.
How to use a web service client?
How to write a web service client?
Define a client class, which must implements WebServiceClient
1 public class MyWebserviceClient extends AbstractWebServiceGUIClient implements TableImportWebServiceClient {
2 ...
3 }
4
// Define a UI class for the setting of the client, may listen to Cytoscape events
1 public class MyWebserviceClientPanel extends JPanel implements ColumnCreatedListener, ColumnDeletedListener,
2 SetCurrentNetworkListener {
3 ...
4 }
5
After the client is registered as service, the client may be find under File-->Import->Table-->Public databases...
How to use the VizMapper programmatically?
Cytosape provides services for using visual mapping programming. There services are VisualMappingManager, VisualStyleFactory and VisualMappingFunctionFactory. App should get references to these services in CyActivator class, the entry point of the app. We can create new visualStyle with VisualStyleFactory and create mapping function with VisualMappingFunctionFactory very easily. After a new visual style is created, it should register with the VisualMappingManger, in this way the new visual style will be available throughout Cytoscape.
1 // To get references to services in CyActivator class
2 VisualMappingManager vmmServiceRef = getService(bc,VisualMappingManager.class);
3
4 VisualStyleFactory visualStyleFactoryServiceRef = getService(bc,VisualStyleFactory.class);
5
6 VisualMappingFunctionFactory vmfFactoryC = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=continuous)");
7 VisualMappingFunctionFactory vmfFactoryD = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=discrete)");
8 VisualMappingFunctionFactory vmfFactoryP = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=passthrough)");
9
10
11 // To create a new VisualStyle object and set the mapping function
12 VisualStyle vs= this.visualStyleFactoryServiceRef.createVisualStyle("My visual style");
13
14
15 //Use pass-through mapping
16 String ctrAttrName1 = "SUID";
17 PassthroughMapping pMapping = (PassthroughMapping) vmfFactoryP.createVisualMappingFunction(ctrAttrName1, String.class, attrForTest, BasicVisualLexicon.NODE_LABEL);
18
19 vs.addVisualMappingFunction(pMapping);
20
21
22 // Add the new style to the VisualMappingManager
23 vmmServiceRef.addVisualStyle(vs);
24
25
26 // Apply the visual style to a NetwokView
27 vs.apply(myNetworkView);
28 myNetworkView.updateView();
29
The source code of sample Apps can be find here.
How to apply a continuous color gradient to nodes according to their degree?
1
2 // Set node color map to attribute "Degree"
3 ContinuousMapping mapping = (ContinuousMapping)
4 this.continuousMappingFactoryServiceRef.createVisualMappingFunction("Degree", Integer.class, BasicVisualLexicon.NODE_FILL_COLOR);
5
6 // Define the points
7 Double val1 = 2d;
8 BoundaryRangeValues<Paint> brv1 = new BoundaryRangeValues<Paint>(Color.RED, Color.GREEN, Color.PINK);
9
10 Double val2 = 12d;
11 BoundaryRangeValues<Paint> brv2 = new BoundaryRangeValues<Paint>(Color.WHITE, Color.YELLOW, Color.BLACK);
12
13 // Set the points
14 mapping.addPoint(val1, brv1);
15 mapping.addPoint(val2, brv2);
16
17 // add the mapping to visual style
18 vs.addVisualMappingFunction(mapping);
19
The source code of sample Apps can be find here.
How to load a visual properties file?
Cytoscape provide a service 'LoadVizmapFileTaskFactory' for loading visual styles definded in a property file.
1 // get a reference to Cytoscape service -- LoadVizmapFileTaskFactory
2 LoadVizmapFileTaskFactory loadVizmapFileTaskFactory = getService(bc,LoadVizmapFileTaskFactory.class);
3
1 // Use the service to load visual style, 'f' is the File object to hold the visual properties
2 Set<VisualStyle> vsSet = loadVizmapFileTaskFactory.loadStyles(f);
3
How to write a layout algorithm?
First define a layout class, which implements CyLayoutAlgorithm interface or extends AbstractLayoutAlgorithm class. Then register the layout class as a service.
1 // Define a layout class
2 public class MyLayout extends AbstractLayoutAlgorithm {
3 ...
4 }
5
6
7 // Define a layout task class
8 public class MyLayoutTask extends AbstractLayoutTask {
9 ...
10
11 //Perform actual layout task
12 final protected void doLayout(final TaskMonitor taskMonitor) {
13 ...
14 }
15 ...
16 }
17
18
19 // Register the layout class as a service in CyActivator class
20 Properties myLayoutProps = new Properties();
21 myLayoutProps.setProperty("preferredMenu","My Layouts");
22 registerService(bc,myLayout,CyLayoutAlgorithm.class, myLayoutProps);
23
The source code of sample Apps can be find here.
How to write a Group Viewer?
1
2
How to add components to the node view, edge view, and attribute browser context menus?
To add a context menu to a NodeView, define a class, which extends AbstractNodeViewTaskFactory, and register the NodeViewTaskFactory as service. We can add the title of menu item by setting the service property when we register the nodeViewTaskFactory.
To add context menu to the table browser, define a class, which extends AbstractTableCellTaskFactory. Create an instance and register it as service (Note register as TableCellTaskFactory).
1 // Define a class MyNodeViewTaskFactory
2 public class MyNodeViewTaskFactory extends AbstractNodeViewTaskFactory {
3 ...
4 }
5
6
7 // Register myNodeViewTaskFactory as a service in CyActivator
8 Properties myNodeViewTaskFactoryProps = new Properties();
9 myNodeViewTaskFactoryProps.setProperty("title","My context menu title");
10 registerService(bc,myNodeViewTaskFactory,NodeViewTaskFactory.class, myNodeViewTaskFactoryProps);
11
The source code of sample Apps can be find here.
How to save/restore app states?
There are two events, which are important for saving/restoring App state. The two evetns are SessionAboutToBeSavedEvent and SessionLoadedEvent. App should implement the two listeners and register them.
1 // Implements the session event listeners
2 public class MyClass implements SessionAboutToBeSavedListener, SessionLoadedListener {
3
4 // Save app state in a file
5 public void handleEvent(SessionAboutToBeSavedEvent e){
6 // save app state file "myAppStateFile"
7 ...
8 }
9
10 // restore app state from a file
11 public void handleEvent(SessionLoadedEvent e){
12
13 if (e.getLoadedSession().getAppFileListMap() == null || e.getLoadedSession().getAppFileListMap().size() ==0){
14 return;
15 }
16 List<File> files = e.getLoadedSession().getAppFileListMap().get("myAppStateFile");
17 ...
18 }
19 }
20
21
22 // Register the two listeners in the CyActivator class
23 registerService(bc,myClass,SessionAboutToBeSavedListener.class, new Properties());
24 registerService(bc,myClass,SessionLoadedListener.class, new Properties());
25
The source code of sample Apps can be find here.
How to use the Cytoscape task monitor to show the progress of my job?
Get a Cytoscape service DialogTaskManager and execute the task through the taskManager.
1 // Get a Cytoscape service 'DialogTaskManager' in CyActivator class
2 DialogTaskManager dialogTaskManager = getService(bc, DialogTaskManager.class);
3
4
5 // Define a task and set the progress in the run() method
6 public class MyTask extends AbstractTask {
7 ...
8 public void run(final TaskMonitor taskMonitor) {
9 // Give the task a title.
10 taskMonitor.setTitle("My task");
11 ...
12 taskMonitor.setProgress(0.1);
13
14 // do something here
15
16 ...
17 taskMonitor.setProgress(1.0);
18 }
19
20
21 // Execute the task through the TaskManager
22 DialogTaskManager.execute(myTaskFactory);
23
The source code of sample Apps can be find here.
How to add new attribute functions via a Cytoscape App?
Here we will go through all the steps necessary to create a new built-in function IXOR(). The complete example code and can be downloaded from here. The easiest part is writing the actual plug-in class, which look similar to this:
1 import org.cytoscape.equations.EquationCompiler;
2 import org.cytoscape.equations.Interpreter;
3 import org.cytoscape.equations.EquationParser;
4
5 public class Sample23 {
6
7 public Sample23(EquationCompiler eqCompilerRef, Interpreter interpreterRef){
8 final EquationParser theParser = eqCompilerRef.getParser();
9 theParser.registerFunction(new IXor());
10 }
11 }
12
Here we register a new built-in function called IXor. You can also register multiple built-in functions if you prefer.
The next part is creating one class each for every new built-in. Each such class has to implement the org.cytoscape.equations.Function interface usually via org.cytoscape.equations.AbstractFunction. The easiest way to get started is to peruse the existing built-ins in equations Cytoscape core library and look for a function with a similar or identical argument list. If you can't find one it may still be instructive to read through the implementation of a couple of existing functions.
First you have to create the constructor where you describe the arguments that your function will take:
1 public IXor() {
2 super(new ArgDescriptor[] {
3 new ArgDescriptor(ArgType.INT, "arg1", "A quantity that can be converted to an integer."),
4 new ArgDescriptor(ArgType.INT, "arg2", "A quantity that can be converted to an integer."),
5 });
6
The most trivial to implement methods are getName(), and getFunctionSummary().
1 /**
2 * Used to parse the function string. This name is treated in a case-insensitive manner!
3 * @return the name by which you must call the function when used in an attribute equation.
4 */
5 public String getName() { return "IXOR"; }
6
7 /**
8 * Used to provide help for users.
9 * @return a description of what this function does
10 */
11 public String getFunctionSummary() { return "Returns an integer value that is the exclusive-or of 2 other integer values."; }
12
The next method is still simple but already touches on the one area that is somewhat complicated in attribute functions: data types and data type conversions!
1 public Class getReturnType() { return Long.class; }
2
Our example function is supposed to return an integer value. Integers in equation functions are represented as instances of class java.lang.Long. Therefore it is imperative not to return Integer.class! The user of the function can be blissfully unaware of this distinction. Just remember to always substitute Long for Integer and you should be fine.
The next method evaluateFunction() is the one that gets called when an expression is being evaluated. No argument type-checking is needed here because the compiler already took care of that for us. But, we need to handle all possible valid argument types and counts. (N.B., functions may be overloaded and/or variadic.) In this example the arguments can be any combination of Long and Double.
1 public Object evaluateFunction(final Object[] args) {
2 long arg1;
3 try {
4 arg1 = FunctionUtil.getArgAsLong(args[0]);
5 } catch (final Exception e) {
6 throw new IllegalArgumentException("IXOR: can't convert the 1st argument to an integer!");
7 }
8
9 long arg2;
10 try {
11 arg2 = FunctionUtil.getArgAsLong(args[0]);
12 } catch (final Exception e) {
13 throw new IllegalArgumentException("IXOR: can't convert the 2nd argument to an integer!");
14 }
15
16 final long result = arg1 ^ arg2;
17 return (Long)result;
18 }
19
See Also
This tutorial on how to write attribute functions may also be helpful.
The source code of sample Apps can be find here.
How to add plug-in specific help to the Cytoscape main help system?
1
2
How to add NetworkViewTaskFactories to the right click or double click menus on network view?
First the customized TaskFactory must implement NetworkViewTaskFactory interface or extend AbstractNetworkViewTaskFactory. Secondly, the service property "preferredAction" should be set to be "OPEN" for double click, or "NEW" for right click menu.
1 MyNetworkViewTaskFactory myNetworkViewTaskFactory = new MyNetworkViewTaskFactory(applicationManagerManagerServiceRef);
2
3 // Add double click menu to the network view
4 Properties myNetworkViewTaskFactoryProps = new Properties();
5 myNetworkViewTaskFactoryProps.setProperty("preferredAction","OPEN");
6 myNetworkViewTaskFactoryProps.setProperty("title","my title");
7
8 // Register the service
9 registerService(bc,myNetworkViewTaskFactory,NetworkViewTaskFactory.class, myNetworkViewTaskFactoryProps);
10
This also applies to add menu item to the double click / right click of nodeView or edgeView.
1 // To add a right click menu item on node view, set "preferredAction" to "NEW"
2 MyNodeViewTaskFactory myNodeViewTaskFactory = new MyNodeViewTaskFactory();
3
4 // Add double click menu item to the node view
5 Properties myNodeViewTaskFactoryProps = new Properties();
6 myNodeViewTaskFactoryProps.setProperty("preferredAction","NEW");
7 myNodeViewTaskFactoryProps.setProperty("title","my node action");
8
9 // Register the service
10 registerService(bc,myNodeViewTaskFactory,NodeViewTaskFactory.class, myNodeViewTaskFactoryProps);
11
The source code of sample Apps can be find here.
XXXXXX?????
1
2
How to build a network reader to support my own format?
First, we should define the format of my network file and its file extension. Let's say, each line in our network file has two columns, tab-delimited. And we define file extension '.tc', stands for 'two columns'.
1 //1. define a file filter (BasicCyFileFilter), to support the reader to read the file with extension '.tc'
2 HashSet<String> extensions = new HashSet<String>();
3 extensions.add("tc");
4 HashSet<String> contentTypes = new HashSet<String>();
5 contentTypes.add("txt");
6 String description = "My test filter";
7 DataCategory category = DataCategory.NETWORK;
8 BasicCyFileFilter filter = new BasicCyFileFilter(extensions,contentTypes, description, category, swingAdapter.getStreamUtil());
9
10
11 //2. Create an instance of the ReaderFactory
12 // Note that extends TCReaderFactory must implement the interface InputStreamTaskFactory or extends the class AbstractInputStreamTaskFactory.
13 // And the defined task must implement CyNetworkReader
14 TCReaderFactory factory = new TCReaderFactory(filter, swingAdapter.getCyNetworkFactory(), swingAdapter.getCyNetworkViewFactory());
15
16
17
18 //3. register the ReaderFactory as an InputStreamTaskFactory.
19 Properties props = new Properties();
20 props.setProperty("readerDescription","TC file reader");
21 props.setProperty("readerId","tcNetworkReader");
22 swingAdapter.getCyServiceRegistrar().registerService(factory, InputStreamTaskFactory.class, props);
23
Compile [http://chianti.ucsd.edu/svn/core3/samples/trunk/sample27a|this sample code], and install the app in Cytoscape. When we try to import a network from a file (File-->Import-->Network-->File..), we will find file type '.tc' is listed as one of the file types supported by Cytoscape.
The source code of sample Apps can be find here.
How to build a plugin with dependency on 3rd party library?
1
2
Trouble shooting
If you get compile error,
Check the version number of parent POM, the latest is at Cytoscape repository
If this is a dependency problem, check the version number of depended bundle at Cytoscape repository
Recommendations
- If you’re a beginner you probably want to use the Simple app type. see example02a, example03a
- If you want to port as quickly as possible, again, go with the Simple app type.
- If you want to publish an API you must use the Bundle app type
- If you experience version conflicts or anticipate future version conflicts, again you must use the Bundle app type.
- If you are in doubt, you should probably use the Simple app type. You can always port it to the Bundle app type later should that become necessary. Both styles are supported and will be until (at least) version 4.0.
App Porting Hints
How to
- get current network --- see sample app 5
- get attributes --- see sample app 11
- add a menu item --- see sample app 3
Questions, suggestions
Please send e-mail to cytoscape help desk or discussion group