package uk.ac.rhul.cs.cl1.ui.cytoscape3;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import javax.swing.JOptionPane;
import org.cytoscape.application.CyApplicationManager;
import org.cytoscape.application.swing.AbstractCyAction;
import org.cytoscape.application.swing.CyNodeViewContextMenuFactory;
import org.cytoscape.application.swing.CySwingApplication;
import org.cytoscape.model.CyColumn;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNode;
import org.cytoscape.model.CyRow;
import org.cytoscape.model.CyTable;
import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.model.CyNetworkViewManager;
import org.cytoscape.work.swing.DialogTaskManager;
import uk.ac.rhul.cs.cl1.ClusterONE;
import uk.ac.rhul.cs.cl1.ClusterONEAlgorithmParameters;
import uk.ac.rhul.cs.cl1.CohesivenessFunction;
import uk.ac.rhul.cs.cl1.MutableNodeSet;
import uk.ac.rhul.cs.cl1.QualityFunction;
public class ClusterONECytoscapeApp {
/**
* The application activator.
*/
private CytoscapeAppActivator activator;
/**
* The Cytoscape desktop handle.
*/
private CySwingApplication app;
/**
* The control panel of the application.
*/
private ControlPanel controlPanel;
/**
* The "Grow cluster from selected nodes" action of the application.
*/
private GrowClusterAction growClusterAction;
/**
* Local cache for converted ClusterONE representations of Cytoscape networks
*/
private CyNetworkCache networkCache;
/**
* Variable storing the name of the resource path wthin the bundle.
*/
private String resourcePathName;
/**
* Manager for the visual styles used by the application.
*/
private VisualStyleManager visualStyleManager;
// --------------------------------------------------------------------
// Static
// --------------------------------------------------------------------
/**
* Attribute name used by ClusterONE to store status information for each node.
*
* A node can have one and only one of the following status values:
*
* <ul>
* <li>0 = the node is an outlier (it is not included in any cluster)</li>
* <li>1 = the node is included in only a single cluster</li>
* <li>2 = the node is an overlap (it is included in more than one cluster)</li>
* </ul>
*/
public static final String ATTRIBUTE_STATUS = "cl1.Status";
/**
* Attribute name used by ClusterONE to store affinities of vertices to a
* given cluster.
*/
public static final String ATTRIBUTE_AFFINITY = "cl1.Affinity";
/**
* The name of the menu in which the app lives.
*/
public static final String PREFERRED_MENU = "Apps." + ClusterONE.applicationName;
// --------------------------------------------------------------------
// Constructors
// --------------------------------------------------------------------
public ClusterONECytoscapeApp(CytoscapeAppActivator activator) {
this.activator = activator;
initialize();
}
private void initialize() {
// Get the application handle
this.app = activator.getService(CySwingApplication.class);
// Create a new network cache
networkCache = new CyNetworkCache(this);
// Create the visual style manager
visualStyleManager = new VisualStyleManager(this);
// Create the control panel
controlPanel = new ControlPanel(this);
// Create the global actions
growClusterAction = new GrowClusterAction(this);
// Add the actions of the plugin
app.addAction(new ShowControlPanelAction(controlPanel));
app.addAction(growClusterAction);
app.addAction(new AffinityColouringAction(this));
app.addAction(new HelpAction(this, "introduction"));
app.addAction(new AboutAction(this));
// Register the node-specific context menu
registerService(new NodeContextMenuFactory(this), CyNodeViewContextMenuFactory.class);
}
// --------------------------------------------------------------------
// Properties
// --------------------------------------------------------------------
/**
* Returns the control panel of the application.
*/
public ControlPanel getControlPanel() {
return controlPanel;
}
// --------------------------------------------------------------------
// Query methods
// --------------------------------------------------------------------
/**
* Returns the application manager from Cytoscape.
*/
public CyApplicationManager getApplicationManager() {
return activator.getService(CyApplicationManager.class);
}
/**
* Returns the currently selected network.
*/
public CyNetwork getCurrentNetwork() {
return getApplicationManager().getCurrentNetwork();
}
/**
* Returns the currently selected network view.
*/
public CyNetworkView getCurrentNetworkView() {
return getApplicationManager().getCurrentNetworkView();
}
/**
* Returns the CySwingApplication in which the ClusterONE plugin lives.
*/
public CySwingApplication getCySwingApplication() {
return app;
}
/**
* Returns the action that grows a cluster from the selected nodes of the current view.
*/
public AbstractCyAction getGrowClusterAction() {
return growClusterAction;
}
/**
* Returns the app-wide network view manager from Cytoscape.
*/
public CyNetworkViewManager getNetworkViewManager() {
return activator.getService(CyNetworkViewManager.class);
}
/**
* Returns URL of the resource with the given name from the plugin bundle.
*/
public URL getResource(String name) {
return activator.getResource(name);
}
/**
* Returns an input stream pointing to the resource with the given name from
* the plugin bundle.
*
* @throws IOException
*/
public InputStream getResourceAsStream(String name) throws IOException {
return activator.getResourceAsStream(name);
}
/**
* Returns the name of the path containing the resources of the bundle.
*/
public String getResourcePathName() {
if (resourcePathName == null) {
String packageName = this.getClass().getPackage().getName().replace('.', '/');
packageName = packageName.substring(0, packageName.lastIndexOf('/'));
packageName = packageName.substring(0, packageName.lastIndexOf('/'));
resourcePathName = packageName + "/resources";
}
return resourcePathName;
}
/**
* Returns the Cytoscape service with the given interface.
*/
public <S> S getService(Class<S> cls) {
return activator.getService(cls);
}
/**
* Returns the Cytoscape service with the given interface.
*/
public <S> S getService(Class<S> cls, String properties) {
return activator.getService(cls, properties);
}
/**
* Returns the visual style manager of the app.
*/
public VisualStyleManager getVisualStyleManager() {
return visualStyleManager;
}
// --------------------------------------------------------------------
// Manipulation methods
// --------------------------------------------------------------------
/**
* Converts a {@link CyNetwork} to a {@link Graph} using the {@link CyNetworkCache}
*
* @param network the network being converted
* @param weightAttr the attribute name used for the weights
* @return the converted graph or null if there was an error
*/
public Graph convertCyNetworkToGraph(CyNetwork network, String weightAttr) {
Graph graph = null;
try {
graph = networkCache.convertCyNetworkToGraph(network, weightAttr);
} catch (NonNumericAttributeException ex) {
showErrorMessage("Weight attribute values must be numeric.");
return null;
}
return graph;
}
/**
* Registers an object as a service in the Cytoscape Swing application.
*
* @param object the object to register
* @param cls the class of the object
* @param properties additional properties to use for registering
*/
public <S> void registerService(S object, Class<S> cls) {
activator.registerService(object, cls);
}
/**
* Unregisters an object as a service in the Cytoscape Swing application.
*
* @param object the object to register
* @param cls the class of the object
*/
public <S> void unregisterService(S object, Class<S> cls) {
activator.unregisterService(object, cls);
}
/**
* Runs ClusterONE with the given parameters on the given Cytoscape network
* asynchronously in a background thread.
*
* @param network the network view we are running the algorithm on
* @param parameters the algorithm parameters of ClusterONE
* @param weightAttr edge attribute holding edge weights
* @param listener the listener to notify when the results are ready
*/
public void runAlgorithm(CyNetwork network, ClusterONEAlgorithmParameters parameters,
String weightAttr, ClusterONECytoscapeTask.ResultListener listener) {
networkCache.invalidate(network);
if (network == null || network.getEdgeCount() == 0) {
showErrorMessage("The selected network contains no edges");
return;
}
ClusterONECytoscapeTaskFactory taskFactory = new ClusterONECytoscapeTaskFactory(this);
taskFactory.setParameters(parameters);
taskFactory.setWeightAttr(weightAttr);
taskFactory.setResultListener(listener);
DialogTaskManager taskManager = activator.getService(DialogTaskManager.class);
taskManager.execute(taskFactory.createTaskIterator(network));
}
/**
* Runs ClusterONE with the given parameters on the given Cytoscape network view
* asynchronously in a background thread.
*
* @param network the network view we are running the algorithm on
* @param parameters the algorithm parameters of ClusterONE
* @param weightAttr edge attribute holding edge weights
* @param listener the listener to notify when the results are ready
*/
public void runAlgorithm(CyNetworkView networkView, ClusterONEAlgorithmParameters parameters,
String weightAttr, ClusterONECytoscapeTask.ResultListener listener) {
CyNetwork network = networkView.getModel();
networkCache.invalidate(network);
if (network == null || network.getEdgeCount() == 0) {
showErrorMessage("The selected network contains no edges");
return;
}
ClusterONECytoscapeTaskFactory taskFactory = new ClusterONECytoscapeTaskFactory(this);
taskFactory.setParameters(parameters);
taskFactory.setWeightAttr(weightAttr);
taskFactory.setResultListener(listener);
DialogTaskManager taskManager = activator.getService(DialogTaskManager.class);
taskManager.execute(taskFactory.createTaskIterator(networkView));
}
/**
* Sets the ClusterONE specific node affinity attributes on a CyNetwork that
* will be used by VizMapper later.
*
* @param network the Cytoscape network to manipulate
* @param graph the ClusterONE graph representation of that network
* @param nodes the list of the selected node indices
*/
public void setAffinityAttributes(CyNetwork network, Graph graph, List<Integer> nodes) {
CyTable nodeTable = network.getDefaultNodeTable();
CyColumn affinityColumn = nodeTable.getColumn(ClusterONECytoscapeApp.ATTRIBUTE_AFFINITY);
if (affinityColumn != null && affinityColumn.getType() != Double.class) {
int response = JOptionPane.showConfirmDialog(app.getJFrame(),
"A node attribute named "+ATTRIBUTE_STATUS+" already exists and "+
"it is not a string attribute.\nDo you want to remove the existing "+
"attribute and re-register it as a string attribute?",
"Attribute type mismatch",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if (response == JOptionPane.NO_OPTION)
return;
nodeTable.deleteColumn(ClusterONECytoscapeApp.ATTRIBUTE_AFFINITY);
affinityColumn = null;
}
if (affinityColumn == null) {
nodeTable.createColumn(ClusterONECytoscapeApp.ATTRIBUTE_AFFINITY, Double.class, false, 0.0);
}
int i = 0;
MutableNodeSet nodeSet = new MutableNodeSet(graph, nodes);
QualityFunction func = new CohesivenessFunction(); // TODO: fix it, it should not be hardwired
double currentQuality = func.calculate(nodeSet);
double affinity;
for (CyNode node: graph.getNodeMapping()) {
if (nodeSet.contains(i))
/* multiplying by -1 here: we want internal nodes to have a positive
* affinity if they "should" belong to the cluster
*/
affinity = - (func.getRemovalAffinity(nodeSet, i) - currentQuality);
else
affinity = func.getAdditionAffinity(nodeSet, i) - currentQuality;
if (Double.isNaN(affinity))
affinity = 0.0;
CyRow row = network.getRow(node);
if (row != null) {
row.set(ClusterONECytoscapeApp.ATTRIBUTE_AFFINITY, affinity);
}
i++;
}
}
/**
* Shows a message dialog box that informs the user about a possible bug in ClusterONE.
*
* @param message the message to be shown
*/
public void showBugMessage(String message) {
StringBuilder sb = new StringBuilder(message);
sb.append("\n\n");
sb.append("This is possibly a bug in ");
sb.append(ClusterONE.applicationName);
sb.append(".\nPlease inform the developers about what you were doing and\n");
sb.append("what the expected result would have been.");
JOptionPane.showMessageDialog(app.getJFrame(),
sb.toString(), "Possible bug in "+ClusterONE.applicationName,
JOptionPane.ERROR_MESSAGE);
}
/**
* Shows an error message in a dialog box
*
* @param message the error message to be shown
*/
public void showErrorMessage(String message) {
JOptionPane.showMessageDialog(app.getJFrame(), message,
ClusterONE.applicationName, JOptionPane.ERROR_MESSAGE);
}
/**
* Shows a message dialog box that informs the user about something
*
* @param message the message to be shown
*/
public void showInformationMessage(String message) {
JOptionPane.showMessageDialog(app.getJFrame(), message,
ClusterONE.applicationName, JOptionPane.INFORMATION_MESSAGE);
}
// --------------------------------------------------------------------
// Private methods
// --------------------------------------------------------------------
}