/* CheckSignalDisplay.java created 2010-10-24
*
*/
package org.signalml.app.view.document.monitor.signalchecking;
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.signalml.app.view.montage.visualreference.VisualReferenceBin;
import org.signalml.app.view.montage.visualreference.VisualReferenceDisplay;
import org.signalml.app.view.montage.visualreference.VisualReferenceModel;
import org.signalml.app.view.montage.visualreference.VisualReferenceSourceChannel;
/**
* A component which - based on information from a {@link VisualReferenceModel} object
* and a {@link GenericAmplifierDiagnosis} object - draws the state of each channel.
*
* @author Tomasz Sawicki
*/
public class CheckSignalDisplay extends VisualReferenceDisplay {
/**
* Space between two bars or bar and a channel.
*/
public static final int BAR_PADDING = 12;
/**
* Width of a bar.
*/
public static final int BAR_WIDTH = 20;
/**
* Height of a bar.
*/
public static final int BAR_HEIGHT = 50;
/**
* Bar border size.
*/
public static final int BAR_BORDER = 2;
/**
* Text space between text and bar.
*/
public static final int TEXT_SPACE = 3;
/**
* How much the bar is moved vertically.
*/
public static final int BAR_VERTICAL_OFFSET = -14;
/**
* Channel info used when drawing the channel state.
*/
private List<HashMap<String, ChannelState>> channels;
/**
* Default construcot only calls super.
*
* @param model super parameter
*/
public CheckSignalDisplay(VisualReferenceModel model) {
super(model);
}
/**
* Sets the given channels state, then repaints the component
* to visualize any possible changes.
*
* @param channels HashMap representing channels state
*/
public void setChannelsState(List<HashMap<String, ChannelState>> channels) {
this.channels = channels;
this.repaint();
}
/**
* Draws bin contents based on the channel state.
*
* @param bin {@link VisualReferenceBin} object
* @param g {@link Graphics2D} object
*/
@Override
protected void paintBinContents(VisualReferenceBin bin, Graphics2D g) {
HashMap<String, Boolean> validData = null;
HashMap<String, List<AdditionalChannelData>> additionalData = null;
boolean stateKnown = isStateKnown();
if (stateKnown) {
validData = getValidData();
additionalData = getAdditionalData();
}
Iterator<VisualReferenceSourceChannel> it = bin.iterator();
VisualReferenceSourceChannel channel;
if (!stateKnown) {
while (it.hasNext()) {
channel = it.next();
paintUnknownChannel(channel, g);
}
} else {
while (it.hasNext()) {
channel = it.next();
String channelLabel = channel.getLabel();
Boolean valid = validData.get(channelLabel);
if (valid != null) {
//ZERO and ONE channels are not in the validData
//and should not be analyzed.
List<AdditionalChannelData> data = additionalData.get(channel.getLabel());
paintKnownChannel(channel, g, valid, data);
}
}
}
}
/**
* Draws an unknown channel - white fill and red thick outline.
*
* @param channel {@link VisualReferenceSourceChannel} object
* @param g {@link Graphics2D} object
*/
private void paintUnknownChannel(VisualReferenceSourceChannel channel, Graphics2D g) {
paintGivenChannel(channel.getLabel(), 0, channel.getShape(), channel.getOutlineShape(), Color.WHITE, Color.RED, true, g);
}
/**
* Draws a known channel - green fill and black thin outline if valid, red fill
* and black thick outline if invalid. Also draws the additional data.
*
* @param channel {@link VisualReferenceSourceChannel} object
* @param g {@link Graphics2D} object
* @param valid whether channel is valid
* @param additionalData list of additional data to draw
*/
private void paintKnownChannel(VisualReferenceSourceChannel channel, Graphics2D g, boolean valid, List<AdditionalChannelData> additionalData) {
paintGivenChannel(channel.getLabel(), 0, channel.getShape(), channel.getOutlineShape(), (valid) ? Color.GREEN : Color.RED, Color.BLACK, false, g);
if (!additionalData.isEmpty()) {
int startX = channel.getLocation().x + VisualReferenceSourceChannel.CIRCLE_DIAMETER + BAR_PADDING;
int startY = channel.getLocation().y + BAR_VERTICAL_OFFSET + (VisualReferenceSourceChannel.CIRCLE_DIAMETER - BAR_HEIGHT) / 2;
Point location = new Point(startX, startY);
for (AdditionalChannelData data : additionalData) {
paintAdditionalDataBar(location, g, data);
startX += BAR_WIDTH + BAR_PADDING;
location = new Point(startX, startY);
}
}
}
/**
* Paints a value bar.
* @param location location of the bar
* @param g {@link Graphics2D} object
* @param data data to draw
*/
private void paintAdditionalDataBar(Point location, Graphics2D g, AdditionalChannelData data) {
paintBar(location, g, data);
paintLabels(location, g, data);
}
/**
* Paints a value bar.
* @param location location of the bar
* @param g {@link Graphics2D} object
* @param data data to draw
*/
private void paintBar(Point location, Graphics2D g, AdditionalChannelData data) {
int validHeight = (int)(BAR_HEIGHT * ((data.getLimit() - data.getMin()) / (data.getMax() - data.getMin())));
int validStart = BAR_HEIGHT - validHeight;
int currentHeight = (int)((data.getCurrent() < data.getMax()) ? (BAR_HEIGHT * ((data.getCurrent() - data.getMin()) / (data.getMax() - data.getMin()))) : BAR_HEIGHT);
int currentEnd = BAR_HEIGHT - currentHeight;
g.setPaint(Color.RED);
g.fillRect(location.x, location.y, BAR_WIDTH, BAR_HEIGHT - validHeight);
g.setPaint(new GradientPaint(location.x, location.y + validStart, Color.RED, location.x, location.y + BAR_HEIGHT, Color.GREEN));
g.fillRect(location.x, location.y + validStart, BAR_WIDTH, validHeight);
g.setPaint(Color.WHITE);
g.fillRect(location.x, location.y, BAR_WIDTH, currentEnd);
g.setPaint(Color.BLACK);
g.setStroke(new BasicStroke(BAR_BORDER));
g.drawRect(location.x, location.y, BAR_WIDTH, BAR_HEIGHT);
g.drawLine(location.x, location.y + validStart, location.x + BAR_WIDTH, location.y + validStart);
}
/**
* Paints bar labels: max, min, current, and method name
* @param location location of the bar
* @param g {@link Graphics2D} object
* @param data data to draw
*/
private void paintLabels(Point location, Graphics2D g, AdditionalChannelData data) {
g.setFont(channelLabelFont);
String toWrite = formatDouble(data.getMax());
Rectangle2D rect = channelLabelFontMetrics.getStringBounds(toWrite, g);
int textHeight = (int)rect.getHeight();
int textWidth = (int)(rect.getWidth());
int offset = (BAR_WIDTH - textWidth) / 2;
int textX = location.x + offset;
int textY = location.y - TEXT_SPACE;
paintLabelWithBackground(toWrite, textX, textY, textWidth, textHeight, g);
toWrite = formatDouble(data.getCurrent());
rect = channelLabelFontMetrics.getStringBounds(toWrite, g);
textHeight = (int)rect.getHeight();
textWidth = (int)rect.getWidth();
offset = (BAR_WIDTH - textWidth) / 2;
textX = location.x + offset;
textY = textY - textHeight;
paintLabelWithBackground(toWrite, textX, textY, textWidth, textHeight, g);
toWrite = formatDouble(data.getMin());
rect = channelLabelFontMetrics.getStringBounds(toWrite, g);
textHeight = (int)rect.getHeight();
textWidth = (int)rect.getWidth();
offset = (BAR_WIDTH - textWidth) / 2;
textX = location.x + offset;
textY = location.y + textHeight + BAR_HEIGHT + TEXT_SPACE;
paintLabelWithBackground(toWrite, textX, textY, textWidth, textHeight, g);
}
/**
* Paints given String with white background.
* @param toWrite String to write
* @param textX x position
* @param textY y position
* @param textWidth text width
* @param textHeight text height
* @param g {@link Graphics2D} object
*/
private void paintLabelWithBackground(String toWrite, int textX, int textY, int textWidth, int textHeight, Graphics2D g) {
g.setPaint(Color.WHITE);
g.fillRect(textX, textY - textHeight, textWidth, textHeight);
g.setPaint(Color.BLACK);
g.drawString(toWrite, textX, textY);
}
/**
* Formats given double.
*
* @param input input value
* @return input as {@link String}
*/
private String formatDouble(double input) {
NumberFormat formatter;
if (input < 1e6)
formatter = new DecimalFormat("00.00");
else
formatter = new DecimalFormat("0E00");
return formatter.format(input);
}
/**
* Whether the state is known - all diagnosis objects returned non-null value.
*
* @return true if the state is known, false otherwise
*/
private boolean isStateKnown() {
for (Object diagnosisResult : channels)
if (diagnosisResult == null)
return false;
return true;
}
/**
* For each channel, checks if that channel is valid.
*
* @return valid data on each channel
*/
private HashMap<String, Boolean> getValidData() {
HashMap<String, Boolean> validData = new HashMap<String, Boolean>();
Set<String> channelNames = channels.get(0).keySet();
for (String channel : channelNames) {
boolean valid = true;
for (HashMap<String, ChannelState> diagnosisResult : channels) {
if (!diagnosisResult.get(channel).isValid()) {
valid = false;
break;
}
}
validData.put(channel, valid);
}
return validData;
}
/**
* Gets list additional channel data for each channel.
*
* @return additional data
*/
private HashMap<String, List<AdditionalChannelData>> getAdditionalData() {
HashMap<String, List<AdditionalChannelData>> additionalData =
new HashMap<String, List<AdditionalChannelData>>();
Set<String> channelNames = channels.get(0).keySet();
for (String channel : channelNames) {
List<AdditionalChannelData> channelsAdditionalData = new ArrayList<AdditionalChannelData>();
for (HashMap<String, ChannelState> diagnosisResult : channels) {
AdditionalChannelData currentData = diagnosisResult.get(channel).getAdditionalChannelData();
if (currentData != null)
channelsAdditionalData.add(currentData);
}
additionalData.put(channel, channelsAdditionalData);
}
return additionalData;
}
}