package nl.tudelft.simulation.jstats.charts.boxAndWhisker;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import nl.tudelft.simulation.event.EventInterface;
import nl.tudelft.simulation.event.EventListenerInterface;
import nl.tudelft.simulation.jstats.statistics.Tally;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
/**
* The Summary chart class defines a summary chart.
* (c) copyright 2002-2005 Delft University of Technology , the
* Netherlands.
* See for project information www.simulation.tudelft.nl
* License of use: Lesser General Public License (LGPL) , no
* warranty.
* @version $Revision: 1.1 $ $Date: 2010/08/10 11:39:06 $
* @author Peter Jacobs
* @author Alexander Verbraeck
*/
public class BoxAndWhiskerPlot extends Plot implements EventListenerInterface
{
/** serialversionUId. */
private static final long serialVersionUID = 1L;
/** BORDER_SIZE refers to the width of the border on the panel. */
public static final short BORDER_SIZE = 50;
/** PLOT_TYPE refers to the plot type. */
public static final String PLOT_TYPE = "SUMMARY_PLOT";
/** FONT defines the font of the plot. */
public static final Font FONT = new Font("SansSerif", Font.PLAIN, 10);
/** TITLE_FONT defines the font of the plot. */
public static final Font TITLE_FONT = new Font("SansSerif", Font.BOLD, 15);
/** target is the tally to represent. */
protected Tally[] tallies = new Tally[0];
/** formatter formats the text. */
protected NumberFormat formatter = NumberFormat.getInstance();
/** the confidenceInterval. */
protected double confidenceInterval = 0.05;
/**
* constructs a new BoxAndWhiskerPlot.
*/
public BoxAndWhiskerPlot()
{
super();
}
/**
* adds a tally to the array of targets;
* @param tally the tally to be summarized
*/
public synchronized void add(final Tally tally)
{
tally.addListener(this, Tally.SAMPLE_MEAN_EVENT, false);
List list = new ArrayList(Arrays.asList(this.tallies));
list.add(tally);
this.tallies = list.toArray(new Tally[list.size()]);
}
/** {@inheritDoc} */
@Override
public String getPlotType()
{
return PLOT_TYPE;
}
/** {@inheritDoc} */
@Override
public void notify(final EventInterface event)
{
this.notifyListeners(new PlotChangeEvent(this));
}
/** ************ PRIVATE METHODS *********************** */
/**
* computes the extent of the targets
* @param tallies the range of tallies
* @return double[min,max]
*/
private static double[] extent(final Tally[] tallies)
{
double[] result = {Double.MAX_VALUE, -Double.MAX_VALUE};
for (int i = 0; i < tallies.length; i++)
{
if (tallies[i].getMin() < result[0])
{
result[0] = tallies[i].getMin();
}
if (tallies[i].getMax() > result[1])
{
result[1] = tallies[i].getMax();
}
}
return result;
}
/**
* determines the borders on the left and right side of the tally
* @param g2 the graphics object
* @param context the context
* @param tallyArray tallies
* @return double[] the extent
*/
private double[] borders(final Graphics2D g2, final FontRenderContext context, final Tally[] tallyArray)
{
double[] result = {0, 0};
for (int i = 0; i < tallyArray.length; i++)
{
double left =
g2.getFont().getStringBounds(this.formatter.format(tallyArray[i].getMin()), context).getWidth();
double rigth =
g2.getFont().getStringBounds(this.formatter.format(tallyArray[i].getMax()), context).getWidth();
if (left > result[0])
{
result[0] = left;
}
if (rigth > result[1])
{
result[1] = rigth;
}
}
result[0] = result[0] + 3;
result[1] = result[1] + 3;
return result;
}
/**
* returns the bounding box
* @param word the word
* @param context the context
* @return Rectangle2D the bounds
*/
private Rectangle2D getBounds(final String word, final FontRenderContext context)
{
return FONT.getStringBounds(word, context);
}
/**
* fills a rectangle
* @param g2 the graphics object
* @param rectangle the area
* @param color the color
*/
private void fillRectangle(final Graphics2D g2, final Rectangle2D rectangle, final Color color)
{
g2.setColor(color);
g2.fillRect((int) rectangle.getX(), (int) rectangle.getY(), (int) rectangle.getWidth(),
(int) rectangle.getHeight());
}
/**
* paints a tally
* @param g2 the graphics object
* @param rectangle the rectangle on which to paint
* @param tally the tally
* @param leftX the lowest real value
* @param leftBorder the left border
* @param scale the scale
*/
private void paintTally(final Graphics2D g2, final Rectangle2D rectangle, final Tally tally, final double leftX,
final double leftBorder, final double scale)
{
this.fillRectangle(g2, rectangle, Color.WHITE);
g2.setColor(Color.BLACK);
g2.setFont(TITLE_FONT);
g2.drawString(
tally.getDescription(),
(int) Math.round(leftBorder + 0.5 * (rectangle.getWidth() - leftBorder - 20) - 0.5
* this.getBounds(tally.getDescription(), g2.getFontRenderContext()).getWidth()),
25 + (int) rectangle.getY());
g2.setFont(FONT);
g2.drawRect((int) rectangle.getX() - 1, (int) rectangle.getY() - 1, (int) rectangle.getWidth() + 2,
(int) rectangle.getHeight() + 2);
int tallyMin = (int) Math.round(rectangle.getX() + (tally.getMin() - leftX) * scale + leftBorder);
int tallyMax = (int) Math.round(rectangle.getX() + (tally.getMax() - leftX) * scale + leftBorder);
int middle = (int) Math.round(rectangle.getY() + 0.5 * rectangle.getHeight());
String label = this.formatter.format(tally.getMin());
g2.drawString(label,
(int) Math.round(tallyMin - 3 - this.getBounds(label, g2.getFontRenderContext()).getWidth()),
(int) Math.round(middle + 0.5 * this.getBounds(label, g2.getFontRenderContext()).getHeight()));
label = this.formatter.format(tally.getMax());
g2.drawString(label, tallyMax + 3,
(int) Math.round(middle + 0.5 * this.getBounds(label, g2.getFontRenderContext()).getHeight()));
g2.drawLine(tallyMin, middle + 6, tallyMin, middle - 6);
g2.drawLine(tallyMin, middle, tallyMax, middle);
g2.drawLine(tallyMax, middle + 6, tallyMax, middle - 6);
double[] confidence = tally.getConfidenceInterval(this.confidenceInterval);
int middleX = (int) Math.round((tally.getSampleMean() - leftX) * scale + tallyMin);
g2.fillRect(middleX, middle - 6, 2, 12);
label = this.formatter.format(tally.getSampleMean());
Rectangle2D bounds = this.getBounds(label, g2.getFontRenderContext());
g2.drawString(label, (int) Math.round(middleX - 0.5 * bounds.getWidth()), Math.round(middle - 8));
if (confidence != null)
{
int confX = (int) Math.round((confidence[0] - leftX) * scale + tallyMin);
int confWidth = (int) Math.round((confidence[1] - confidence[0]) * scale);
g2.fillRect(confX, middle - 2, confWidth, 4);
label = this.formatter.format(confidence[0]);
bounds = this.getBounds(label, g2.getFontRenderContext());
g2.drawString(label, (int) Math.round(confX - bounds.getWidth()),
(int) Math.round(middle + 8 + bounds.getHeight()));
label = this.formatter.format(confidence[1]);
bounds = this.getBounds(label, g2.getFontRenderContext());
g2.drawString(label, Math.round(confX + confWidth), (int) Math.round(middle + 8 + bounds.getHeight()));
}
}
/** {@inheritDoc} */
@Override
public void draw(final Graphics2D g2, final Rectangle2D rectangle, final Point2D point, final PlotState plotState,
final PlotRenderingInfo plotRenderingInfo)
{
// TODO Point2D point not in use yet -- look up meaning
g2.setBackground(Color.WHITE);
double height = Math.min(rectangle.getHeight() / this.tallies.length * 1.0, rectangle.getHeight());
double[] extent = BoxAndWhiskerPlot.extent(this.tallies);
double[] border = this.borders(g2, g2.getFontRenderContext(), this.tallies);
double scale = (0.85 * rectangle.getWidth() - 10 - border[0] - border[1]) / ((extent[1] - extent[0]) * 1.0);
for (int i = 0; i < this.tallies.length; i++)
{
g2.setFont(FONT);
Rectangle2D area =
new Rectangle2D.Double(rectangle.getX() + 0.15 * rectangle.getWidth(), rectangle.getY() + i
* height + 3, 0.85 * rectangle.getWidth() - 10, 0.75 * height - 3);
this.paintTally(g2, area, this.tallies[i], extent[0], border[0], scale);
}
}
/**
* Returns the confidence interval of the BoxAndWhiskerPlot
* @return the confidence interval of the BoxAndWhiskerPlot
*/
public double getConfidenceInterval()
{
return this.confidenceInterval;
}
/**
* sets the confidence interval of the plot. The default value = 0.05 (=5%)
* @param confidenceInterval the confidence interval
*/
public void setConfidenceInterval(final double confidenceInterval)
{
this.confidenceInterval = confidenceInterval;
}
}