sim.util.media.chart.HistogramGenerator Maven / Gradle / Ivy
Show all versions of mason Show documentation
/*
Copyright 2006 by Sean Luke
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.awt.geom.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
// From MASON (cs.gmu.edu/~eclab/projects/mason/)
import sim.util.gui.LabelledList;
import sim.util.gui.NumberTextField;
// From JFreeChart (jfreechart.org)
import org.jfree.data.xy.*;
import org.jfree.chart.*;
import org.jfree.chart.event.*;
import org.jfree.chart.plot.*;
import org.jfree.data.general.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
import org.jfree.data.statistics.*;
// from iText (www.lowagie.com/iText/)
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
/**
TimeSeriesChartGenerator is a ChartGenerator which displays a histogram using the JFreeChart library.
The generator uses the HistoramDataset as its dataset, which holds histogram 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.
TimeSeriesChartGenerator creates attributes components in the form of TimeSeriesAttributes, which work with
the generator to properly update the chart to reflect changes the user has made to its display.
*/
public class HistogramGenerator extends ChartGenerator
{
HistogramDataset dataset;
ArrayList stoppables = new ArrayList();
HistogramType histogramType = HistogramType.FREQUENCY;
public class HistogramSeries
{
double[] values;
int bins;
String name;
public HistogramSeries(String name, double[] values, int bins)
{ this.name = name; this.values = values; this.bins = bins; }
public void setValues(double[] v) { values = v; }
public double[] getValues() { return values; }
public void setBins(int b) { bins = b; }
public int getBins() { return bins; }
public String getName() { return name; }
public void setName(String val) { name = val; }
}
ArrayList histogramSeries = new ArrayList();
public AbstractSeriesDataset getSeriesDataset() { return dataset; }
public void removeSeries(int index)
{
// stop the inspector....
Object tmpObj = stoppables.remove(index);
if( ( tmpObj != null ) && ( tmpObj instanceof SeriesChangeListener ) )
((SeriesChangeListener)tmpObj).seriesChanged(new SeriesChangeEvent(this));
// remove from the dataset. This is very hard to do in Histograms, stupid JFreeChart design. Basicaly
// we have to make a new dataset
histogramSeries.remove(index);
XYPlot xyplot = (XYPlot)(chart.getPlot());
dataset = new HistogramDataset();
for(int i=0; i < histogramSeries.size(); i++)
{
HistogramSeries series = (HistogramSeries)(histogramSeries.get(i));
dataset.addSeries(series.getName(),series.getValues(), series.getBins());
}
xyplot.setDataset(dataset);
dataset.setType(histogramType); // It looks like the histograms reset
// remove the attribute
seriesAttributes.remove(index);
// shift all the seriesAttributes' indices down so they know where they are
Component[] c = seriesAttributes.getComponents();
for(int i = 0; i < c.length; i++) // do for just the components >= index in the seriesAttributes
{
SeriesAttributes csa = (SeriesAttributes)(c[i]);
if (i >= index)
csa.setSeriesIndex(csa.getSeriesIndex() - 1);
csa.rebuildGraphicsDefinitions(); // they've ALL just been deleted and changed, must update
}
revalidate();
}
public void moveSeries(int index, boolean up)
{
if ((index == 0 && up) || (index == histogramSeries.size()-1 && !up))
//first one can't move up, last one can't move down
return;
int delta = up? -1:1;
// move the series
histogramSeries.add(index + delta, histogramSeries.remove(index));
XYPlot xyplot = (XYPlot)(chart.getPlot());
dataset = new HistogramDataset();
for(int i=0; i < histogramSeries.size(); i++)
{
HistogramSeries series = (HistogramSeries)(histogramSeries.get(i));
dataset.addSeries(series.getName(),series.getValues(), series.getBins());
}
xyplot.setDataset(dataset);
dataset.setType(histogramType); // It looks like the histograms reset
// adjust the seriesAttributes' indices
Component[] c = seriesAttributes.getComponents();
SeriesAttributes csa;
(csa = (SeriesAttributes)c[index]).setSeriesIndex(index+delta);
csa.rebuildGraphicsDefinitions();
(csa = (SeriesAttributes)c[index+delta]).setSeriesIndex(index);
csa.rebuildGraphicsDefinitions();
seriesAttributes.remove(index+delta);
//seriesAttributes.add((SeriesAttributes)(c[index+delta]), index);
seriesAttributes.add(csa, index);
revalidate();
// adjust the stoppables, too
stoppables.add(index+delta, stoppables.remove(index));
}
protected void buildChart()
{
dataset = new HistogramDataset();
dataset.setType(HistogramType.FREQUENCY); // when buildChart() is called, histogramType hasn't been set yet.
chart = ChartFactory.createHistogram("Untitled Chart","Untitled X Axis","Untitled Y Axis",dataset,
PlotOrientation.VERTICAL, false, true, false);
chart.setAntiAlias(false);
chartPanel = new ChartPanel(chart, true);
chartPanel.setPreferredSize(new java.awt.Dimension(640,480));
chartPanel.setMinimumDrawHeight(10);
chartPanel.setMaximumDrawHeight(2000);
chartPanel.setMinimumDrawWidth(20);
chartPanel.setMaximumDrawWidth(2000);
chartHolder.getViewport().setView(chartPanel);
}
//I need this so I can override this later when going for unit-wide bins
//(chose the values for min, max and # bins).
protected void addSeriesToDataSet(HistogramSeries series)
{
dataset.addSeries(series.getName(),series.getValues(), series.getBins());
}
public void update()
{
// We have to rebuild the whole stupid dataset. Dumb design, JFreeCharters!
XYPlot xyplot = (XYPlot)(chart.getPlot());
dataset = new HistogramDataset();
for(int i=0; i < histogramSeries.size(); i++)
{
HistogramSeries series = (HistogramSeries)(histogramSeries.get(i));
addSeriesToDataSet(series);
}
xyplot.setDataset(dataset);
dataset.setType(histogramType); // It looks like the histograms reset
// tell all the seriesAttributes they need to rebuild, just for a single series. Stupid. O(n) when it should be O(1).
Component[] c = seriesAttributes.getComponents();
for(int i = 0; i < c.length; i++)
{
SeriesAttributes csa = (SeriesAttributes)(c[i]);
csa.rebuildGraphicsDefinitions();
}
revalidate();
}
public HistogramGenerator()
{
// buildChart is called by super() first
LabelledList list = new LabelledList("Show Histograms...");
final HistogramType[] styles = new HistogramType[]
{ HistogramType.FREQUENCY, HistogramType.RELATIVE_FREQUENCY, HistogramType.SCALE_AREA_TO_1 };
final JComboBox style = new JComboBox(new String[] {"By Frequency", "By Relative Frequency", "With Area = 1.0"});
style.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
histogramType = styles[style.getSelectedIndex()];
dataset.setType(histogramType);
}
});
list.add(style);
addGlobalAttribute(list);
}
/** Changes the name in the histogram but not in the seriesAttributes. Typically called FROM the seriesAttributes' setName() method. */
void updateName(int index, String name, boolean waitUntilUpdate)
{
((HistogramSeries)(histogramSeries.get(index))).setName(name);
if (!waitUntilUpdate) update();
}
/** 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. If values is null, then the series is added in the seriesAttributes
but not in the chart: the expectation is that you will then do an update() which will load the series properly. This is a hack
to get around the fact that you HAVE to provide values to a series even if you don't know what they are yet because JFreeChart dies
on a series of length 0.
Returns the series attributes. */
public HistogramSeriesAttributes addSeries(double[] values, int bins, String name, final org.jfree.data.general.SeriesChangeListener stopper)
{
int i = dataset.getSeriesCount();
if (values != null) dataset.addSeries(name, values, bins);
dataset.setType(histogramType); // It looks like the histograms reset
histogramSeries.add(new HistogramSeries(name,values,bins)); // histogram dataset gives us no way to hold onto these, so we must do so ourselves
HistogramSeriesAttributes csa = new HistogramSeriesAttributes(this, name, i, false);
seriesAttributes.add(csa);
stoppables.add( stopper );
revalidate();
return csa;
}
public void updateSeries(int index, double[] vals, boolean waitUntilUpdate)
{
if (histogramSeries.size() > index)
updateSeries(index, vals, ((HistogramSeries)(histogramSeries.get(index))).getBins(),waitUntilUpdate);
}
public void updateSeries(int index, int bins, boolean waitUntilUpdate)
{
if (histogramSeries.size() > index)
updateSeries(index, ((HistogramSeries)(histogramSeries.get(index))).getValues(), bins, waitUntilUpdate);
}
public void updateSeries(int index, double[] vals, int bins, boolean waitUntilUpdate)
{
if (histogramSeries.size() > index)
{
HistogramSeries series = (HistogramSeries)(histogramSeries.get(index));
series.setValues(vals);
series.setBins(bins);
if (!waitUntilUpdate) update();
}
}
public int getNumBins(int index)
{
return ((HistogramSeries)(histogramSeries.get(index))).getBins();
}
public String getName(int index)
{
return ((HistogramSeries)(histogramSeries.get(index))).getName();
}
public double[] getValues(int index)
{
return ((HistogramSeries)(histogramSeries.get(index))).getValues();
}
}