Commit in java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal on MAIN
ColorScale.java+182added 122
Datum.java+110added 122
EcalHit.java+56added 122
EcalPanel.java+574added 122
EventManager.java+152added 122
GradientScale.java+147added 122
Main.java+78added 122
MultiGradientScale.java+172added 122
Viewer.java+223added 122
+1694
9 added files
initial commit of Kyle's ECal event display; not hooked into MonitoringApplication yet

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
ColorScale.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/ColorScale.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/ColorScale.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,182 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.Color;
+
+/**
+ * The abstract class <code>ColorScale</code> contains shared methods for all
+ * types of color scalers.
+ * 
+ * @author Kyle McCarty
+ **/
+public abstract class ColorScale {
+    // Indicates if linear or logairthmic scaling should be used.
+    protected boolean linear = true;
+    // The minimum value for color scaling.
+    protected double min = 0;
+    // The maximum value for color scaling.
+    protected double max = 1;
+    // A scale variable used for mapping logarithmic values.
+    protected double scale = 1.0;
+    // An efficiency variable used for logarithmic mapping.
+    protected double lMin = 0.0;
+    // An efficiency variable used for logarithmic mapping.
+    protected double lMax = 1.0;
+
+    /**
+     * <b>setMinimum</b><br/>
+     * <br/>
+     * <code>public void <b>setMinimum</b>(double minimum)</code><br/>
+     * <br/>
+     * Sets the value under which no color scaling will be performed.
+     * 
+     * @param minimum
+     *            - The lowest value for which color scaling will be performed.
+     **/
+    public void setMinimum(double minimum) {
+        min = minimum;
+        revalidate();
+    }
+
+    /**
+     * <b>setMaximum</b><br/>
+     * <br/>
+     * <code>public void <b>setMaximum</b>(double maximum)</code><br/>
+     * <br/>
+     * Sets the value over which no color scaling will be performed.
+     * 
+     * @param maximum
+     *            - The highest value for which color scaling will be performed.
+     **/
+    public void setMaximum(double maximum) {
+        max = maximum;
+        revalidate();
+    }
+
+    /**
+     * <b>getMinimum</b><br/>
+     * <br/>
+     * <code>public double <b>getMinimum</b>()</code><br/>
+     * <br/>
+     * Gets the lowest value for which color scaling is performed.
+     * 
+     * @return Returns the minimum value for color scaling as a
+     *         <code>double</code>.
+     **/
+    public double getMinimum() {
+        return min;
+    }
+
+    /**
+     * <b>getMaximum</b><br/>
+     * <br/>
+     * <code>public double <b>getMaximum</b>()</code><br/>
+     * <br/>
+     * Gets the highest value for which color scaling is performed.
+     * 
+     * @return Returns the maximum value for color scaling as a
+     *         <code>double</code>.
+     **/
+    public double getMaximum() {
+        return max;
+    }
+
+    /**
+     * <b>setScalingLinear</b><br/>
+     * <br/>
+     * <code>public void <b>setScalingLinear</b>()</code><br/>
+     * <br/>
+     * Sets the scaling behavior to linear.
+     **/
+    public void setScalingLinear() {
+        linear = true;
+        revalidate();
+    }
+
+    /**
+     * <b>isLinearScale</b><br/>
+     * <br/>
+     * <code>public boolean <b>isLinearScale</b>()</code><br/>
+     * <br/>
+     * Indicates whether this color mapping is linear or not.
+     * 
+     * @return Returns <code>true</code> if this is a linear mapping and
+     *         <code>false</code> otherwise.
+     **/
+    public boolean isLinearScale() {
+        return linear;
+    }
+
+    /**
+     * <b>isLogarithmicScale</b><br/>
+     * <br/>
+     * <code>public boolean <b>isLogarithmicScale</b>()</code><br/>
+     * <br/>
+     * Indicates whether this color mapping is logarithmic or not.
+     * 
+     * @return Returns <code>true</code> if this is a logarithmic mapping and
+     *         <code>false</code> if it is not.
+     **/
+    public boolean isLogairthmicScale() {
+        return !linear;
+    }
+
+    /**
+     * <b>setScalingLogarithmic</b><br/>
+     * <br/>
+     * <code>public void <b>setScalingLogarithmic</b>()</code><br/>
+     * <br/>
+     * Sets the scaling behavior to logarithmic.
+     **/
+    public void setScalingLogarithmic() {
+        linear = false;
+        revalidate();
+    }
+
+    /**
+     * <b>getColor</b><br/>
+     * <br/>
+     * <code>public Color <b>getColor</b>(double value)</code><br/>
+     * <br/>
+     * Determines the color representing the indicated value.
+     * 
+     * @param value
+     *            - The value to relate to a color.
+     * @return Returns a <code>Color</code> object associated with the argument
+     *         value.
+     **/
+    public abstract Color getColor(double value);
+
+    /**
+     * <b>revalidate</b><br/>
+     * <br/>
+     * <code>protected void <b>revalidate</b>()</code><br/>
+     * <br/>
+     * Makes any necessary changes whenever a critical value is changed.
+     **/
+    protected void revalidate() {
+        // Ensure that the minimum is not zero in the case of log scaling.
+        if (!linear && min == 0) {
+            if (max < 0.01) {
+                min = max / 100.0;
+            } else {
+                min = 0.01;
+            }
+        }
+
+        // We only need to revalidate if we are using a logarithmic scale.
+        if (!linear) {
+            // Determine the scaling variable for logarithmic results.
+            double temp = min;
+            int steps = 0;
+            while (temp < 1) {
+                temp = temp * 10;
+                steps++;
+            }
+            scale = Math.pow(10, steps);
+
+            // Revalidate the logarithmic variables.
+            lMax = Math.log10(scale * max);
+            lMin = Math.log10(scale * min);
+        }
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
Datum.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Datum.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Datum.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,110 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.Point;
+
+/**
+ * The <code>Datum</code> class contains a point representing a crystal in the
+ * calorimeter display panel.
+ * 
+ * @author Kyle McCarty
+ **/
+public class Datum {
+    // The coordinate on the calorimeter panel.
+    protected Point loc;
+
+    /**
+     * <b>Datum</b><br/>
+     * <br/>
+     * <code>public <b>Datum</b>()</code><br/>
+     * <br/>
+     * Initializes an empty <code>Datum</code>. Note that it will have an
+     * invalid coordinate.
+     **/
+    public Datum() {
+        this(-1, -1);
+    }
+
+    /**
+     * <b>Datum</b><br/>
+     * <br/>
+     * <code>public <b>Datum</b>(int x, int y)</code><br/>
+     * <br/>
+     * Initializes a new <code>Datum</code> at the indicated coordinate.
+     * 
+     * @param x
+     *            - The x-coordinate of the object.
+     * @param y
+     *            - The y-coordinate of the object.
+     **/
+    public Datum(int x, int y) {
+        loc = new Point(x, y);
+    }
+
+    /**
+     * <b>getX</b><br/>
+     * <br/>
+     * <code>public int <b>getX</b>()</code><br/>
+     * <br/>
+     * Indicates the x-coordinate of the object.
+     * 
+     * @return Returns the x-cooridinate as an <code>int</code>.
+     **/
+    public int getX() {
+        return loc.x;
+    }
+
+    /**
+     * <b>getY</b><br/>
+     * <br/>
+     * <code>public int <b>getY</b>()</code><br/>
+     * <br/>
+     * Indicates the y-coordinate of the object.
+     * 
+     * @return Returns the y-coordiate as an <code>int</code>.
+     **/
+    public int getY() {
+        return loc.y;
+    }
+
+    /**
+     * <b>getLocation</b><br/>
+     * <br/>
+     * <code>public Point <b>getLocation</b>()</code><br/>
+     * <br/>
+     * Indicates the location of the object.
+     * 
+     * @return Returns the object's location as a <code>Point
+     * </code> object.
+     **/
+    public Point getLocation() {
+        return loc;
+    }
+
+    /**
+     * <b>setX</b><br/>
+     * <br/>
+     * <code>public void <b>setX</b>(int x)</code><br/>
+     * <br/>
+     * Sets the object's x-coordinate.
+     * 
+     * @param x
+     *            - The new x-coordinate.
+     **/
+    public void setX(int x) {
+        loc.x = x;
+    }
+
+    /**
+     * <b>setY</b><br/>
+     * <br/>
+     * <code>public void <b>setY</b>(int y)</code><br/>
+     * <br/>
+     * Sets the obejct's y-coordinate.
+     * 
+     * @param y
+     *            - The new y-coordinate.
+     **/
+    public void setY(int y) {
+        loc.y = y;
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
EcalHit.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EcalHit.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EcalHit.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,56 @@
+package org.hps.monitoring.ecal;
+
+/**
+ * The class <code>EcalHit</code> is an extension of <code>Datum
+ * </code> that stores an energy.
+ **/
+public final class EcalHit extends Datum {
+    // The (raw) energy of this hit.
+    private double energy = 0.0;
+
+    /**
+     * <b>EcalHit</b><br/>
+     * <br/>
+     * <code>public <b>EcalHit</b>(int x, int y, double energy)</code><br/>
+     * <br/>
+     * Initializes a calorimeter hit object.
+     * 
+     * @param x
+     *            - The x-coordinate of the hit.
+     * @param y
+     *            - The y-coordinate of the hit.
+     * @param energy
+     *            - The raw energy of the hit.
+     **/
+    public EcalHit(int x, int y, double energy) {
+        super(x, y);
+        this.energy = energy;
+    }
+
+    /**
+     * <b>getEnergy</b><br/>
+     * <br/>
+     * <code>public double <b>getEnergy</b>()</code><br/>
+     * <br/>
+     * Indicates the raw energy of this ht.
+     * 
+     * @return Returns the raw energy as a <code>double</code>.
+     **/
+    public double getEnergy() {
+        return energy;
+    }
+
+    /**
+     * <b>setEnergy</b><br/>
+     * <br/>
+     * <code>public void <b>setEnergy</b>(double energy)</code><br/>
+     * <br/>
+     * Sets the energy of the hit to the indicated value.
+     * 
+     * @param energy
+     *            - The new energy of the hit.
+     **/
+    public void setEnergy(double energy) {
+        this.energy = energy;
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
EcalPanel.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EcalPanel.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EcalPanel.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,574 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Line2D;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+import javax.swing.JPanel;
+
+/**
+ * The class <code>EcalPanel</code> handles the rendering of the calorimeter
+ * crystals as well as mapping colors to their values, rendering a color scale,
+ * and marking cluster crystals.
+ * 
+ * @author Kyle McCarty
+ **/
+public class EcalPanel extends JPanel {
+    // Java-suggested variable.
+    private static final long serialVersionUID = 6292751227464151897L;
+    // The color used for rendering seed hits.
+    private Color clusterColor = Color.GREEN;
+    // The color-mapping scale used by to color calorimeter crystals.
+    private MultiGradientScale scale = MultiGradientScale.makeRainbowScale(0.0,
+            1.0);
+    // The number of boxes in the x-direction.
+    private int xBoxes = 1;
+    // The number of boxes in the y-direction.
+    private int yBoxes = 1;
+    // The width of the scale.
+    private int scaleWidth = 75;
+    // Whether the scale has changed or not since its last rendering.
+    private boolean scaleChanged = true;
+    // Stores which calorimeter crystals are disabled.
+    private boolean[][] disabled;
+    // Stores the energies in each calorimeter crystal.
+    private double[][] hit;
+    // Stores whether a crystal is the location of a seed hit.
+    private boolean[][] cluster;
+    // Stores whether a crystal has changed.
+    private boolean changed[][];
+    // Stores whether the panel size has chaged.
+    private boolean sizeChanged = true;
+    // The panel on which the scale is rendered.
+    ScalePanel scalePanel = new ScalePanel();
+
+    // Efficiency variables for crystal placement.
+    int boxWidth = 0;
+    int widthRem = 0;
+    int boxHeight = 0;
+    int heightRem = 0;
+
+    /**
+     * <b>EcalPanel</b><br/>
+     * <br/>
+     * Initializes the calorimeter panel.
+     * 
+     * @param numXBoxes
+     *            - The number of crystals in the x-direction.
+     * @param numYBoxes
+     *            - The number of crystals in the y-direction.
+     **/
+    public EcalPanel(int numXBoxes, int numYBoxes) {
+        // Initialize the base component.
+        super();
+
+        // Set the number of calorimeter crystals.
+        xBoxes = numXBoxes;
+        yBoxes = numYBoxes;
+
+        // Initialize the arrays.
+        disabled = new boolean[xBoxes][yBoxes];
+        hit = new double[xBoxes][yBoxes];
+        cluster = new boolean[xBoxes][yBoxes];
+        changed = new boolean[xBoxes][yBoxes];
+
+        // Add the scale panel.
+        setLayout(null);
+        add(scalePanel);
+        sizeChanged = true;
+        scaleChanged = true;
+    }
+
+    /**
+     * <b>setCrystalEnabled</b><br/>
+     * <br/>
+     * <code>public void <b>setCrystalEnabled</b>(int xIndex, int yIndex, boolean active)</code>
+     * <br/>
+     * <br/>
+     * Sets whether the indicated crystal is enabled or not. Invalid indices
+     * will be ignored.
+     * 
+     * @param xIndex
+     *            - The x-coordinate of the crystal.
+     * @param yIndex
+     *            - The y-coordinate of the crystal.
+     * @param active
+     *            - This should be <code>true</code> if the crystal is active
+     *            and <code>false</code> if it is not.
+     * @throws IndexOutOfBoundsException
+     *             Occurs when the given xy crystal coordinate does not point to
+     *             a crystal.
+     **/
+    public void setCrystalEnabled(int xIndex, int yIndex, boolean active)
+            throws IndexOutOfBoundsException {
+        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
+            disabled[xIndex][yIndex] = !active;
+            changed[xIndex][yIndex] = true;
+        } else {
+            throw new IndexOutOfBoundsException(String.format(
+                    "Invalid crystal address (%2d, %2d).", xIndex, yIndex));
+        }
+    }
+
+    /**
+     * <b>addCrystalEnergy</b><br/>
+     * <br/>
+     * <code>public void <b>addCrystalEnergy</b>(int xIndex, int yIndex, double energy)</code>
+     * <br/>
+     * <br/>
+     * Adds the indicated quantity of energy to the crystal at the given
+     * coordinates.
+     * 
+     * @param xIndex
+     *            - The x-coordinate of the crystal.
+     * @param yIndex
+     *            - The y-coordinate of the crystal.
+     * @param energy
+     *            - The energy to add.
+     * @throws IndexOutOfBoundsException
+     *             Occurs when the given xy crystal coordinate does not point to
+     *             a crystal.
+     **/
+    public void addCrystalEnergy(int xIndex, int yIndex, double energy)
+            throws IndexOutOfBoundsException {
+        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
+            this.hit[xIndex][yIndex] += energy;
+            changed[xIndex][yIndex] = true;
+        } else {
+            throw new IndexOutOfBoundsException(String.format(
+                    "Invalid crystal address (%2d, %2d).", xIndex, yIndex));
+        }
+    }
+
+    /**
+     * <b>setCrystalCluster</b>
+     * <code>public void <b>setCrystalCluster</b>(int xIndex, int yIndex, boolean cluster)</code>
+     * <br/>
+     * <br/>
+     * Sets whether a crystal is also the location of a seed hit.
+     * 
+     * @param xIndex
+     *            - The x-coordinate of the crystal.
+     * @param yIndex
+     *            - The y-coordinate of the crystal.
+     * @param cluster
+     *            - This should be <code>true</code> if there is a seed hit and
+     *            <code>false</code> if there is not.
+     * @throws IndexOutOfBoundsException
+     *             Occurs when the given xy crystal coordinate does not point to
+     *             a crystal.
+     **/
+    public void setCrystalCluster(int xIndex, int yIndex, boolean cluster)
+            throws IndexOutOfBoundsException {
+        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
+            this.cluster[xIndex][yIndex] = cluster;
+            changed[xIndex][yIndex] = true;
+        } else {
+            throw new IndexOutOfBoundsException(String.format(
+                    "Invalid crystal address (%2d, %2d).", xIndex, yIndex));
+        }
+    }
+
+    /**
+     * <b>clearCrystals</b><br/>
+     * <br/>
+     * <code>public void <b>clearCrystals</b>()</code><br/>
+     * <br/>
+     * Sets all crystal energies to zero and removes all clusters. This <b>does
+     * not</b> enable any disabled crystals.
+     **/
+    public void clearCrystals() {
+        for (int x = 0; x < xBoxes; x++) {
+            for (int y = 0; y < yBoxes; y++) {
+                if (hit[x][y] != 0.0) {
+                    hit[x][y] = 0.0;
+                    changed[x][y] = true;
+                }
+                if (cluster[x][y]) {
+                    cluster[x][y] = false;
+                    changed[x][y] = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * <b>setClusterColor</b><br/>
+     * <br/>
+     * <code>public void <b>setClusterColor</b>(Color c)</code><br/>
+     * <br/>
+     * Sets the color of the seed hit marker.
+     * 
+     * @param c
+     *            - The color to be used for seed hit markers. A value of
+     *            <code>null</code> will result in seed hit markers being the
+     *            inverse color of the crystal in which they appear.
+     **/
+    public void setClusterColor(Color c) {
+        clusterColor = c;
+    }
+
+    /**
+     * <b>setMinimum</b><br/>
+     * <br>
+     * <code>public void <b>setMinimum</b>(double minimum)</code><br/>
+     * <br/>
+     * Sets the minimum value of the color mapping scale. Energies below this
+     * value will all be the same minimum color.
+     * 
+     * @param minimum
+     *            - The minimum energy to be mapped.
+     **/
+    public void setMinimum(double minimum) {
+        scale.setMinimum(minimum);
+        scaleChanged = true;
+    }
+
+    /**
+     * <b>setMaximum</b><br/>
+     * <br/>
+     * <code>public void <b>setMaximum</b>(double maximum)</code><br/>
+     * <br/>
+     * Sets the maximum value of the color mapping scale. Energies above this
+     * value will all be the same maximum color.
+     * 
+     * @param maximum
+     *            - The maximum energy to be mapped.
+     **/
+    public void setMaximum(double maximum) {
+        scale.setMaximum(maximum);
+        scaleChanged = true;
+    }
+
+    /**
+     * <b>setScalingLinear</b><br/>
+     * <br/>
+     * <code>public void <b>setScalingLinear</b>()<br/><br/>
+     * Sets the color mapping scale behavior to linear mapping.
+     **/
+    public void setScalingLinear() {
+        scale.setScalingLinear();
+        scaleChanged = true;
+    }
+
+    /**
+     * <b>setScalingLogarithmic</b><br/>
+     * <br/>
+     * <code>public void <b>setScalingLogarithmic</b></code><br/>
+     * <br/>
+     * Sets the color mapping scale behavior to logarithmic mapping.
+     **/
+    public void setScalingLogarithmic() {
+        scale.setScalingLogarithmic();
+        scaleChanged = true;
+    }
+
+    /**
+     * <b>setScaleEnabled</b><br/>
+     * <br/>
+     * <code>public void <b>setScaleEnabled</b>(boolean enabled)</code><br/>
+     * <br/>
+     * Sets whether the scale should be visible or not.
+     * 
+     * @param enabled
+     *            - <code>true</code> indicates that the scale should be visible
+     *            and <code>false</code> that it should be hidden.
+     **/
+    public void setScaleEnabled(boolean enabled) {
+        if (scalePanel.isVisible() != enabled) {
+            scalePanel.setVisible(enabled);
+            scaleChanged = true;
+            sizeChanged = true;
+        }
+    }
+
+    /**
+     * <b>redraw</b><br/>
+     * <br/>
+     * <code>public void <b>redraw</b>()</code> Re-renders the calorimeter
+     * panel.
+     **/
+    public void redraw() {
+        super.repaint();
+    }
+
+    public void setSize(Dimension d) {
+        setSize(d.width, d.height);
+    }
+
+    public void setSize(int width, int height) {
+        super.setSize(width, height);
+        scalePanel.setLocation(width - scaleWidth, 0);
+        scalePanel.setSize(scaleWidth, height);
+        sizeChanged = true;
+    }
+
+    protected void paintComponent(Graphics g) {
+        if (sizeChanged) {
+            // Determine the width and heights of the calorimeter crystals.
+            int width;
+            if (scalePanel.isVisible()) {
+                width = getWidth() - scaleWidth;
+            } else {
+                width = getWidth();
+            }
+            int height = getHeight();
+
+            boxWidth = width / xBoxes;
+            widthRem = width % xBoxes;
+            boxHeight = height / yBoxes;
+            heightRem = height % yBoxes;
+        }
+        int heightRemReset = heightRem;
+        int widthRemReset = widthRem;
+
+        // Start drawing the calorimeter crystals. To avoid having empty
+        // space, we distribute the extra widthRem pixels to the boxes at
+        // a rate of one pixel for box until we run out. We do the same thing
+        // for the heightRem.
+        int curX = 0;
+        int curY = 0;
+        for (int x = 0; x < xBoxes; x++) {
+            // Determine if this column should use an extra pixel.
+            int tw = boxWidth;
+            if (widthRem != 0) {
+                tw++;
+                widthRem--;
+            }
+            for (int y = 0; y < yBoxes; y++) {
+                // Determine if this row should use an extra pixel.
+                int th = boxHeight;
+                if (heightRem != 0) {
+                    th++;
+                    heightRem--;
+                }
+
+                if (sizeChanged || scaleChanged || changed[x][y]) {
+                    // Determine the appropriate color for the box.
+                    Color crystalColor;
+                    if (disabled[x][y]) {
+                        crystalColor = Color.BLACK;
+                    } else {
+                        crystalColor = scale.getColor(hit[x][y]);
+                    }
+                    g.setColor(crystalColor);
+
+                    // Draw the box.
+                    g.fillRect(curX, curY, tw, th);
+                    g.setColor(Color.BLACK);
+                    g.drawRect(curX, curY, tw, th);
+
+                    // If there is a cluster, draw an x.
+                    if (cluster[x][y]) {
+                        // Get the correct coordinates.
+                        double ltw = (0.3 * tw) / 2;
+                        double lth = (0.5 * th) / 2;
+                        double[] lx = { curX + ltw, curX + tw - ltw };
+                        double[] ly = { curY + lth, curY + th - lth };
+
+                        // Get the appropriate cluster color.
+                        Color c;
+                        if (clusterColor == null) {
+                            int red = Math.abs(255 - crystalColor.getRed());
+                            int blue = Math.abs(255 - crystalColor.getBlue());
+                            int green = Math.abs(255 - crystalColor.getGreen());
+                            c = new Color(red, green, blue);
+                        } else {
+                            c = clusterColor;
+                        }
+
+                        // Draw an x on the cluster crystal.
+                        Graphics2D g2 = (Graphics2D) g;
+                        g2.setColor(c);
+                        // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                        // RenderingHints.VALUE_ANTIALIAS_ON);
+                        g2.setStroke(new BasicStroke(2));
+                        g2.draw(new Line2D.Double(lx[0], ly[0], lx[1], ly[1]));
+                        g2.draw(new Line2D.Double(lx[0], ly[1], lx[1], ly[0]));
+                        // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                        // RenderingHints.VALUE_ANTIALIAS_OFF);
+                    }
+
+                    // Note that this crystals has been updated.
+                    changed[x][y] = false;
+                }
+
+                // Increment the current y position.
+                curY += th;
+            }
+
+            // Increment the current x position.
+            curX += tw;
+
+            // Reset the current y position and heightRem.
+            curY = 0;
+            heightRem = heightRemReset;
+        }
+
+        // If the scale has changed, redraw the scale panel as well.
+        if (scaleChanged && scalePanel.isVisible()) {
+            scalePanel.redraw();
+        }
+
+        // Indicate that the any size changes have been handled.
+        scaleChanged = false;
+        sizeChanged = false;
+
+        // Reset the height and width remainder variables.
+        heightRem = heightRemReset;
+        widthRem = widthRemReset;
+    }
+
+    /**
+     * The local class <b>ScalePanel</b> renders the scale for the calorimeter
+     * color map.
+     **/
+    private class ScalePanel extends JPanel {
+        /**
+         * <b>redraw</b><br/>
+         * <br/>
+         * <code>public void <b>redraw</b>()</code><br/>
+         * <br/>
+         * Orders the scale to re-render itself.
+         **/
+        public void redraw() {
+            super.repaint();
+        }
+
+        protected void paintComponent(Graphics g) {
+            // Set the text region width.
+            int textWidth = 45;
+            boolean useText;
+
+            // Store height and width.
+            int height = getHeight();
+            int width;
+            if (getWidth() > textWidth) {
+                width = getWidth() - textWidth;
+                useText = true;
+            } else {
+                width = getWidth();
+                useText = false;
+            }
+
+            // Define the step size for the scale. This will differ depending
+            // on whether we employ a linear or logarithmic scale.
+            double step;
+            double curValue;
+            boolean linear = scale.isLinearScale();
+            if (linear) {
+                step = (scale.getMaximum() - scale.getMinimum()) / height;
+                curValue = scale.getMinimum();
+            } else {
+                double max = Math.log10(scale.getMaximum());
+                double min = Math.log10(scale.getMinimum());
+                step = (max - min) / height;
+                curValue = min;
+            }
+
+            // Color the text area.
+            g.setColor(Color.BLACK);
+            g.drawRect(0, 0, width, height);
+            g.drawRect(1, 1, width - 1, height - 1);
+            g.fillRect(width, 0, textWidth, height);
+
+            // Render the scale.
+            int sy = height;
+            int[] sx = { 0, width };
+            for (int i = 0; i <= height; i++) {
+                // Get the appropriate value for the current pixel.
+                double scaledValue;
+                if (linear) {
+                    scaledValue = curValue;
+                } else {
+                    scaledValue = Math.pow(10, curValue);
+                }
+                g.setColor(scale.getColor(scaledValue));
+
+                // Draw a line.
+                g.drawLine(sx[0], sy, sx[1], sy);
+
+                // Update the spacing variables.
+                curValue += step;
+                sy--;
+            }
+
+            // Generate the scale text.
+            if (useText) {
+                // Determine the spacing of the text.
+                FontMetrics fm = g.getFontMetrics(g.getFont());
+                int fontHeight = fm.getHeight();
+                double fStep = (height - 2.0 * fontHeight) / fontHeight;
+                double halfStep = fStep / 2.0;
+
+                // Get the scaling value.
+                double fScale;
+                double fMin;
+                if (linear) {
+                    fScale = scale.getMaximum() - scale.getMinimum();
+                    fMin = scale.getMinimum();
+                } else {
+                    fScale = Math.log10(scale.getMaximum())
+                            - Math.log10(scale.getMinimum());
+                    fMin = Math.log10(scale.getMinimum());
+                }
+
+                // Populate the first and last values.
+                NumberFormat nf = new DecimalFormat("0.#E0");
+                g.setColor(Color.WHITE);
+                g.drawString(nf.format(scale.getMaximum()), width + 5,
+                        fontHeight);
+                g.drawString(nf.format(scale.getMinimum()), width + 5,
+                        height - 3);
+
+                // Calculate text placement variables.
+                double heightAvailable = height - 2.0 * fontHeight;
+                double heightDefault = heightAvailable / (1.5 * fontHeight);
+                int num = (int) Math.floor(heightAvailable / heightDefault);
+                double heightRemainder = heightAvailable
+                        - (num * heightDefault);
+                double heightExtra = heightRemainder / num;
+                double lSpacing = heightDefault + heightExtra;
+                double lHalfSpacing = lSpacing / 2.0;
+                int lHeight = fontHeight + 3;
+                int[] lX = { width - 4, width, width + 5 };
+                int lShift = (int) (fontHeight * 0.25 + lHalfSpacing);
+                double lTemp = 0.0;
+
+                // Calculate value conversion variables.
+                double lMin = scale.getMinimum();
+                double lScale;
+                if (linear) {
+                    lMin = scale.getMinimum();
+                    lScale = scale.getMaximum() - scale.getMinimum();
+                } else {
+                    double min = Math.log10(scale.getMinimum());
+                    double max = Math.log10(scale.getMaximum());
+                    lMin = min;
+                    lScale = max - min;
+                }
+
+                // Write the labels.
+                for (int i = 0; i < num; i++) {
+                    g.setColor(Color.BLACK);
+                    int h = (int) (lHeight + lHalfSpacing);
+                    g.drawLine(lX[0], h, lX[1], h);
+                    g.setColor(Color.WHITE);
+                    double lVal = lMin + (1.0 - ((double) h / height)) * lScale;
+                    if (!linear) {
+                        lVal = Math.pow(10, lVal);
+                    }
+                    g.drawString(nf.format(lVal), lX[2], lHeight + lShift);
+                    lTemp += lSpacing;
+                    lHeight = (int) (fontHeight + lTemp);
+                }
+            }
+        }
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
EventManager.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EventManager.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EventManager.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,152 @@
+package org.hps.monitoring.ecal;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * The class <code>EventManager</code> handles loading hits and clusters from a
+ * text file to populate the calorimeter panel.
+ * 
+ * @author Kyle McCarty
+ **/
+public class EventManager {
+    // File readers for reading the input.
+    private FileReader fr;
+    private BufferedReader reader;
+    // List for storing the hits from the current event.
+    private ArrayList<EcalHit> hitList = new ArrayList<EcalHit>();
+    // List for storing the clusters from the current hit.
+    private ArrayList<Datum> clusterList = new ArrayList<Datum>();
+    // Whether the event manager has an open file.
+    private boolean open = true;
+
+    /**
+     * <b>EventManager</b><br/>
+     * <br/>
+     * <code>public <b>EventManager</b>(String filename)</code><br/>
+     * <br/>
+     * Initializes an event manager that will read from the indicated file.
+     * 
+     * @param filename
+     *            - The path to the file containing hit information.
+     **/
+    public EventManager(String filename) throws IOException {
+        fr = new FileReader(filename);
+        reader = new BufferedReader(fr);
+    }
+
+    /**
+     * <b>readEvent</b><br/>
+     * <br/>
+     * <code>public boolean <b>readEvent</b>()</code><br/>
+     * <br/>
+     * Populates the event manager with hits and clusters from the next event.
+     * 
+     * @return Returns <code>true</code> if an event was read and
+     *         <code>false</code> if it was not.
+     **/
+    public boolean readEvent() throws IOException {
+        // We can only read of the reader is open.
+        if (!open) {
+            return false;
+        }
+
+        // Clear the data lists.
+        hitList.clear();
+        clusterList.clear();
+
+        // Store the current line.
+        String curLine = reader.readLine();
+
+        // Keep sorting until we hit a null or an event header.
+        while (curLine != null && curLine.compareTo("Event") != 0) {
+            curLine = reader.readLine();
+        }
+
+        // If we hit a null, we are at the end of the file.
+        if (curLine == null) {
+            return false;
+        }
+
+        // Otherwise, we have read an event header and must populate
+        // the data lists.
+        curLine = reader.readLine();
+        while (curLine != null && curLine.compareTo("Event") != 0) {
+            // Break apart the line.
+            StringTokenizer st = new StringTokenizer(curLine);
+            String name = st.nextToken();
+            int ix = Integer.parseInt(st.nextToken());
+            int iy = Integer.parseInt(st.nextToken());
+
+            // Convert it to an object.
+            if (name.compareTo("Cluster") == 0) {
+                clusterList.add(new Datum(ix, iy));
+            } else if (name.compareTo("EcalHit") == 0) {
+                double energy = Double.parseDouble(st.nextToken());
+                hitList.add(new EcalHit(ix, iy, energy));
+            }
+
+            // Get the next line.
+            curLine = reader.readLine();
+        }
+
+        // Indicate that an event was processed.
+        return true;
+    }
+
+    /**
+     * <b>close</b><br/>
+     * <br/>
+     * <code>public void <b>close</b>()</code><br/>
+     * <br/>
+     * Closes the event manager. Once this is performed, no additional events
+     * may be read.
+     * 
+     * @throws IOException
+     *             Occurs if there is an error closing the file stream.
+     **/
+    public void close() throws IOException {
+        reader.close();
+        fr.close();
+        open = false;
+    }
+
+    /**
+     * <b>getHits</b><br/>
+     * <br/>
+     * <code>public ArrayList<EcalHit> <b>getHits</b>()</code><br/>
+     * <br/>
+     * Allows access to the current event's list of hits.
+     * 
+     * @return Returns the current hits as an <code>ArrayList
+     * </code> object.
+     **/
+    public ArrayList<EcalHit> getHits() {
+        if (!open) {
+            return null;
+        } else {
+            return hitList;
+        }
+    }
+
+    /**
+     * <b>getClusters</b><br/>
+     * <br/>
+     * <code>public ArrayList<Datum> <b>getClusters</b></code><br/>
+     * <br/>
+     * Allows access to the current event's list of clusters.
+     * 
+     * @return Returns the current clusters as an <code>ArrayList
+     * </code> object.
+     **/
+    public ArrayList<Datum> getClusters() {
+        if (!open) {
+            return null;
+        } else {
+            return clusterList;
+        }
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
GradientScale.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/GradientScale.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/GradientScale.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,147 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.Color;
+
+/**
+ * The class <code>GradientScale</code> is an implementation of the abstract
+ * class <code>ColorScale</code> which represents a simple gradient from one
+ * color to another. It will map any argument value that exceeds its maximum
+ * value to the hot color and any argument value that is below its minimum value
+ * to its cold color. All other argument values will be mapped somewhere between
+ * the cold and hot colors using either a linear or logarithmic scale.
+ * 
+ * @author Kyle McCarty
+ **/
+public final class GradientScale extends ColorScale {
+    // The color associated with the maximum value.
+    private Color hotColor = Color.WHITE;
+    // The color associated with the minimum value.
+    private Color coldColor = Color.BLACK;
+    // Efficiency variable holding the rgb difference between the two
+    // colors. This is used to prevent recalculation when mapping values.
+    private int[] drgb = { 255, 255, 255 };
+
+    /**
+     * <b>setHotColor</b><br/>
+     * <br/>
+     * <code>public void <b>setHotColor</b>(Color c)</code><br/>
+     * <br/>
+     * Sets the color associated with the maximum value.
+     * 
+     * @param c
+     *            - The new color to use.
+     **/
+    public void setHotColor(Color c) {
+        hotColor = c;
+        revalidateColor();
+    }
+
+    /**
+     * <b>setColdColor</b><br/>
+     * <br/>
+     * <code>public void <b>setColdColor</b>(Color c)</code><br/>
+     * <br/>
+     * Sets the color assocaited with the minimum value.
+     * 
+     * @param c
+     *            - The color to use.
+     **/
+    public void setColdColor(Color c) {
+        coldColor = c;
+        revalidateColor();
+    }
+
+    public Color getColor(double value) {
+        // If the value is less than the minimum, return the cold color.
+        if (value < min) {
+            return coldColor;
+        }
+
+        // If the value is greater than the maximum, return the hot color.
+        if (value > max) {
+            return hotColor;
+        }
+
+        // Otherwise, calculate how far along the gradient the value is.
+        double percent;
+        if (linear) {
+            percent = (value - min) / (max - min);
+        } else {
+            double lValue = Math.log10(scale * value);
+            percent = (lValue - lMin) / (lMax - lMin);
+        }
+
+        // Scale the color.
+        int dr = (int) Math.round(percent * drgb[0]);
+        int dg = (int) Math.round(percent * drgb[1]);
+        int db = (int) Math.round(percent * drgb[2]);
+
+        // Return the result.
+        return new Color(coldColor.getRed() + dr, coldColor.getGreen() + dg,
+                coldColor.getBlue() + db);
+    }
+
+    /**
+     * <b>revalidateColor</b><br/>
+     * <br/>
+     * <code>private void <b>revalidateColor</b>()</code><br/>
+     * <br/>
+     * Calculates the differences between the hot and cold colors and sets the
+     * class related class variables.
+     **/
+    private void revalidateColor() {
+        drgb[0] = hotColor.getRed() - coldColor.getRed();
+        drgb[1] = hotColor.getGreen() - coldColor.getGreen();
+        drgb[2] = hotColor.getBlue() - coldColor.getBlue();
+    }
+
+    /**
+     * <b>makeGreyScale</b><br/>
+     * <br/>
+     * <code>public static GradientScale <b>makeGreyScale</b>(double minimum, double maximum)</code>
+     * <br/>
+     * <br/>
+     * Creates a color scale that ranges from black (cold) to white (hot) with
+     * the indicated maximum and minimum.
+     * 
+     * @param minimum
+     *            - The lowest value for color scaling.
+     * @param maximum
+     *            - The highest value for color scaling.
+     * @return Returns a <code>GradientScale</code> that maps to grey scale over
+     *         the indicated range.
+     **/
+    public static GradientScale makeGreyScale(double minimum, double maximum) {
+        GradientScale gs = new GradientScale();
+        gs.setMinimum(minimum);
+        gs.setMaximum(maximum);
+
+        return gs;
+    }
+
+    /**
+     * <b>makeHeatScale</b><br/>
+     * <br>
+     * <code>public static GradientScale <b>makeHeatScale</b>(double minimum, double maximum)</code>
+     * <br/>
+     * <br/>
+     * Creates a color scale that ranges from black (cold) to red (hot) with the
+     * indicated maximum and minimum.
+     * 
+     * @param minimum
+     *            - The lowest value for color scaling.
+     * @param maximum
+     *            - The highest value for color scaling.
+     * @return Returns a <code>GradientScale</code> that maps to a black- to-red
+     *         gradient over the indicated range.
+     **/
+    public static GradientScale makeHeatScale(double minimum, double maximum) {
+        GradientScale hs = new GradientScale();
+        hs.setHotColor(Color.RED);
+        hs.setColdColor(Color.BLACK);
+        hs.setMinimum(minimum);
+        hs.setMaximum(maximum);
+
+        return hs;
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
Main.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Main.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Main.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,78 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Random;
+
+public class Main {
+    private static final Viewer window = new Viewer();
+
+    public static void main(String[] args) throws IOException {
+        // Get screen size of primary monitor
+        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment()
+                .getDefaultScreenDevice();
+        int screenWidth = gd.getDisplayMode().getWidth();
+        int screenHeight = gd.getDisplayMode().getHeight();
+
+        // Set the viewer location and make it visible
+        window.setLocation((screenWidth - window.getPreferredSize().width) / 2,
+                (screenHeight - window.getPreferredSize().height) / 2);
+        window.setDataSource("cluster-hit.txt");
+        window.displayNextEvent();
+        window.setVisible(true);
+
+        // makeData();
+    }
+
+    static void makeData() {
+        // Generate a random test input file.
+        Random rng = new Random();
+        try {
+            // Make a file writer to write the results.
+            FileWriter writer = new FileWriter("cluster-hit.txt");
+
+            // Make 10 - 100 events.
+            int events = 10 + rng.nextInt(91);
+
+            // For each events, generate some data.
+            for (int e = 0; e < events; e++) {
+                // Write the event header.
+                writer.append("Event\n");
+
+                // Make 3 - 15 hits.
+                int hits = 3 + rng.nextInt(13);
+                for (int h = 0; h < hits; h++) {
+                    // Write identifier.
+                    writer.append("EcalHit\t");
+
+                    // Make a random address.
+                    // x = [0, 46); y = [0, 11)
+                    int ix = rng.nextInt(46);
+                    int iy = rng.nextInt(11);
+                    writer.append(ix + "\t" + iy + "\n");
+                }
+
+                // Make 0 - 4 clusters.
+                int clusters = rng.nextInt(5);
+                for (int c = 0; c < clusters; c++) {
+                    // Write identifier.
+                    writer.append("Cluster\t");
+
+                    // Make a random address.
+                    // x = [0, 46); y = [0, 11)
+                    int ix = rng.nextInt(46);
+                    int iy = rng.nextInt(11);
+                    writer.append(ix + "\t" + iy + "\n");
+                }
+            }
+
+            // Close the writer.
+            writer.close();
+        } catch (IOException e) {
+            System.err.println(e.getStackTrace());
+            System.exit(1);
+        }
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
MultiGradientScale.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/MultiGradientScale.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/MultiGradientScale.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,172 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+/**
+ * The class <code>MultiGradientScale</code> is an implementation of
+ * <code>ColorScale</code> that maps values to a color over several different
+ * individual <code>GradientScale</code> objects to allow for multi-color
+ * mapping.
+ * 
+ * @author Kyle McCarty
+ **/
+public final class MultiGradientScale extends ColorScale {
+    // Stores the colors in the map.
+    private ArrayList<Color> colorList = new ArrayList<Color>();
+    // Stores the component mapping scales.
+    private ArrayList<GradientScale> scaleList = new ArrayList<GradientScale>();
+
+    /**
+     * <b>addColor</b><br/>
+     * <br/>
+     * <code>public void <b>addColor</b>(Color c)</code><br/>
+     * <br/>
+     * Adds a new color to the mapping scale. The first color will be the
+     * coldest with subsequent colors being more and more hot.
+     * 
+     * @param c
+     *            - The color to add.
+     **/
+    public void addColor(Color c) {
+        colorList.add(c);
+        revalidate();
+    }
+
+    /**
+     * <b>removeColor</b><br/>
+     * <br/>
+     * <code>public boolean <b>removeColor</b>(int colorIndex)</code><br/>
+     * <br/>
+     * Removes the nth color from the mapping scale.
+     * 
+     * @param colorIndex
+     *            - The index of the color to be removed.
+     * @return Returns <code>true</code> if the color was removed and
+     *         <code>false</code> if it was not.
+     **/
+    public boolean removeColor(int colorIndex) {
+        // Only remove the value if the index is valid.
+        if (colorIndex >= 0 && colorIndex < colorList.size()) {
+            colorList.remove(colorIndex);
+            revalidate();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public Color getColor(double value) {
+        // Get the number of colors and scales.
+        int colors = colorList.size();
+        int scales = scaleList.size();
+
+        // If there are no colors or scales, give black.
+        if (colors == 0 && scales == 0) {
+            return Color.BLACK;
+        }
+
+        // If there are no scales, but there is a color, give that.
+        if (scales == 0 && colors == 1) {
+            return colorList.get(0);
+        }
+
+        // Scale the value if logarithmic.
+        double sValue;
+        if (linear) {
+            sValue = value;
+        } else {
+            sValue = Math.log10(scale * value);
+        }
+
+        // Otherwise, determine which scale should get the value.
+        for (GradientScale s : scaleList) {
+            if (sValue < s.getMaximum()) {
+                return s.getColor(sValue);
+            }
+        }
+
+        // If it didn'tappear in the list, it is the hottest color.
+        return colorList.get(colors - 1);
+    }
+
+    protected void revalidate() {
+        // Handle the default logarithmic revalidation.
+        super.revalidate();
+
+        // Redistribute the lists.
+        scaleList.clear();
+
+        // We need at least colors to make a scale - otherwise, the
+        // special cases handle the color.
+        int colors = colorList.size();
+        if (colors < 2) {
+            return;
+        }
+
+        // Otherwise, define the list variables.
+        double sStep;
+        double sMin;
+        if (linear) {
+            sStep = (max - min) / (colors - 1);
+            sMin = min;
+        } else {
+            sStep = (lMax - lMin) / (colors - 1);
+            sMin = lMin;
+        }
+        double sMax = sMin + sStep;
+
+        // Generate a list of scales.
+        for (int i = 0; i < (colors - 1); i++) {
+            // Make and add a scale.
+            GradientScale s = new GradientScale();
+            s.setMinimum(sMin);
+            s.setMaximum(sMax);
+            s.setColdColor(colorList.get(i));
+            s.setHotColor(colorList.get(i + 1));
+            scaleList.add(s);
+
+            // Update the min/max.
+            sMin = sMax;
+            sMax += sStep;
+        }
+    }
+
+    /**
+     * <b>makeRainboowScale</b><br/>
+     * <br/>
+     * <code>public static <b>makeRainbowScale</b>(double minimum, double maximum)</code>
+     * <br/>
+     * <br>
+     * Creates a <code>MultiGradientScale</code> that maps values from purple,
+     * to blue, to cyan, to green, to yellow, and to red at the hottest.
+     * 
+     * @param minimum
+     *            - The lowet mapped value.
+     * @param maximum
+     *            - The highest mapped value.
+     * @return Returns the rainbow color mapping scale.
+     **/
+    public static MultiGradientScale makeRainbowScale(double minimum,
+            double maximum) {
+        int str = 165;
+        Color purple = new Color(55, 0, 55);
+        Color blue = new Color(0, 0, str);
+        Color cyan = new Color(0, str, str);
+        Color green = new Color(0, str, 0);
+        Color yellow = new Color(str, str, 0);
+        Color red = new Color(str, 0, 0);
+
+        MultiGradientScale mgs = new MultiGradientScale();
+        mgs.addColor(purple);
+        mgs.addColor(blue);
+        mgs.addColor(cyan);
+        mgs.addColor(green);
+        mgs.addColor(yellow);
+        mgs.addColor(red);
+        mgs.setMinimum(minimum);
+        mgs.setMaximum(maximum);
+
+        return mgs;
+    }
+}

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
Viewer.java added at 122
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Viewer.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Viewer.java	2014-01-28 22:47:39 UTC (rev 122)
@@ -0,0 +1,223 @@
+package org.hps.monitoring.ecal;
+
+import java.awt.Dimension;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.io.IOException;
+
+import javax.swing.JFrame;
+
+/**
+ * The class <code>Viewer</code> handles initialization of the calorimeter panel
+ * with the proper settings, provides a window for it to live in, and feeds it
+ * events.
+ * 
+ * @author Kyle McCarty
+ **/
+public class Viewer extends JFrame {
+    // Java-suggested variable.
+    private static final long serialVersionUID = -2022819652687941812L;
+    // The calorimeter panel.
+    private static final EcalPanel ecalPanel = new EcalPanel(46, 11);
+    // The event data reader.
+    private EventManager em = null;
+
+    /**
+     * <b>Viewer</b><br/>
+     * <br/>
+     * <code>public <b>Viewer</b>()</code><br/>
+     * <br/>
+     * Initializes the viewer window and calorimeter panel.
+     **/
+    public Viewer() {
+        // Initialize the viewer window
+        super();
+        setTitle("HPS Ecal Cluster Viewer");
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        setPreferredSize(new Dimension(1060, 600));
+        setMinimumSize(new Dimension(1060, 400));
+        setLayout(null);
+
+        // Set the scaling settings.
+        ecalPanel.setMinimum(0.001);
+        ecalPanel.setMaximum(3000);
+        ecalPanel.setScalingLogarithmic();
+
+        // Disable the crystals in the ecal panel along the beam gap.
+        for (int i = -23; i < 24; i++) {
+            ecalPanel.setCrystalEnabled(getPanelX(i), 5, false);
+            if (i > -11 && i < -1) {
+                ecalPanel.setCrystalEnabled(getPanelX(i), 4, false);
+                ecalPanel.setCrystalEnabled(getPanelX(i), 6, false);
+            }
+        }
+
+        // Make a key listener to change events.
+        addKeyListener(new EcalListener());
+
+        // Add the ecal pane
+        add(ecalPanel);
+
+        // Add a listener to update everything when the window changes size
+        addComponentListener(new ResizeListener());
+    }
+
+    public void setSize(int width, int height) {
+        super.setSize(width, height);
+        resize();
+    }
+
+    public void setSize(Dimension d) {
+        setSize(d.width, d.height);
+    }
+
+    /**
+     * <b>setDataSouce</b><br/>
+     * <br/>
+     * <code>public void <b>setDataSource</b>(String filepath)</code><br/>
+     * <br/>
+     * Sets the viewer to read from the indicated data source.
+     * 
+     * @param filepath
+     *            - The full path to the desired data file.
+     * @throws IOException
+     *             Occurs when there is an error opening the data file.
+     **/
+    public void setDataSource(String filepath) throws IOException {
+        em = new EventManager(filepath);
+    }
+
+    /**
+     * <b>displayNextEvent</b><br/>
+     * <br/>
+     * <code>public void <b>displayNextEvent</b>()</code><br/>
+     * <br/>
+     * Feeds the calorimeter panel the data from the next event.
+     * 
+     * @throws IOException
+     *             Occurs when there is an issue with reading the data file.
+     **/
+    public void displayNextEvent() throws IOException {
+        // Clear the ecal panel.
+        ecalPanel.clearCrystals();
+
+        // If there is no data source, we can not do anything.
+        if (em == null) {
+            return;
+        }
+
+        // Otherwise, get the next event.
+        em.readEvent();
+
+        // Display it.
+        for (EcalHit h : em.getHits()) {
+            int ix = getPanelX(h.getX());
+            int iy = getPanelY(h.getY());
+            ecalPanel.addCrystalEnergy(ix, iy, h.getEnergy());
+        }
+        for (Datum d : em.getClusters()) {
+            int ix = getPanelX(d.getX());
+            int iy = getPanelY(d.getY());
+            ecalPanel.setCrystalCluster(ix, iy, true);
+        }
+
+        // Redraw the ecal panel.
+        ecalPanel.redraw();
+    }
+
+    /**
+     * <b>resize</b><br/>
+     * <br/>
+     * <code>private void <b>resize</b>()</code><br/>
+     * <br/>
+     * Handles proper resizing of the window and its components.
+     **/
+    private void resize() {
+        // Size and position the calorimeter display
+        ecalPanel.setLocation(0, 0);
+        ecalPanel.setSize(getContentPane().getSize());
+    }
+
+    /**
+     * <b>getPanelX</b><br/>
+     * <br/>
+     * <code>private int <b>getPanelX</b>(int ecalX)</code><br/>
+     * <br/>
+     * Converts the lcsim x-coordinate to the calorimeter panel's coordinate
+     * system.
+     * 
+     * @param ecalX
+     *            - An lcsim calorimeter x-coordinate.
+     * @return Returns the x-coordinate in the calorimeter panel's coordinate
+     *         system as an <code>int</code>.
+     **/
+    private int getPanelX(int ecalX) {
+        if (ecalX <= 0) {
+            return ecalX + 23;
+        } else {
+            return ecalX + 22;
+        }
+    }
+
+    /**
+     * <b>getPanelY</b><br/>
+     * <br/>
+     * <code>private int <b>getPanelY</b>(int ecalY)</code><br/>
+     * <br/>
+     * Converts the lcsim y-coordinate to the calorimeter panel's coordinate
+     * system.
+     * 
+     * @param ecalY
+     *            - An lcsim calorimeter y-coordinate.
+     * @return Returns the y-coordinate in the calorimeter panel's coordinate
+     *         system as an <code>int</code>.
+     **/
+    private int getPanelY(int ecalY) {
+        return 5 - ecalY;
+    }
+
+    /**
+     * The <code>EcalListener</code> class binds the enter key to the
+     * <code>displayNextEvent</code> method.
+     **/
+    private class EcalListener implements KeyListener {
+        public void keyPressed(KeyEvent e) {
+        }
+
+        public void keyReleased(KeyEvent e) {
+            // If enter was pressed, go to the next event.
+            if (e.getKeyCode() == 10) {
+                try {
+                    displayNextEvent();
+                } catch (IOException ex) {
+                    System.err.println(ex.getMessage());
+                    System.exit(1);
+                }
+            }
+        }
+
+        public void keyTyped(KeyEvent e) {
+        }
+    }
+
+    /**
+     * The <code>ResizeListener</code> class ensures that the components remain
+     * at the correct size and location when the window is resized.
+     **/
+    private class ResizeListener implements ComponentListener {
+        public void componentResized(ComponentEvent e) {
+            resize();
+        }
+
+        public void componentHidden(ComponentEvent e) {
+        }
+
+        public void componentMoved(ComponentEvent e) {
+        }
+
+        public void componentShown(ComponentEvent e) {
+        }
+    }
+}
SVNspam 0.1