package com.srbenoit.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import javax.swing.JPanel;
import com.srbenoit.math.Histogram;
/**
* A panel that displays a histogram of data.
*/
public class HistogramPanel extends JPanel {
/** version number for serialization */
private static final long serialVersionUID = -1112693135693520783L;
/** the histogram data */
private final transient Histogram data;
/** the labels for the planes of the histogram */
private final transient String[] labels;
/** the line color */
private final transient Color lines;
/** the point color */
private final transient Color points;
/** the line accent color */
private final transient Color lines2;
/** the point accent color */
private final transient Color points2;
/** the grid color */
private final transient Color grid;
/** the background color */
private final transient Color back;
/** a boldface font */
protected final transient Font font;
/** the phase of the color switch */
private transient boolean phase;
/**
* Constructs a new HistogramPanel
.
*
* @param hist the histogram to display
* @param histogramLabels the labels for the histogram planes
* @param backgroundColor the background color
* @param linesColor the lines color
* @param pointsColor the points color
* @param gridColor the grid color
* @param backColor the background color
*/
public HistogramPanel(final Histogram hist, final String[] histogramLabels,
final Color backgroundColor, final Color linesColor, final Color pointsColor,
final Color gridColor, final Color backColor) {
super();
Insets insets;
int width;
if (histogramLabels.length != hist.getNumPlanes()) {
throw new IllegalArgumentException("Label length mismatch");
}
this.data = hist;
this.labels = histogramLabels.clone();
this.lines = linesColor;
this.lines2 = new Color((int) (linesColor.getRed() * 0.8),
(int) (linesColor.getGreen() * 0.8), (int) (linesColor.getBlue() * 0.8));
this.points = pointsColor;
this.points2 = new Color((int) (pointsColor.getRed() * 0.8),
(int) (pointsColor.getGreen() * 0.8), (int) (pointsColor.getBlue() * 0.8));
this.grid = gridColor;
this.back = backColor;
this.font = getFont().deriveFont(Font.BOLD, 11.0f);
setFont(this.font);
setBackground(backgroundColor);
// Compute the preferred width
insets = getInsets();
width = insets.left + insets.right + 12 + hist.getNumTimes();
setPreferredSize(new Dimension(width, 50 * hist.getNumPlanes()));
}
/**
* Gets the histogram that this panel is displaying.
*
* @return the histogram.
*/
public Histogram getData() {
return this.data;
}
/**
* Registers a tick from the owning panel, which shifts the histogram to the left one notch, and
* initializes the newly opened slot to a particular value.
*/
public void tick() {
this.data.shiftHigher();
this.phase ^= true; // NOT operation
invalidate();
repaint();
}
/**
* Draws an activity histogram.
*
* @param grx the Graphics
to which to draw
*/
@Override public void paintComponent(final Graphics grx) {
super.paintComponent(grx);
if (isEnabled()) {
synchronized (this.data) {
paintContents(grx);
}
}
}
/**
* Draws an activity histogram.
*
* @param grx the Graphics
to which to draw
*/
private void paintContents(final Graphics grx) {
Insets insets;
int height;
int heightPerPlane;
int top;
FontMetrics met;
int xPos;
int max;
int scale;
int xPix;
int yPix;
int barHeight;
boolean toggle;
super.paintComponent(grx);
insets = getInsets();
height = getHeight() - insets.top - insets.bottom;
heightPerPlane = height / this.data.getNumPlanes();
met = grx.getFontMetrics();
barHeight = heightPerPlane - met.getHeight();
// Don't draw lines unless our panel is tall enough
top = insets.top;
xPos = insets.left + 3;
for (int plane = 0; plane < this.data.getNumPlanes(); plane++) {
scale = 1;
if (barHeight > 1) {
max = maxValue(plane);
// Construct a scale to use when drawing data
while (max > barHeight) {
scale++;
max = max * (scale - 1) / scale;
}
// Draw the label with optional scale indicator.
drawLabel(grx, scale, top + met.getAscent(), plane);
// Draw a background
grx.setColor(this.back);
grx.fillRect(xPos, top + heightPerPlane - barHeight, this.data.getNumTimes() + 1,
barHeight + 1);
// Draw some grid lines, and labels
grx.setColor(this.grid);
for (int i = 0; i < barHeight; i += 10) {
yPix = top + heightPerPlane - i;
grx.drawLine(xPos, yPix, xPos + this.data.getNumTimes(), yPix);
}
// Draw the lines
xPix = xPos + this.data.getNumTimes();
for (int i = 0; i < this.data.getNumTimes(); i++) {
toggle = ((i & 0x01) == 0x01) ^ this.phase;
yPix = top + heightPerPlane - (this.data.getValue(plane, i) / scale);
grx.setColor(toggle ? this.lines : this.lines2);
grx.drawLine(xPix, top + heightPerPlane, xPix, yPix);
grx.setColor(toggle ? this.points : this.points2);
grx.drawLine(xPix, yPix, xPix, yPix);
xPix--;
}
}
top += heightPerPlane;
}
}
/**
* Finds the maximum data value for a plane.
*
* @param plane
* @return the maximum value
*/
private int maxValue(final int plane) {
int max;
int value;
max = this.data.getValue(plane, 0);
for (int i = 1; i < this.data.getNumTimes(); i++) {
value = this.data.getValue(plane, i);
if (value > max) {
max = value;
}
}
return max;
}
/**
* Draw a label at the top of the histogram.
*
* @param grx the Graphics
to which to draw
* @param scale the scale
* @param yPos the Y position at which to draw
* @param plane the plane whose label to draw
*/
private void drawLabel(final Graphics grx, final int scale, final int yPos, final int plane) {
StringBuilder str;
str = new StringBuilder(100);
str.append(this.labels[plane]);
str.append(" (");
str.append(10 * scale);
str.append(" per gridline)");
str.append(":");
grx.setColor(this.points2);
grx.drawString(str.toString(), 4, yPos);
}
}