/*
Copyright 2013 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.portrayal.inspector;
import java.awt.*;
import java.awt.event.*;
import sim.util.*;
import sim.display.*;
import sim.engine.*;
import javax.swing.*;
import sim.util.gui.*;
import sim.util.media.chart.*;
import org.jfree.data.xy.*;
import org.jfree.data.general.*;
/** A property inspector which generates pie charts of data. The pie charts update in real-time as
requested by the user. Data properties for which
the PieChartChartingPropertyInspector will operate include:
<ul>
<li>Any array of Objects (of which it will show counts of duplicates)
<li>Any array of Datum objects (each Datum will be used as a separate element in the series)
</ul>
<p>PieChartChartingPropertyInspector registers itself with the property menu option "Make Pie Chart".
*/
public class PieChartChartingPropertyInspector extends ChartingPropertyInspector
{
Object[] previousValues = new Object[] { }; // sacrificial
// this double-instanceof hack is a simple way of only locking onto PieChartGenerator
// but still permitting anonymous subclasses (which are critical) but NOT BarChartGenerator.
// The alternative would be to have each class emit some kind of key and you check for that
// key, where anonymous subclasses don't override the emitting function. But for now since
// there are so few generators, we're doing it this ugly way instead.
protected boolean validChartGenerator(ChartGenerator generator) { return generator instanceof PieChartGenerator &&
!(generator instanceof BarChartGenerator); }
protected boolean includeAggregationMethodAttributes() { return false; }
public static String name() { return "Make Pie Chart"; }
public static Class[] types()
{
return new Class[]
{
new Object[0].getClass(), java.util.Collection.class,
ChartUtilities.ProvidesDoublesAndLabels.class,
ChartUtilities.ProvidesObjects.class,
ChartUtilities.ProvidesCollection.class,
};
}
public PieChartChartingPropertyInspector(Properties properties, int index, Frame parent, final GUIState simulation)
{
super(properties,index,parent,simulation);
setupSeriesAttributes(properties, index);
}
public PieChartChartingPropertyInspector(Properties properties, int index, final GUIState simulation, ChartGenerator generator)
{
super(properties, index, simulation, generator);
setupSeriesAttributes(properties, index);
}
// I isolated this code from the constructor into this method because I have two constructors now.
private void setupSeriesAttributes(Properties properties, int index)
{
if (isValidInspector())
{
if (getGenerator().getNumSeriesAttributes() == 0) // recall that we've not been added yet
{
// take control
getGenerator().setTitle("" + properties.getName(index) + " of " + properties.getObject());
}
// add our series
seriesAttributes = ((PieChartGenerator)generator).addSeries(previousValues, properties.getName(index),
new SeriesChangeListener()
{
public void seriesChanged(SeriesChangeEvent event) { getStopper().stop(); }
});
}
}
protected ChartGenerator createNewGenerator()
{
return new PieChartGenerator()
{
public void quit()
{
super.quit();
Stoppable stopper = getStopper();
if (stopper!=null) stopper.stop();
// remove the chart from the GUIState's charts
getCharts(simulation).remove(this);
}
};
}
public void updateSeries(double time, double lastTime)
{
Object obj = properties.getValue(index);
if (obj==null) return;
Class cls = obj.getClass();
Object[] vals = previousValues; // set it to something in case we don't get anything new.
if (cls.isArray())
{
Class comp = cls.getComponentType();
// first check to see if the data is already organized into Datum
// chunks for us.
if (Datum.class.isAssignableFrom(comp))
{
Datum[] data = (Datum[])(obj);
double[] doublevals = new double[data.length];
String[] labels = new String[data.length];
for(int i = 0; i < data.length; i++)
{
if (data[i] != null)
{
doublevals[i] = data[i].getValue();
labels[i] = data[i].getLabel();
}
else
{
doublevals[i] = 0;
labels[i] = "null";
}
}
previousValues = null;
((PieChartGenerator)generator).updateSeries(seriesAttributes.getSeriesIndex(), doublevals, labels);
return;
}
// okay, next check to see if the data can just be counted.
else if (Object.class.isAssignableFrom(comp))
{
Object[] array = (Object[]) obj;
vals = new Object[array.length];
for(int i=0;i<array.length;i++)
vals[i] = array[i];
}
}
else if (java.util.Collection.class.isAssignableFrom(cls))
{
Object[] array = ((java.util.Collection) obj).toArray();
// first check to see if the data is already organized into Datum
// chunks for us.
boolean hasDatum = false;
boolean hasNonDatum = false;
for(int i=0;i<array.length;i++)
{
if (array[i] != null)
{
if (array[i] instanceof Datum)
hasDatum = true;
else
hasNonDatum = true;
}
}
if (hasDatum && !hasNonDatum)
{
double[] doublevals = new double[array.length];
String[] labels = new String[array.length];
for(int i = 0; i < array.length; i++)
{
if (array[i] != null)
{
doublevals[i] = ((Datum)(array[i])).getValue();
labels[i] = ((Datum)(array[i])).getLabel();
}
else
{
doublevals[i] = 0;
labels[i] = "null";
}
}
previousValues = null;
((PieChartGenerator)generator).updateSeries(seriesAttributes.getSeriesIndex(), doublevals, labels);
return;
}
// okay, they're not Datum. Just count the data
vals = new Object[array.length];
for(int i=0;i<array.length;i++)
vals[i] = array[i];
}
else if (obj instanceof ChartUtilities.ProvidesObjects)
{
Object[] array = ((ChartUtilities.ProvidesObjects) obj).provide();
vals = new Object[array.length];
for(int i=0;i<array.length;i++)
vals[i] = array[i];
}
else if (obj instanceof ChartUtilities.ProvidesCollection)
{
Object[] array = ((ChartUtilities.ProvidesCollection) obj).provide().toArray();
vals = new Object[array.length];
for(int i=0;i<array.length;i++)
vals[i] = array[i];
}
else if (obj instanceof ChartUtilities.ProvidesDoublesAndLabels) // Handled Specially
{
double[] array = ((ChartUtilities.ProvidesDoublesAndLabels) obj).provide();
String[] labels = ((ChartUtilities.ProvidesDoublesAndLabels) obj).provideLabels();
previousValues = null;
((PieChartGenerator)generator).updateSeries(seriesAttributes.getSeriesIndex(), array, labels);
return;
}
boolean same = true;
if (previousValues != null && vals.length == previousValues.length)
{
for(int i=0;i < vals.length; i++)
if (vals[i] != previousValues[i])
{ same = false; break; }
}
else same = false;
if (same) return; // they're identical
// at this point we're committed to do an update
previousValues = vals;
((PieChartGenerator)generator).updateSeries(seriesAttributes.getSeriesIndex(), vals);
}
}