/*
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 <tt>addSeries</tt>
method.
<p>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 <i>single</i>
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();
}
}