Print

Print


Author: [log in to unmask]
Date: Thu Mar  5 13:46:25 2015
New Revision: 2273

Log:
Added monitoring components for the trigger diagnostics to their new home package.

Added:
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTriggerTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTwoColumnTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ClusterTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ComponentUtils.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/DiagnosticUpdatable.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/EfficiencyTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/PairTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/SinglesTablePanel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TableTextModel.java
    java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TriggerDiagnosticGUIDriver.java

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,249 @@
+package org.hps.monitoring.trigger;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+
+import org.hps.analysis.trigger.util.ComponentUtils;
+
+/**
+ * Class <code>AbstractTablePanel</code> displays two <code>JTable</code>
+ * objects side-by-side with headers above them. The left table displays
+ * statistical data for recent events processed with trigger diagnostics
+ * while the right table displays the same, but over the course of the
+ * entire run.<br/>
+ * <br/>
+ * This implements the interface <code>DiagnosticUpdatable</code>.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see JPanel
+ * @see DiagnosticUpdatable
+ */
+public abstract class AbstractTablePanel extends JPanel implements DiagnosticUpdatable {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	public static final int ORIENTATION_HORIZONTAL = 0;
+	public static final int ORIENTATION_VERTICAL   = 1;
+	
+	// Components.
+	private JLabel localHeader;
+	private JLabel globalHeader;
+	protected final JTable localTable;
+	protected final JTable globalTable;
+	
+	// Component parameters.
+	private boolean horizontal = true;
+	private Dimension userPrefSize = null;
+	private Dimension defaultPrefSize = new Dimension(0, 0);
+	
+	/**
+	 * Instantiates an <code>AbstractTablePanel</code>.
+	 * @param args Arguments to be usd when generating the panel tables.
+	 */
+	public AbstractTablePanel(Object... args) {
+		// Initialize the tables.
+		JTable[] tables = initializeTables(args);
+		localTable = tables[0];
+		globalTable = tables[1];
+		add(globalTable);
+		add(localTable);
+		
+		// Set the panels to their null starting values.
+		updatePanel(null);
+		
+		// Define the panel layout.
+		setLayout(null);
+		
+		// Create header labels for the tables.
+		localHeader = new JLabel("Local Statistics");
+		localHeader.setHorizontalAlignment(JLabel.CENTER);
+		add(localHeader);
+		
+		globalHeader = new JLabel("Run Statistics");
+		globalHeader.setHorizontalAlignment(JLabel.CENTER);
+		add(globalHeader);
+		
+		// Track when the component changes size and reposition the
+		// components accordingly.
+		addComponentListener(new ComponentAdapter() {
+			@Override
+			public void componentResized(ComponentEvent e) { positionComponents(); }
+		});
+		
+		// Define the component preferred size.
+		defaultPrefSize.width = localTable.getPreferredSize().width +
+				ComponentUtils.hinternal + globalTable.getPreferredSize().width;
+		defaultPrefSize.height = localTable.getPreferredSize().height +
+				ComponentUtils.vinternal + globalTable.getPreferredSize().height;
+	}
+	
+	@Override
+	public Dimension getPreferredSize() {
+		// If there is a user-specified preferred size, return that.
+		if(userPrefSize == null) { return defaultPrefSize; }
+		
+		// Otherwise, return the default calculated preferred size.
+		else { return userPrefSize; }
+	}
+	
+	@Override
+	public void setBackground(Color bg) {
+		// Set the base component background.
+		super.setBackground(bg);
+		
+		// If the components have been initialized, pass the background
+		// color change to them as appropriate. Note that the tables
+		// will always retain the same background color.
+		if(localTable != null) {
+			// Set the header backgrounds.
+			localHeader.setBackground(bg);
+			globalHeader.setBackground(bg);
+		}
+	}
+	
+	@Override
+	public void setFont(Font font) {
+		// Set the base component font.
+		super.setFont(font);
+		
+		// If the components have been initialized, pass the font change
+		// to them as appropriate.
+		if(localTable != null) {
+			// Set the table fonts.
+			localTable.setFont(font);
+			globalTable.setFont(font);
+			
+			// Set the header fonts.
+			Font headerFont = font.deriveFont(Font.BOLD, (float) Math.ceil(font.getSize2D() * 1.3));
+			localHeader.setFont(headerFont);
+			globalHeader.setFont(headerFont);
+		}
+	}
+	
+	@Override
+	public void setForeground(Color fg) {
+		// Set the base component foreground.
+		super.setForeground(fg);
+		
+		// If the components have been initialized, pass the foreground
+		// color change to them as appropriate. Note that the tables
+		// will always retain the same foreground color.
+		if(localTable != null) {
+			// Set the header foregrounds.
+			localHeader.setForeground(fg);
+			globalHeader.setForeground(fg);
+		}
+	}
+	
+	/**
+	 * Sets the orientation of components on the panel.
+	 * @param orientation - The orientation identifier. Identifiers can
+	 * be obtained as static variables from the within the root object
+	 * <code>AbstractTable</code>.
+	 */
+	public void setOrientation(int orientation) {
+		if(orientation == ORIENTATION_HORIZONTAL) {
+			if(!horizontal) {
+				horizontal = true;
+				positionComponents();
+			}
+		} else if(orientation == ORIENTATION_VERTICAL) {
+			if(horizontal) {
+				horizontal = false;
+				positionComponents();
+			}
+		} else {
+			throw new IllegalArgumentException("Invalid orienation identifier.");
+		}
+	}
+	
+	@Override
+	public void setPreferredSize(Dimension preferredSize) {
+		userPrefSize = preferredSize;
+	}
+	
+	/**
+	 * Generates the two tables that are used by the component. This
+	 * must return an array of size two.
+	 * @param args - Any arguments that should be passed to the method
+	 * for generating tables.
+	 * @return Returns an array of size two, where the first index must
+	 * contain the local table and the second index the global table.
+	 */
+	protected abstract JTable[] initializeTables(Object... args);
+	
+	/**
+	 * Repositions the components to the correct places on the parent
+	 * <code>JPanel</code>. This should be run whenever the panel
+	 * changes size.
+	 */
+	private void positionComponents() {
+		// Do not update if the components have not been initialized.
+		if(localHeader == null) { return; }
+		
+		// If the components should be position horizontally...
+		if(horizontal) {
+			// The local components get the left half of the panel and the
+			// global components the right. Find half of the panel width,
+			// accounting for the internal spacing. This is an internal
+			// component, so it does not employ additional spacing between
+			// itself and the parent component's edges.
+			int compWidth = (getWidth() - 10) / 2;
+			
+			// If there is any width remaining, it goes to the spacing.
+			int horizontal = ComponentUtils.hinternal + (getWidth() - 10) % 2;
+			
+			// Place the header labels. These are given their preferred
+			// height. Note that this means a very small panel may cut off
+			// some of the components. First, get the preferred height of
+			// the label with the larger preferred height. These should be
+			// the same thing, but just in case...
+			int labelHeight = localHeader.getPreferredSize().height;
+			if(labelHeight < globalHeader.getPreferredSize().height) {
+				labelHeight = globalHeader.getPreferredSize().height;
+			}
+			
+			// Set the label sizes and positions.
+			localHeader.setBounds(0, 0, compWidth, labelHeight);
+			globalHeader.setLocation(ComponentUtils.getNextX(localHeader, horizontal), 0);
+			globalHeader.setSize(compWidth, labelHeight);
+			
+			// The tables go under their respective labels and should fill
+			// the remainder of the label height.
+			int tableY = ComponentUtils.getNextY(localHeader, ComponentUtils.vinternal);
+			localTable.setBounds(0, tableY, compWidth, localTable.getPreferredSize().height);
+			globalTable.setBounds(globalHeader.getX(), tableY, compWidth, globalTable.getPreferredSize().height);
+		}
+		
+		// Otherwise, position them vertically.
+		else {
+			// Place the header labels. These are given their preferred
+			// height. Note that this means a very small panel may cut off
+			// some of the components. First, get the preferred height of
+			// the label with the larger preferred height. These should be
+			// the same thing, but just in case...
+			int labelHeight = localHeader.getPreferredSize().height;
+			if(labelHeight < globalHeader.getPreferredSize().height) {
+				labelHeight = globalHeader.getPreferredSize().height;
+			}
+			
+			// The local components go first, taking up the entire upper
+			// width of the panel.
+			localHeader.setBounds(0, 0, getWidth(), labelHeight);
+			localTable.setBounds(0, ComponentUtils.getNextY(localHeader, ComponentUtils.vinternal),
+					getWidth(), localTable.getPreferredSize().height);
+			
+			// The global components go immediately below.
+			globalHeader.setBounds(0, ComponentUtils.getNextY(localTable, ComponentUtils.vinternal),
+					getWidth(), labelHeight);
+			globalTable.setBounds(0, ComponentUtils.getNextY(globalHeader, ComponentUtils.vinternal),
+					getWidth(), globalTable.getPreferredSize().height);
+		}
+	}
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTriggerTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTriggerTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTriggerTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,176 @@
+package org.hps.monitoring.trigger;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+import org.hps.analysis.trigger.event.TriggerStatModule;
+import org.hps.analysis.trigger.util.ComponentUtils;
+
+/**
+ * Abstract class <code>AbstractTriggerTablePanel</code> creates the
+ * basic framework to display trigger statistics. It is also able to
+ * update itself from the diagnostic snapshot given certain information,
+ * which is obtained through the implementation of its abstract methods
+ * by subclasses.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public abstract class AbstractTriggerTablePanel extends AbstractTwoColumnTablePanel {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	
+	// Internal variables.
+	private final int numCuts;
+	
+	// Reference variables to the default table rows.
+	protected static final int ROW_RECON_COUNT        = 0;
+	protected static final int ROW_SSP_SIM_COUNT      = 1;
+	protected static final int ROW_SSP_BANK_COUNT     = 2;
+	protected static final int ROW_SSP_EFFICIENCY     = 3;
+	protected static final int ROW_TRIGGER_EFFICIENCY = 4;
+	protected static final int ROW_EMPTY_SPACE        = 5;
+	protected static final int ROW_CUT_FAILS_TITLE    = 6;
+	protected static final int ROW_FIRST_TRIGGER_CUT  = 7;
+	
+	/**
+	 * Instantiates an <code>AbstractTriggerTablePanel</code> with the
+	 * indicated cut names.
+	 * @param cutNames
+	 */
+	public AbstractTriggerTablePanel(String[] cutNames) {
+		// Instantiate the superclass.
+		super(makeTitle(cutNames));
+		
+		// Store the number of cuts.
+		numCuts = cutNames.length;
+		updatePanel(null);
+	}
+	
+	@Override
+	public void updatePanel(DiagSnapshot snapshot) {
+		// If the snapshot is null, all values should be "N/A."
+		if(snapshot == null) {
+			// Output cluster count data.
+			String scalerNullValue = "---";
+			setLocalRowValue(ROW_RECON_COUNT,     scalerNullValue);
+			setLocalRowValue(ROW_SSP_SIM_COUNT,   scalerNullValue);
+			setLocalRowValue(ROW_SSP_BANK_COUNT,  scalerNullValue);
+			setGlobalRowValue(ROW_RECON_COUNT,    scalerNullValue);
+			setGlobalRowValue(ROW_SSP_SIM_COUNT,  scalerNullValue);
+			setGlobalRowValue(ROW_SSP_BANK_COUNT, scalerNullValue);
+			
+			// Output the tracked statistical data.
+			String percentNullValue = "--- / --- (---%)";
+			setLocalRowValue(ROW_SSP_EFFICIENCY,      percentNullValue);
+			setLocalRowValue(ROW_TRIGGER_EFFICIENCY,  percentNullValue);
+			setGlobalRowValue(ROW_SSP_EFFICIENCY,     percentNullValue);
+			setGlobalRowValue(ROW_TRIGGER_EFFICIENCY, percentNullValue);
+			
+			int ROW_SECOND_TRIGGER_CUT = ROW_FIRST_TRIGGER_CUT + numCuts + 2;
+			for(int cutRow = 0; cutRow < numCuts; cutRow++) {
+				setLocalRowValue(cutRow + ROW_FIRST_TRIGGER_CUT,   percentNullValue);
+				setLocalRowValue(cutRow + ROW_SECOND_TRIGGER_CUT,  percentNullValue);
+				setGlobalRowValue(cutRow + ROW_FIRST_TRIGGER_CUT,  percentNullValue);
+				setGlobalRowValue(cutRow + ROW_SECOND_TRIGGER_CUT, percentNullValue);
+			}
+		} else {
+			// Get the local and run trigger statistics from the snapshot.
+			TriggerStatModule lstat = getLocalModule(snapshot);
+			TriggerStatModule rstat = getRunModule(snapshot);
+			
+			// Determine the most spaces needed to display the values.
+			// Get the largest number of digits in any of the values.
+			int mostDigits = ComponentUtils.max(lstat.getReconTriggerCount(), lstat.getSSPBankTriggerCount(),
+					lstat.getSSPSimTriggerCount(), rstat.getReconTriggerCount(), rstat.getSSPBankTriggerCount(),
+					rstat.getSSPSimTriggerCount());
+			
+			// Update the single-value counters.
+			String countFormat = "%" + mostDigits + "d";
+			setLocalRowValue(ROW_RECON_COUNT,     String.format(countFormat, lstat.getReconTriggerCount()));
+			setLocalRowValue(ROW_SSP_SIM_COUNT,   String.format(countFormat, lstat.getSSPSimTriggerCount()));
+			setLocalRowValue(ROW_SSP_BANK_COUNT,  String.format(countFormat, lstat.getSSPBankTriggerCount()));
+			setGlobalRowValue(ROW_RECON_COUNT,    String.format(countFormat, rstat.getReconTriggerCount()));
+			setGlobalRowValue(ROW_SSP_SIM_COUNT,  String.format(countFormat, rstat.getSSPSimTriggerCount()));
+			setGlobalRowValue(ROW_SSP_BANK_COUNT, String.format(countFormat, rstat.getSSPBankTriggerCount()));
+			
+			// Update the percentage counters.
+			String percentFormat = "%" + mostDigits + "d / %" + mostDigits + "d (%7.3f)";
+			
+			setLocalRowValue(ROW_SSP_EFFICIENCY, String.format(percentFormat, lstat.getMatchedSSPTriggers(),
+					lstat.getSSPSimTriggerCount(), (100.0 * lstat.getMatchedSSPTriggers() / lstat.getSSPSimTriggerCount())));
+			setLocalRowValue(ROW_TRIGGER_EFFICIENCY, String.format(percentFormat, lstat.getMatchedReconTriggers(),
+					lstat.getReconTriggerCount(), (100.0 * lstat.getMatchedReconTriggers() / lstat.getReconTriggerCount())));
+			setGlobalRowValue(ROW_SSP_EFFICIENCY, String.format(percentFormat, rstat.getMatchedSSPTriggers(),
+					rstat.getSSPSimTriggerCount(), (100.0 * rstat.getMatchedSSPTriggers() / rstat.getSSPSimTriggerCount())));
+			setGlobalRowValue(ROW_TRIGGER_EFFICIENCY, String.format(percentFormat, lstat.getMatchedReconTriggers(),
+					rstat.getReconTriggerCount(), (100.0 * rstat.getMatchedReconTriggers() / rstat.getReconTriggerCount())));
+			
+			int ROW_SECOND_TRIGGER_CUT = ROW_FIRST_TRIGGER_CUT + numCuts + 2;
+			int[] total = { lstat.getSSPSimTriggerCount() / 2, rstat.getSSPSimTriggerCount() / 2 };
+			for(int cutRow = 0; cutRow < numCuts; cutRow++) {
+				setLocalRowValue(cutRow + ROW_FIRST_TRIGGER_CUT, String.format(percentFormat, lstat.getCutFailures(0, cutRow),
+						total[0], (100.0 * lstat.getCutFailures(0, cutRow) / total[0])));
+				setLocalRowValue(cutRow + ROW_SECOND_TRIGGER_CUT, String.format(percentFormat, lstat.getCutFailures(1, cutRow),
+						total[0], (100.0 * lstat.getCutFailures(1, cutRow) / total[0])));
+				setGlobalRowValue(cutRow + ROW_FIRST_TRIGGER_CUT, String.format(percentFormat, lstat.getCutFailures(0, cutRow),
+						total[1], (100.0 * lstat.getCutFailures(0, cutRow) / total[1])));
+				setGlobalRowValue(cutRow + ROW_SECOND_TRIGGER_CUT, String.format(percentFormat, lstat.getCutFailures(1, cutRow),
+						total[1], (100.0 * lstat.getCutFailures(1, cutRow) / total[1])));
+			}
+		}
+	}
+	
+	/**
+	 * Gets the statistical module from which local statistics should
+	 * be drawn.
+	 * @param snapshot - The snapshot containing the modules.
+	 * @return Returns the module containing local statistical data.
+	 */
+	protected abstract TriggerStatModule getLocalModule(DiagSnapshot snapshot);
+	
+	/**
+	 * Gets the statistical module from which run statistics should
+	 * be drawn.
+	 * @param snapshot - The snapshot containing the modules.
+	 * @return Returns the module containing run statistical data.
+	 */
+	protected abstract TriggerStatModule getRunModule(DiagSnapshot snapshot);
+	
+	/**
+	 * Creates the table appropriate table rows from the argument cut
+	 * names.
+	 * @param cutNames - An array containing the names of the cuts to
+	 * display.
+	 * @return Returns an array with the default table rows merged in
+	 * with the provided cut names.
+	 */
+	private static final String[] makeTitle(String[] cutNames) {
+		// Make a new array to hold all the text.
+		String[] mergedArray = new String[cutNames.length + cutNames.length + 9];
+		
+		// Define the default trigger headers.
+		mergedArray[0] = "Recon Triggers:";
+		mergedArray[1] = "SSP Sim Triggers:";
+		mergedArray[2] = "SSP Bank Triggers:";
+		mergedArray[3] = "SSP Efficiency:";
+		mergedArray[4] = "Trigger Efficiency:";
+		mergedArray[5] = "";
+		mergedArray[6] = "First Trigger Cut Failures";
+		
+		// Insert the cut names for the first trigger.
+		for(int cutIndex = 0; cutIndex < cutNames.length; cutIndex++) {
+			mergedArray[7 + cutIndex] = cutNames[cutIndex];
+		}
+		
+		// Insert the header for the second trigger cut names.
+		int startIndex = 7 + cutNames.length;
+		mergedArray[startIndex]     = "";
+		mergedArray[startIndex + 1] = "Second Trigger Cut Failures";
+		
+		// Insert the next set of cut names.
+		for(int cutIndex = 0; cutIndex < cutNames.length; cutIndex++) {
+			mergedArray[startIndex + 2 + cutIndex] = cutNames[cutIndex];
+		}
+		
+		// Return the resultant array.
+		return mergedArray;
+	}
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTwoColumnTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTwoColumnTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/AbstractTwoColumnTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,93 @@
+package org.hps.monitoring.trigger;
+
+import javax.swing.JTable;
+
+/**
+ * Class <code>AbstractTwoColumnTablePanel</code> is an implementation
+ * of <code>AbstractTablePanel</code> that specifically handles tables
+ * with two columns where the first column's cells are row headers and
+ * the second column contains values.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see AbstractTablePanel
+ */
+public abstract class AbstractTwoColumnTablePanel extends AbstractTablePanel {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	
+	// Table models.
+	private TableTextModel localModel;
+	private TableTextModel globalModel;
+	
+	// Table model mappings.
+	private static final int COL_TITLE = 0;
+	private static final int COL_VALUE = 1;
+	
+	/**
+	 * Instantiates an <code>AbstractTwoColumnTablePanel</code> object
+	 * with the indicated row names.
+	 * @param rowNames - The names of the rows.
+	 */
+	public AbstractTwoColumnTablePanel(String[] rowNames) {
+		super((Object[]) rowNames);
+	}
+	
+	@Override
+	protected JTable[] initializeTables(Object... args) {
+		// The arguments should be a string array.
+		if(!(args instanceof String[])) {
+			throw new IllegalArgumentException("Row names must be strings!");
+		}
+		String[] rowNames = (String[]) args;
+		
+		// Initialize the table models. They should have two columns
+		// (one for values and one for headers) and a number of rows
+		// equal to the number of row names.
+		localModel = new TableTextModel(rowNames.length, 2);
+		globalModel = new TableTextModel(rowNames.length, 2);
+		
+		// Initialize the titles.
+		for(int i = 0; i < rowNames.length; i++) {
+			localModel.setValueAt(rowNames[i], i, COL_TITLE);
+			globalModel.setValueAt(rowNames[i], i, COL_TITLE);
+		}
+		updatePanel(null);
+		
+		// Create JTable objects to display the data.
+		JTable localTable = new JTable(localModel);
+		localTable.setRowSelectionAllowed(false);
+		localTable.setColumnSelectionAllowed(false);
+		localTable.setCellSelectionEnabled(false);
+		localTable.setShowVerticalLines(false);
+		
+		JTable globalTable = new JTable(globalModel);
+		globalTable.setRowSelectionAllowed(false);
+		globalTable.setColumnSelectionAllowed(false);
+		globalTable.setCellSelectionEnabled(false);
+		globalTable.setShowVerticalLines(false);
+		
+		// Return the two tables.
+		return new JTable[] { localTable, globalTable };
+	}
+	
+	/**
+	 * Sets the value of the indicated row for the global statistical
+	 * table.
+	 * @param rowIndex - The row.
+	 * @param value - The new value.
+	 */
+	protected void setGlobalRowValue(int rowIndex, String value) {
+		globalModel.setValueAt(value, rowIndex, COL_VALUE);
+	}
+	
+	/**
+	 * Sets the value of the indicated row for the local statistical
+	 * table.
+	 * @param rowIndex - The row.
+	 * @param value - The new value.
+	 */
+	protected void setLocalRowValue(int rowIndex, String value) {
+		localModel.setValueAt(value, rowIndex, COL_VALUE);
+	}
+
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ClusterTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ClusterTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ClusterTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,111 @@
+package org.hps.monitoring.trigger;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+import org.hps.analysis.trigger.event.ClusterStatModule;
+import org.hps.analysis.trigger.util.ComponentUtils;
+
+/**
+ * Class <code>ClusterTablePanel</code> is an implementation of class
+ * <code>AbstractTablePanel</code> for cluster statistical data.<br/>
+ * <br/>
+ * This implements the interface <code>DiagnosticUpdatable</code>.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see AbstractTablePanel
+ */
+public class ClusterTablePanel extends AbstractTwoColumnTablePanel {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	private static final String[] TABLE_TITLES = { "Recon Clusters", "SSP Clusters", "Matched Clusters",
+			"Failed (Position)", "Failed (Energy)", "Failed (Hit Count)" };
+	
+	// Table model mappings.
+	private static final int ROW_RECON_COUNT      = 0;
+	private static final int ROW_SSP_COUNT        = 1;
+	private static final int ROW_MATCHED          = 2;
+	private static final int ROW_FAILED_POSITION  = 3;
+	private static final int ROW_FAILED_ENERGY    = 4;
+	private static final int ROW_FAILED_HIT_COUNT = 5;
+	
+	/**
+	 * Instantiate a new <code>ClusterTablePanel</code>.
+	 */
+	public ClusterTablePanel() { super(TABLE_TITLES); }
+	
+	@Override
+	public void updatePanel(DiagSnapshot snapshot) {
+		// If the snapshot is null, all values should be "N/A."
+		if(snapshot == null) {
+			// Output cluster count data.
+			String scalerNullValue = "---";
+			setLocalRowValue(ROW_RECON_COUNT,  scalerNullValue);
+			setLocalRowValue(ROW_SSP_COUNT,    scalerNullValue);
+			setGlobalRowValue(ROW_RECON_COUNT, scalerNullValue);
+			setGlobalRowValue(ROW_SSP_COUNT,   scalerNullValue);
+			
+			// Output the tracked statistical data.
+			String percentNullValue = "--- / --- (---%)";
+			setLocalRowValue(ROW_MATCHED,           percentNullValue);
+			setLocalRowValue(ROW_FAILED_POSITION,   percentNullValue);
+			setLocalRowValue(ROW_FAILED_ENERGY,     percentNullValue);
+			setLocalRowValue(ROW_FAILED_HIT_COUNT,  percentNullValue);
+			setGlobalRowValue(ROW_MATCHED,          percentNullValue);
+			setGlobalRowValue(ROW_FAILED_POSITION,  percentNullValue);
+			setGlobalRowValue(ROW_FAILED_ENERGY,    percentNullValue);
+			setGlobalRowValue(ROW_FAILED_HIT_COUNT, percentNullValue);
+		}
+		
+		// Otherwise, populate the table with the diagnostic data.
+		else {
+			// Get the cluster statistical banks.
+			ClusterStatModule lstat = snapshot.clusterLocalStatistics;
+			ClusterStatModule rstat = snapshot.clusterRunStatistics;
+			
+			// Get the largest number of digits in any of the values.
+			int mostDigits = ComponentUtils.max(lstat.getReconClusterCount(), lstat.getSSPClusterCount(), lstat.getMatches(),
+					lstat.getPositionFailures(), lstat.getEnergyFailures(), lstat.getHitCountFailures(),
+					rstat.getReconClusterCount(), rstat.getSSPClusterCount(), rstat.getMatches(),
+					rstat.getPositionFailures(), rstat.getEnergyFailures(), rstat.getHitCountFailures());
+			
+			// Put the number of reconstructed and SSP clusters into
+			// the tables.
+			int[] clusterValue = {
+					lstat.getReconClusterCount(),
+					lstat.getSSPClusterCount(),
+					rstat.getReconClusterCount(),
+					rstat.getSSPClusterCount()
+			};
+			String countFormat = "%" + mostDigits + "d";
+			setLocalRowValue(ROW_RECON_COUNT,  String.format(countFormat, clusterValue[0]));
+			setLocalRowValue(ROW_SSP_COUNT,    String.format(countFormat, clusterValue[1]));
+			setGlobalRowValue(ROW_RECON_COUNT, String.format(countFormat, clusterValue[2]));
+			setGlobalRowValue(ROW_SSP_COUNT,   String.format(countFormat, clusterValue[3]));
+			
+			// Output the tracked statistical data.
+			int total;
+			String percentFormat = "%" + mostDigits + "d / %" + mostDigits + "d (%7.3f)";
+			int[] statValue = {
+					lstat.getMatches(),
+					lstat.getPositionFailures(),
+					lstat.getEnergyFailures(),
+					lstat.getHitCountFailures(),
+					rstat.getMatches(),
+					rstat.getPositionFailures(),
+					rstat.getEnergyFailures(),
+					rstat.getHitCountFailures()
+			};
+			
+			total = lstat.getReconClusterCount();
+			setLocalRowValue(ROW_MATCHED,          String.format(percentFormat, statValue[0], total, 100.0 * statValue[0] / total));
+			setLocalRowValue(ROW_FAILED_POSITION,  String.format(percentFormat, statValue[1], total, 100.0 * statValue[1] / total));
+			setLocalRowValue(ROW_FAILED_ENERGY,    String.format(percentFormat, statValue[2], total, 100.0 * statValue[2] / total));
+			setLocalRowValue(ROW_FAILED_HIT_COUNT, String.format(percentFormat, statValue[3], total, 100.0 * statValue[3] / total));
+			
+			total = rstat.getReconClusterCount();
+			setGlobalRowValue(ROW_MATCHED,          String.format(percentFormat, statValue[4], total, 100.0 * statValue[4] / total));
+			setGlobalRowValue(ROW_FAILED_POSITION,  String.format(percentFormat, statValue[5], total, 100.0 * statValue[5] / total));
+			setGlobalRowValue(ROW_FAILED_ENERGY,    String.format(percentFormat, statValue[6], total, 100.0 * statValue[6] / total));
+			setGlobalRowValue(ROW_FAILED_HIT_COUNT, String.format(percentFormat, statValue[7], total, 100.0 * statValue[7] / total));
+		}
+	}
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ComponentUtils.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ComponentUtils.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/ComponentUtils.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,126 @@
+package org.hps.monitoring.trigger;
+
+import java.awt.Component;
+
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+
+/**
+ * Class <code>ComponentUtils</code> is a list of utility methods used
+ * by the trigger diagnostic GUI.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class ComponentUtils {
+	/** The default spacing used between a horizontal edge of one
+	 * component and the horizontal edge of another. */
+	static final int hinternal = 10;
+	/** The default spacing used between a vertical edge of one
+	 * component and the vertical edge of another. */
+	static final int vinternal = 10;
+	/** The default spacing used between a horizontal edge of one
+	 * component and the edge of its parent component. */
+	static final int hexternal = 0;
+	/** The default spacing used between a vertical edge of one
+	 * component and the edge of its parent component. */
+	static final int vexternal = 0;
+	
+	/**
+	 * Gets a <code>String</code> composed of a number of instances of
+	 * character <code>c</code> equal to <code>number</code>.
+	 * @param c - The character to repeat.
+	 * @param number - The number of repetitions.
+	 * @return Returns the repeated character as a <code>String</code>.
+	 */
+	public static final String getChars(char c, int number) {
+		// Create a buffer to store the characters in.
+		StringBuffer s = new StringBuffer();
+		
+		// Add the indicated number of instances.
+		for(int i = 0; i < number; i++) {
+			s.append(c);
+		}
+		
+		// Return the string.
+		return s.toString();
+	}
+	
+	/**
+	 * Gets the number of digits in the base-10 String representation
+	 * of an integer primitive. Negative signs are not included in the
+	 * digit count.
+	 * @param value - The value of which to obtain the length.
+	 * @return Returns the number of digits in the String representation
+	 * of the argument value.
+	 */
+	public static final int getDigits(int value) {
+		return TriggerDiagnosticUtil.getDigits(value);
+	}
+	
+	/**
+	 * Gets the maximum value from a list of values.
+	 * @param values - The values to compare.
+	 * @return Returns the largest of the argument values.
+	 * @throws IllegalArgumentException Occurs if no values are given.
+	 */
+	public static final int max(int... values) throws IllegalArgumentException {
+		// Throw an error if no arguments are provided.
+		if(values == null || values.length == 0) {
+			throw new IllegalArgumentException("Can not determine maximum value from a list of 0 values.");
+		}
+		
+		// If there is only one value, return it.
+		if(values.length == 1) { return values[0]; }
+		
+		// Otherwise, get the largest value.
+		int largest = Integer.MIN_VALUE;
+		for(int value : values) {
+			if(value > largest) { largest = value; }
+		}
+		
+		// Return the result.
+		return largest;
+	}
+	
+	/**
+	 * Gets the x-coordinate immediately to the right of the given
+	 * component.
+	 * @param c - The component of which to find the edge.
+	 * @return Returns the x-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextX(Component c) {
+		return getNextX(c, 0);
+	}
+	
+	/**
+	 * Gets the x-coordinate a given distance to the right edge of the
+	 * argument component.
+	 * @param c - The component of which to find the edge.
+	 * @param spacing - The additional spacing past the edge of the
+	 * component to add.
+	 * @return Returns the x-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextX(Component c, int spacing) {
+		return c.getX() + c.getWidth() + spacing;
+	}
+	
+	/**
+	 * Gets the y-coordinate immediately below the given component.
+	 * @param c - The component of which to find the edge.
+	 * @return Returns the y-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextY(Component c) {
+		return getNextY(c, 0);
+	}
+	
+	/**
+	 * Gets the y-coordinate a given distance below the bottom edge
+	 * of the argument component.
+	 * @param c - The component of which to find the edge.
+	 * @param spacing - The additional spacing past the edge of the
+	 * component to add.
+	 * @return Returns the y-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextY(Component c, int spacing) {
+		return c.getY() + c.getHeight() + spacing;
+	}
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/DiagnosticUpdatable.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/DiagnosticUpdatable.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/DiagnosticUpdatable.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,22 @@
+package org.hps.monitoring.trigger;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+
+/**
+ * Interface <code>DiagnosticUpdatable</code> defines a class of objects
+ * that can be updated with information from a trigger diagnostic driver.
+ * They can take snapshots of the driver results and use this in order to
+ * alter their displayed or constituent values.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see DiagSnapshot
+ */
+public interface DiagnosticUpdatable {
+	/**
+	 * Updates the object with information from the trigger diagnostic
+	 * snapshot in the argument.
+	 * @param snapshot - The snapshot containing information with which
+	 * to update the object.
+	 */
+	public void updatePanel(DiagSnapshot snapshot);
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/EfficiencyTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/EfficiencyTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/EfficiencyTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,116 @@
+package org.hps.monitoring.trigger;
+
+import javax.swing.JTable;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+import org.hps.analysis.trigger.event.TriggerEfficiencyModule;
+import org.hps.analysis.trigger.util.ComponentUtils;
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+
+public class EfficiencyTablePanel extends AbstractTablePanel implements DiagnosticUpdatable {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	
+	// Table models.
+	private TableTextModel localModel;
+	private TableTextModel globalModel;
+	
+	/**
+	 * Instantiates a new <code>EfficiencyTablePanel</code>.
+	 */
+	public EfficiencyTablePanel() {
+		// Instantiate the superclass.
+		super();
+		
+		// Set the orientation to vertical.
+		setOrientation(ORIENTATION_VERTICAL);
+	}
+	
+	@Override
+	public void updatePanel(DiagSnapshot snapshot) {
+		// If there is no snapshot, the tables should all display an
+		// empty value.
+		if(snapshot == null) {
+			for(int eventTriggerID = 0; eventTriggerID < 6; eventTriggerID++) {
+				for(int seenTriggerID = 0; seenTriggerID < 6; seenTriggerID++) {
+					localModel.setValueAt("--- / ---", eventTriggerID + 1, seenTriggerID + 1);
+					globalModel.setValueAt("--- / ---", eventTriggerID + 1, seenTriggerID + 1);
+				}
+			}
+		}
+		
+		// Otherwise, update the table cells from the snapshot data.
+		else {
+		// Get the efficiency modules.
+			TriggerEfficiencyModule rmod = snapshot.efficiencyRunStatistics;
+			TriggerEfficiencyModule lmod = snapshot.efficiencyLocalStatistics;
+			
+			// Determine the spacing needed to display the largest numerical
+			// cell value.
+			int numWidth = -1;
+			for(int eventTriggerID = 0; eventTriggerID < 6; eventTriggerID++) {
+				for(int seenTriggerID = 0; seenTriggerID < 6; seenTriggerID++) {
+					int rSize = ComponentUtils.getDigits(rmod.getTriggersSeen(eventTriggerID, seenTriggerID));
+					int lSize = ComponentUtils.getDigits(lmod.getTriggersSeen(eventTriggerID, seenTriggerID));
+					numWidth = ComponentUtils.max(numWidth, rSize, lSize);
+				}
+			}
+			
+			// Generate the format string for the cells.
+			String nullText = String.format("%s / %s", ComponentUtils.getChars('-', numWidth),
+					ComponentUtils.getChars('-', numWidth));
+			String format = "%" + numWidth + "d / %" + numWidth + "d";
+			
+			// Update the table.
+			for(int eventTriggerID = 0; eventTriggerID < 6; eventTriggerID++) {
+				for(int seenTriggerID = 0; seenTriggerID < 6; seenTriggerID++) {
+					if(eventTriggerID == seenTriggerID) {
+						localModel.setValueAt(nullText, eventTriggerID + 1, seenTriggerID + 1);
+					} else {
+						localModel.setValueAt(String.format(format, lmod.getTriggersMatched(eventTriggerID, seenTriggerID),
+								lmod.getTriggersSeen(eventTriggerID, seenTriggerID)), eventTriggerID + 1, seenTriggerID + 1);
+						globalModel.setValueAt(String.format(format, rmod.getTriggersMatched(eventTriggerID, seenTriggerID),
+								rmod.getTriggersSeen(eventTriggerID, seenTriggerID)), eventTriggerID + 1, seenTriggerID + 1);
+					}
+				}
+			}
+		}
+	}
+	
+	@Override
+	protected JTable[] initializeTables(Object... args) {
+		// Get a shorter reference to the trigger name list.
+		String[] triggerNames = TriggerDiagnosticUtil.TRIGGER_NAME;
+		
+		// Initialize the table models. There should be one row and
+		// one column for each type of trigger plus an additional one
+		// of each for headers.
+		localModel = new TableTextModel(triggerNames.length + 1, triggerNames.length + 1);
+		globalModel = new TableTextModel(triggerNames.length + 1, triggerNames.length + 1);
+		
+		// Set the column and row headers.
+		for(int triggerType = 0; triggerType < triggerNames.length; triggerType++) {
+			localModel.setValueAt(triggerNames[triggerType], triggerType + 1, 0);
+			localModel.setValueAt(triggerNames[triggerType], 0, triggerType + 1);
+			globalModel.setValueAt(triggerNames[triggerType], triggerType + 1, 0);
+			globalModel.setValueAt(triggerNames[triggerType], 0, triggerType + 1);
+		}
+		
+		// Create JTable objects to display the data.
+		JTable localTable = new JTable(localModel);
+		localTable.setRowSelectionAllowed(false);
+		localTable.setColumnSelectionAllowed(false);
+		localTable.setCellSelectionEnabled(false);
+		localTable.setShowVerticalLines(false);
+		
+		JTable globalTable = new JTable(globalModel);
+		globalTable.setRowSelectionAllowed(false);
+		globalTable.setColumnSelectionAllowed(false);
+		globalTable.setCellSelectionEnabled(false);
+		globalTable.setShowVerticalLines(false);
+		
+		// Return the tables.
+		return new JTable[] { localTable, globalTable };
+	}
+	
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/PairTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/PairTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/PairTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,33 @@
+package org.hps.monitoring.trigger;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+import org.hps.analysis.trigger.event.TriggerStatModule;
+
+/**
+ * Class <code>PairTablePanel</code> displays statistical information
+ * for trigger pair cuts.
+ * 
+ * @author Kyle McCarty
+ */
+public class PairTablePanel extends AbstractTriggerTablePanel {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	private static final String[] CUT_NAMES = { "        Energy Sum:",
+		"        Energy Difference:", "        Energy Slope:", "        Coplanarity:" };
+	
+	/**
+	 * Instantiates a <code>PairTablePanel</code>.
+	 */
+	public PairTablePanel() { super(CUT_NAMES); }
+
+	@Override
+	protected TriggerStatModule getLocalModule(DiagSnapshot snapshot) {
+		return snapshot.pairLocalStatistics;
+	}
+
+	@Override
+	protected TriggerStatModule getRunModule(DiagSnapshot snapshot) {
+		return snapshot.pairRunStatistics;
+	}
+	
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/SinglesTablePanel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/SinglesTablePanel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/SinglesTablePanel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,33 @@
+package org.hps.monitoring.trigger;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+import org.hps.analysis.trigger.event.TriggerStatModule;
+
+/**
+ * Class <code>SinglesTablePanel</code> displays statistical information
+ * for trigger singles cuts.
+ * 
+ * @author Kyle McCarty
+ */
+public class SinglesTablePanel extends AbstractTriggerTablePanel {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	private static final String[] CUT_NAMES = { "        Cluster Energy (Low):",
+		"        Cluster Energy (High):", "        Hit Count:"  };
+	
+	/**
+	 * Instantiates a <code>SinglesTablePanel</code>.
+	 */
+	public SinglesTablePanel() { super(CUT_NAMES); }
+	
+	@Override
+	protected TriggerStatModule getLocalModule(DiagSnapshot snapshot) {
+		return snapshot.singlesLocalStatistics;
+	}
+	
+	@Override
+	protected TriggerStatModule getRunModule(DiagSnapshot snapshot) {
+		return snapshot.singlesRunStatistics;
+	}
+	
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TableTextModel.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TableTextModel.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TableTextModel.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,103 @@
+package org.hps.monitoring.trigger;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Class <code>TableTextModel</code> is a simple implementation of
+ * <code>AbstractTableModel</code> that supports a definable number
+ * of rows and columns which must be populated with <code>String</code>
+ * data.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TableTextModel extends AbstractTableModel {
+	// Serial UID.
+	private static final long serialVersionUID = 0L;
+	
+	// Stored values.
+	private final int rows, columns;
+	private final String[][] values;
+	
+	/**
+	 * Instantiates a new <code>TableTextModel</code> with the indicated
+	 * number of rows and columns.
+	 * @param rows - The number of rows.
+	 * @param columns - The number of columns.
+	 */
+	public TableTextModel(int rows, int columns) {
+		// Make sure that the arguments for rows and columns are valid.
+		if(rows < 1) {
+			throw new IllegalArgumentException("TableTextModel must have at least one row.");
+		} else 	if(columns < 1) {
+			throw new IllegalArgumentException("TableTextModel must have at least one column.");
+		}
+		
+		// Define the number of rows and columns.
+		this.rows = rows;
+		this.columns = columns;
+		
+		// Instantiate the data storage array.
+		values = new String[rows][columns];
+	}
+	
+	@Override
+	public int getRowCount() { return rows; }
+	
+	@Override
+	public int getColumnCount() { return columns; }
+	
+	@Override
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		// Ensure that the value is within the allowed range.
+		validateIndex(rowIndex, columnIndex);
+		
+		// Return the value.
+		return values[rowIndex][columnIndex];
+	}
+	
+	@Override
+	public void setValueAt(Object value, int rowIndex, int columnIndex) {
+		// If the object is a string, pass it to the preferred handler.
+		// This can also be performed if the value is null.
+		if(value == null || value instanceof String) {
+			setValueAt((String) value, rowIndex, columnIndex);
+		}
+		
+		// Otherwise, cast the object to a string and use that instead.
+		else { setValueAt(value.toString(), rowIndex, columnIndex); }
+	}
+	
+	/**
+	 * Sets the text for the indicated column and row of the table.
+	 * @param value - The new text.
+	 * @param rowIndex - The row.
+	 * @param columnIndex - The column.
+	 * @throws IndexOutOfBoundsException Occurs if the row and column
+	 * are not a valid member of table model.
+	 */
+	public void setValueAt(String value, int rowIndex, int columnIndex) throws IndexOutOfBoundsException {
+		// Ensure that the value is within the allowed range.
+		validateIndex(rowIndex, columnIndex);
+		
+		// Set the value.
+		values[rowIndex][columnIndex] = value;
+	}
+	
+	/**
+	 * Checks to make sure that a given row/column pointer refers to
+	 * an extant position in the data array. In the event that the row
+	 * and column values are not valid, an <code>IndexOutOfBounds</code>
+	 * exception is thrown.
+	 * @param rowIndex - The row index.
+	 * @param columnIndex - The column index.
+	 * @throws IndexOutOfBoundsException Occurs if the row and column
+	 * are not a valid member of the data array.
+	 */
+	private void validateIndex(int rowIndex, int columnIndex) throws IndexOutOfBoundsException {
+		if(rowIndex < 0 || rowIndex >= getRowCount()) {
+			throw new IndexOutOfBoundsException(String.format("Row index %d is out of bounds.", rowIndex));
+		} else if(columnIndex < 0 || columnIndex >= getColumnCount()) {
+			throw new IndexOutOfBoundsException(String.format("Column index %d is out of bounds.", columnIndex));
+		}
+	}
+}

Added: java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TriggerDiagnosticGUIDriver.java
 =============================================================================
--- java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TriggerDiagnosticGUIDriver.java	(added)
+++ java/trunk/monitoring-util/src/main/java/org/hps/monitoring/trigger/TriggerDiagnosticGUIDriver.java	Thu Mar  5 13:46:25 2015
@@ -0,0 +1,43 @@
+package org.hps.monitoring.trigger;
+
+import java.util.List;
+
+import javax.swing.JFrame;
+
+import org.hps.analysis.trigger.DiagSnapshot;
+import org.lcsim.event.EventHeader;
+import org.lcsim.util.Driver;
+
+public class TriggerDiagnosticGUIDriver extends Driver {
+	private JFrame window = new JFrame();
+	private ClusterTablePanel clusterTable = new ClusterTablePanel();
+	private String diagnosticCollectionName = "DiagnosticSnapshot";
+	
+	@Override
+	public void startOfData() {
+		window.add(clusterTable);
+		window.setVisible(true);
+		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		window.setSize(500, 400);
+	}
+	
+	@Override
+	public void process(EventHeader event) {
+		// Updates are only performed if a diagnostic snapshot object
+		// exists. Otherwise, do nothing.
+		if(event.hasCollection(DiagSnapshot.class, diagnosticCollectionName)) {
+			// Get the snapshot collection.
+			List<DiagSnapshot> snapshotList = event.get(DiagSnapshot.class, diagnosticCollectionName);
+			
+			// Get the snapshot. There will only ever be one.
+			DiagSnapshot snapshot = snapshotList.get(0);
+			
+			// Feed it to the table.
+			clusterTable.updatePanel(snapshot);
+		}
+	}
+	
+	public void setDiagnosticCollectionName(String name) {
+		diagnosticCollectionName = name;
+	}
+}