package uk.ac.rhul.cs.cl1.ui;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.table.AbstractTableModel;
import uk.ac.rhul.cs.cl1.ClusterONE;
import uk.ac.rhul.cs.cl1.quality.CohesivenessFunction;
import uk.ac.rhul.cs.cl1.NodeSet;
import uk.ac.rhul.cs.cl1.quality.QualityFunction;
import uk.ac.rhul.cs.cl1.ValuedNodeSet;
import uk.ac.rhul.cs.graph.FruchtermanReingoldLayoutAlgorithm;
import uk.ac.rhul.cs.graph.Graph;
import uk.ac.rhul.cs.graph.GraphLayoutAlgorithm;
import com.sosnoski.util.hashmap.ObjectIntHashMap;
/**
* Table model that can be used to show a list of {@link NodeSet} objects
* in a JTable.
*
* @author tamas
*/
public class NodeSetTableModel extends AbstractTableModel {
private static final Color DARK_RED = new Color(128, 0, 0);
/**
* The quality function we are working with
*
* @todo Fix it, it should not be hardwired here
*/
protected final QualityFunction qualityFunc = new CohesivenessFunction();
/** Column headers for the simple mode */
String[] simpleHeaders = { "Cluster", "Details" };
/** Column classes for the simple mode */
Class<?>[] simpleClasses = { ImageIcon.class, NodeSetDetails.class };
/** Column headers for the detailed mode */
String[] detailedHeaders = { "Cluster", "Nodes", "Density",
"In-weight", "Out-weight", "Quality", "P-value" };
/** Column classes for the detailed mode */
Class<?>[] detailedClasses = {
ImageIcon.class, Integer.class, Double.class, Double.class, Double.class, Double.class,
Double.class
};
/** Column headers for the current mode */
String[] currentHeaders = null;
/** Column classes for the current mode */
Class<?>[] currentClasses = null;
/**
* The list of {@link NodeSet} objects shown in this model
*/
protected List<ValuedNodeSet> nodeSets = null;
/**
* The list of rendered cluster graphs for all the {@link NodeSet} objects shown in this model
*/
protected List<Future<Icon>> nodeSetIcons = new ArrayList<Future<Icon>>();
/**
* The list of {@link NodeSetDetails} objects to avoid having to calculate
* the Details column all the time
*/
protected List<NodeSetDetails> nodeSetDetails = new ArrayList<NodeSetDetails>();
/**
* Whether the model is in detailed mode or simple mode
*
* In the simple mode, only two columns are shown: the cluster members
* and some basic properties (in a single column). In the detailed mode,
* each property has its own column
*/
boolean detailedMode = true;
/**
* Icon showing a circular progress indicator. Loaded on demand from resources.
*/
private Icon progressIcon = null;
/**
* Internal class that represents the task that renders the cluster in the result table
*/
private class RendererTask extends FutureTask<Icon> {
int rowIndex;
public RendererTask(int rowIndex, Graph subgraph, GraphLayoutAlgorithm algorithm,
HashMap<Integer, Color> colorMapping) {
super(new GraphRenderer(subgraph, algorithm, colorMapping));
this.rowIndex = rowIndex;
}
protected void done() {
fireTableCellUpdated(rowIndex, 0);
}
}
/**
* Constructs a new table model backed by the given list of nodesets
*/
public NodeSetTableModel(List<ValuedNodeSet> nodeSets) {
this.nodeSets = new ArrayList<ValuedNodeSet>(nodeSets);
updateNodeSetDetails();
this.setDetailedMode(false);
}
public int getColumnCount() {
return currentHeaders.length;
}
public int getRowCount() {
return nodeSets.size();
}
@Override
public Class<?> getColumnClass(int col) {
return currentClasses[col];
}
@Override
public String getColumnName(int col) {
return currentHeaders[col];
}
/**
* Returns the names of members in a given row.
*
* @param row the index of the row for which we need the list
* @return an array containing the names of the members
*/
public String[] getMemberNames(int row) {
NodeSet nodeSet = this.nodeSets.get(row);
if (nodeSet == null)
return new String[0];
return nodeSet.getMemberNames();
}
public Object getValueAt(int row, int col) {
NodeSet nodeSet = this.nodeSets.get(row);
if (nodeSet == null)
return null;
if (col == 0) {
/* Check whether we have a rendered image or not */
try {
Future<Icon> icon = nodeSetIcons.get(row);
if (icon != null && icon.isDone())
return icon.get();
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
return this.getProgressIcon();
}
if (!detailedMode) {
/* Simple mode, column 1 */
return this.nodeSetDetails.get(row);
}
/* Detailed mode */
if (col == 1)
return nodeSet.size();
if (col == 2)
return nodeSet.getDensity();
if (col == 3)
return nodeSet.getTotalInternalEdgeWeight();
if (col == 4)
return nodeSet.getTotalBoundaryEdgeWeight();
if (col == 5)
return qualityFunc.calculate(nodeSet);
if (col == 6)
return nodeSet.getSignificance();
return "TODO";
}
/**
* Returns an icon showing a progress indicator
*/
private Icon getProgressIcon() {
return this.progressIcon;
}
/**
* Returns the {@link NodeSet} shown in the given row.
*
* @param row the row index
* @return the corresponding {@link NodeSet}
*/
public NodeSet getNodeSetByIndex(int row) {
return nodeSets.get(row);
}
/**
* Returns whether the table model is in detailed mode
*/
public boolean isInDetailedMode() {
return detailedMode;
}
/**
* Removes the given nodeset from the table model
*/
public void remove(NodeSet nodeSet) {
int index = nodeSets.indexOf(nodeSet);
if (index < 0)
return;
nodeSets.remove(index);
nodeSetIcons.remove(index);
nodeSetDetails.remove(index);
fireTableRowsDeleted(index, index);
}
/**
* Returns whether the table model is in detailed mode
*/
public void setDetailedMode(boolean mode) {
if (mode == detailedMode)
return;
detailedMode = mode;
currentHeaders = detailedMode ? detailedHeaders : simpleHeaders;
currentClasses = detailedMode ? detailedClasses : simpleClasses;
this.fireTableStructureChanged();
}
/**
* Sets the icon that shows a progress indicator.
*/
public void setProgressIcon(Icon value) {
this.progressIcon = value;
}
private void updateNodeSetDetails() {
Executor threadPool = ClusterONE.getThreadPool();
int i = 0;
nodeSetDetails.clear();
nodeSetIcons.clear();
for (ValuedNodeSet nodeSet: nodeSets) {
HashMap<Integer, Color> subgraphColorMap = new HashMap<Integer, Color>();
Graph subgraph = nodeSet.getSubgraph();
ObjectIntHashMap nodeNameIndex = subgraph.getNodeNameHashMap();
boolean onlyCores = true;
for (int nodeIndex: nodeSet)
if (nodeSet.getValue(nodeIndex) > 1) {
onlyCores = false;
break;
}
if (!onlyCores) {
for (int nodeIndex: nodeSet) {
if (nodeSet.getValue(nodeIndex) == 1) {
int id = nodeNameIndex.get(Integer.toString(nodeIndex));
if (id != ObjectIntHashMap.DEFAULT_NOT_FOUND)
subgraphColorMap.put(id, DARK_RED);
}
}
}
RendererTask rendererTask = new RendererTask(i, subgraph,
new FruchtermanReingoldLayoutAlgorithm(), subgraphColorMap);
threadPool.execute(rendererTask);
nodeSetIcons.add(rendererTask);
nodeSetDetails.add(new NodeSetDetails(nodeSet));
i++;
}
}
}