sim.util.media.chart.BoxPlotGenerator Maven / Gradle / Ivy
Show all versions of mason Show documentation
/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.util.media.chart;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import sim.util.gui.*;
// From JFreeChart
import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.event.*;
import org.jfree.chart.plot.*;
import org.jfree.data.statistics.*;
import org.jfree.data.general.*;
import org.jfree.chart.title.*;
import org.jfree.data.xy.*;
import org.jfree.chart.renderer.category.*;
import org.jfree.data.*;
import org.jfree.data.category.*;
import org.jfree.chart.labels.*;
// from iText (www.lowagie.com/iText/)
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
/* // looks like we'll have to move to these soon
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
*/
/**
BoxPlotGenerator is a ChartGenerator which displays a BoxPlot using the JFreeChart library.
The generator uses the HistoramDataset as its dataset, which holds BoxPlot elements consisting of
a name, an array of doubles (the samples), and an integer (the number of bins).
representing a time series displayed on the chart. You add series to the generator with the addSeries
method.
BoxPlotChartGenerator creates attributes components in the form of BoxPlotAttributes, which work with
the generator to properly update the chart to reflect changes the user has made to its display.
*/
public class BoxPlotGenerator extends ChartGenerator
{
/** The global attributes range axis field. */
PropertyField yLabel;
/** The global attributes domain axis field. */
PropertyField xLabel;
/** The global attributes logarithmic range axis check box. */
JCheckBox yLog;
JCheckBox mean;
JCheckBox median;
NumberTextField maximumWidthField;
public void setMaximumWidth(double value) { maximumWidthField.setValue(maximumWidthField.newValue(value)); }
public double getMaximumWidth() { return maximumWidthField.getValue(); }
public void setYAxisLogScaled(boolean isLogScaled){yLog.setSelected(isLogScaled);}
public boolean isYAxisLogScaled(){return yLog.isSelected();}
public void setMeanShown(boolean val){mean.setSelected(val);}
public boolean isMeanShown(){return mean.isSelected();}
public void setMedianShown(boolean val){median.setSelected(val);}
public boolean isMedianShown(){return median.isSelected();}
/** Returns the name of the Y Axis label. */
public String getYAxisLabel()
{
return ((CategoryPlot)(chart.getPlot())).getRangeAxis().getLabel();
}
/** Returns the name of the X Axis label. */
public String getXAxisLabel()
{
return ((CategoryPlot)(chart.getPlot())).getDomainAxis().getLabel();
}
public Dataset getSeriesDataset() { return ((CategoryPlot)(chart.getPlot())).getDataset(); }
public void setSeriesDataset(Dataset obj)
{
((CategoryPlot)(chart.getPlot())).setDataset((DefaultBoxAndWhiskerCategoryDataset)obj);
if (invalidChartTitle != null)
setInvalidChartTitle(null);
}
public int getSeriesCount()
{
DefaultBoxAndWhiskerCategoryDataset dataset = (DefaultBoxAndWhiskerCategoryDataset)(getSeriesDataset());
return dataset.getRowCount();
}
public void removeSeries(int index)
{
super.removeSeries(index);
update();
}
public void moveSeries(int index, boolean up)
{
super.moveSeries(index, up);
update();
}
protected void buildChart()
{
DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset();
// we build the chart manually rather than using ChartFactory
// because we need to customize the getDataRange method below
CategoryAxis categoryAxis = new CategoryAxis("");
NumberAxis valueAxis = new NumberAxis("Untitled Y Axis");
valueAxis.setAutoRangeIncludesZero(false);
BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer();
renderer.setBaseToolTipGenerator(new BoxAndWhiskerToolTipGenerator());
CategoryPlot plot = new CategoryPlot(dataset, categoryAxis, valueAxis, renderer)
{
// Customizing this method in order to provide a bit of
// vertical buffer. Otherwise the bar chart box gets drawn
// slightly off-chart, which looks really bad.
public Range getDataRange(ValueAxis axis)
{
Range range = super.getDataRange(axis);
if (range == null) return null;
final double EXTRA_PERCENTAGE = 0.02;
return Range.expand(range, EXTRA_PERCENTAGE, EXTRA_PERCENTAGE);
}
};
chart = new JFreeChart("Untitled Chart", JFreeChart.DEFAULT_TITLE_FONT, plot, false);
ChartFactory.getChartTheme().apply(chart);
chart.setAntiAlias(true);
chartPanel = buildChartPanel(chart);
setChartPanel(chartPanel);
// this must come last because the chart must exist for us to set its dataset
setSeriesDataset(dataset);
}
ArrayList buildList(double[] vals)
{
ArrayList list = new ArrayList();
for(int i = 0; i < vals.length; i++)
list.add(new Double(vals[i]));
return list;
}
protected void update()
{
// We have to rebuild the dataset from scratch (deleting and replacing it) because JFreeChart's
// BoxPlot facility doesn't have a way to remove or move elements. Stupid stupid stupid.
SeriesAttributes[] sa = getSeriesAttributes();
DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset();
for(int i=0; i < sa.length; i++)
{
BoxPlotSeriesAttributes attributes = (BoxPlotSeriesAttributes)(sa[i]);
double[][] values = attributes.getValues();
String[] labels = attributes.getLabels();
//UniqueString series = new UniqueString(attributes.getSeriesName());
String series = attributes.getSeriesName();
for(int j = 0; j < values.length; j++)
{
dataset.add(buildList(values[j]), series, labels[j]);
}
}
((BoxAndWhiskerRenderer)(((CategoryPlot)(chart.getPlot())).getRenderer())).setMaximumBarWidth(getMaximumWidth());
setSeriesDataset(dataset);
}
public SeriesAttributes addSeries(double[] vals, String name, SeriesChangeListener stopper)
{
double[][] vvals = new double[1][];
vvals[0] = vals;
return addSeries(vvals, name, stopper);
}
/** Adds a series, plus a (possibly null) SeriesChangeListener which will receive a single
event if/when the series is deleted from the chart by the user. Returns the series attributes. */
SeriesAttributes addSeries(double[][] vals, String name, SeriesChangeListener stopper)
{
if (vals == null || vals.length == 0) vals = new double[0][0];
int i = getSeriesCount();
// need to have added the dataset BEFORE calling this since it'll try to change the name of the series
BoxPlotSeriesAttributes csa = new BoxPlotSeriesAttributes(this, name, i, vals, stopper);
seriesAttributes.add(csa);
revalidate(); // display the new series panel
update();
// won't update properly unless I force it here by letting all the existing scheduled events to go through. Dumb design. :-(
SwingUtilities.invokeLater(new Runnable() { public void run() { update(); } });
return csa;
}
public SeriesAttributes addSeries(double[][] vals, String[] labels, String name, SeriesChangeListener stopper)
{
if (vals == null || vals.length == 0) vals = new double[0][0];
int i = getSeriesCount();
// need to have added the dataset BEFORE calling this since it'll try to change the name of the series
BoxPlotSeriesAttributes csa = new BoxPlotSeriesAttributes(this, name, i, vals, labels, stopper);
seriesAttributes.add(csa);
revalidate(); // display the new series panel
update();
// won't update properly unless I force it here by letting all the existing scheduled events to go through. Dumb design. :-(
SwingUtilities.invokeLater(new Runnable() { public void run() { update(); } });
return csa;
}
/** Sets the name of the Y Axis label. */
public void setYAxisLabel(String val)
{
CategoryPlot xyplot = (CategoryPlot)(chart.getPlot());
xyplot.getRangeAxis().setLabel(val);
xyplot.axisChanged(new AxisChangeEvent(xyplot.getRangeAxis()));
yLabel.setValue(val);
}
/** Sets the name of the X Axis label. */
public void setXAxisLabel(String val)
{
CategoryPlot xyplot = (CategoryPlot)(chart.getPlot());
xyplot.getDomainAxis().setLabel(val);
xyplot.axisChanged(new AxisChangeEvent(xyplot.getDomainAxis()));
xLabel.setValue(val);
}
public void updateSeries(int index, double[] vals)
{
double[][] vvals = new double[1][];
vvals[0] = vals;
updateSeries(index, vvals);
}
public void updateSeries(int index, double[][] vals)
{
if (index < 0) // this happens when we're a dead chart but the inspector doesn't know
return;
if (index >= getNumSeriesAttributes()) // this can happen when we close a window if we use the BoxPlot in a display
return;
if (vals == null || vals.length == 0) vals = new double[0][0];
BoxPlotSeriesAttributes hsa = (BoxPlotSeriesAttributes)(getSeriesAttribute(index));
hsa.setValues(vals);
hsa.setLabels(null);
}
public void updateSeries(int index, double[][] vals, String[] labels)
{
if (index < 0) // this happens when we're a dead chart but the inspector doesn't know
return;
if (index >= getNumSeriesAttributes()) // this can happen when we close a window if we use the BoxPlot in a display
return;
if (vals == null || vals.length == 0) vals = new double[0][0];
if (labels == null || labels.length == 0) labels = new String[0];
if (vals.length != labels.length) // uh oh
return;
BoxPlotSeriesAttributes hsa = (BoxPlotSeriesAttributes)(getSeriesAttribute(index));
hsa.setValues(vals);
hsa.setLabels(labels);
}
protected void buildGlobalAttributes(LabelledList list)
{
// create the chart
((CategoryPlot)(chart.getPlot())).setRangeGridlinesVisible(false);
((CategoryPlot)(chart.getPlot())).setRangeGridlinePaint(new Color(200,200,200));
xLabel = new PropertyField()
{
public String newValue(String newValue)
{
setXAxisLabel(newValue);
getChartPanel().repaint();
return newValue;
}
};
xLabel.setValue(getXAxisLabel());
list.add(new JLabel("X Label"), xLabel);
yLabel = new PropertyField()
{
public String newValue(String newValue)
{
setYAxisLabel(newValue);
getChartPanel().repaint();
return newValue;
}
};
yLabel.setValue(getYAxisLabel());
list.add(new JLabel("Y Label"), yLabel);
yLog = new JCheckBox();
yLog.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e)
{
if(yLog.isSelected())
{
LogarithmicAxis logAxis = new LogarithmicAxis(yLabel.getValue());
logAxis.setStrictValuesFlag(false);
((CategoryPlot)(chart.getPlot())).setRangeAxis(logAxis);
}
else
((CategoryPlot)(chart.getPlot())).setRangeAxis(new NumberAxis(yLabel.getValue()));
}
});
list.add(new JLabel("Y Log Axis"), yLog);
final JCheckBox ygridlines = new JCheckBox();
ygridlines.setSelected(false);
ItemListener il = new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
if (e.getStateChange() == ItemEvent.SELECTED)
{
((CategoryPlot)(chart.getPlot())).setRangeGridlinesVisible(true);
}
else
{
((CategoryPlot)(chart.getPlot())).setRangeGridlinesVisible(false);
}
}
};
ygridlines.addItemListener(il);
// JFreeChart's Box Plots look awful when wide because the mean
// circle is based on the width of the bar to the exclusion of all
// else. So I've restricted the width to be no more than 0.4, and 0.1
// is the suggested default.
final double INITIAL_WIDTH = 0.1;
final double MAXIMUM_RATIONAL_WIDTH = 0.4;
maximumWidthField = new NumberTextField(INITIAL_WIDTH, 2.0, 0)
{
public double newValue(double newValue)
{
if (newValue <= 0.0 || newValue > MAXIMUM_RATIONAL_WIDTH)
newValue = currentValue;
((BoxAndWhiskerRenderer)(((CategoryPlot)(chart.getPlot())).getRenderer())).setMaximumBarWidth(newValue);
//update();
return newValue;
}
};
list.addLabelled("Max Width",maximumWidthField);
Box box = Box.createHorizontalBox();
box.add(new JLabel(" Y"));
box.add(ygridlines);
box.add(Box.createGlue());
list.add(new JLabel("Y Grid Lines"), ygridlines);
mean = new JCheckBox();
mean.setSelected(true);
il = new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
BoxAndWhiskerRenderer renderer = ((BoxAndWhiskerRenderer)((CategoryPlot)(chart.getPlot())).getRenderer());
renderer.setMeanVisible(mean.isSelected());
}
};
mean.addItemListener(il);
median = new JCheckBox();
median.setSelected(true);
il = new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
BoxAndWhiskerRenderer renderer = ((BoxAndWhiskerRenderer)((CategoryPlot)(chart.getPlot())).getRenderer());
renderer.setMedianVisible(median.isSelected());
}
};
median.addItemListener(il);
list.add(new JLabel("Mean"), mean);
list.add(new JLabel("Median"), median);
final JCheckBox horizontal = new JCheckBox();
horizontal.setSelected(false);
il = new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
CategoryPlot plot = (CategoryPlot)(chart.getPlot());
if (e.getStateChange() == ItemEvent.SELECTED)
{
plot.setOrientation(PlotOrientation.HORIZONTAL);
}
else
{
plot.setOrientation(PlotOrientation.VERTICAL);
}
//updateGridLines();
}
};
horizontal.addItemListener(il);
list.add(new JLabel("Horizontal"), horizontal);
final JCheckBox whiskersUseFillColorButton = new JCheckBox();
whiskersUseFillColorButton.setSelected(false);
whiskersUseFillColorButton.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e)
{
BoxAndWhiskerRenderer renderer = ((BoxAndWhiskerRenderer)((CategoryPlot)(chart.getPlot())).getRenderer());
renderer.setUseOutlinePaintForWhiskers(!whiskersUseFillColorButton.isSelected());
}
});
box = Box.createHorizontalBox();
box.add(new JLabel(" Colored"));
box.add(whiskersUseFillColorButton);
box.add(Box.createGlue());
list.add(new JLabel("Whiskers"), box);
}
}