/*
* File name: ChartPlugin.java (package eas.plugins.standard)
* Author(s): Lukas König
* Java version: 6.0
* Generation date: 15.12.2011 (16:04:08)
*
* (c) This file and the EAS (Easy Agent Simulation) framework containing it
* is protected by Creative Commons by-nc-sa license. Any altered or
* further developed versions of this file have to meet the agreements
* stated by the license conditions.
*
* In a nutshell
* -------------
* You are free:
* - to Share -- to copy, distribute and transmit the work
* - to Remix -- to adapt the work
*
* Under the following conditions:
* - Attribution -- You must attribute the work in the manner specified by the
* author or licensor (but not in any way that suggests that they endorse
* you or your use of the work).
* - Noncommercial -- You may not use this work for commercial purposes.
* - Share Alike -- If you alter, transform, or build upon this work, you may
* distribute the resulting work only under the same or a similar license to
* this one.
*
* + Detailed license conditions (Germany):
* http://creativecommons.org/licenses/by-nc-sa/3.0/de/
* + Detailed license conditions (unported):
* http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en
*
* This header must be placed in the beginning of any version of this file.
*/
package eas.plugins.standard.visualization.chartPlugin;
import java.awt.BorderLayout;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.renderer.xy.XYSplineRenderer;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.statistics.HistogramBin;
import org.jfree.data.statistics.HistogramDataset;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RefineryUtilities;
import com.lowagie.text.pdf.DefaultFontMapper;
import eas.miscellaneous.StaticMethods;
import eas.plugins.AbstractDefaultPlugin;
import eas.simulation.Wink;
import eas.simulation.event.EASEvent;
import eas.simulation.event.EventFilter;
import eas.simulation.standardEnvironments.AbstractEnvironment;
import eas.startSetup.ParCollection;
import eas.startSetup.SingleParameter;
import eas.startSetup.parameterDatatypes.Datatypes;
/**
* @author Lukas König
*/
public class AllroundChartPlugin extends AbstractDefaultPlugin<AbstractEnvironment<?>> {
private static final long serialVersionUID = -8043309759341640567L;
private HashMap<String, XYSeriesCollection> lineDataSet = new HashMap<String, XYSeriesCollection>();
private HashMap<String, JFrame> lineChartFrameList = new HashMap<String, JFrame>();
private HashMap<String, HistogramDataset> histogramDataSet = new HashMap<String, HistogramDataset>();
private HashMap<String, JFrame> histogramFrameList = new HashMap<String, JFrame>();
private HashMap<String, DefaultPieDataset> pieDataSet = new HashMap<String, DefaultPieDataset>();
private HashMap<String, JFrame> pieFrameList = new HashMap<String, JFrame>();
// This list contains ALL charts (not just line charts)!
private HashMap<String, JFreeChart> chartList = new HashMap<String, JFreeChart>();
/**
*
* @param series
* @param seriesName
* @return Positive number for position of the found item, negative if
* item was not found.
*/
private int containsSeriesLinechart(List<?> series, String seriesName) {
for (int i = 0; i < series.size(); i++) {
if (((XYSeries) series.get(i)).getKey().equals(seriesName)) {
return i;
}
}
return -1;
}
private int containsSeriesHistogram(HistogramDataset dataset, String seriesName) {
for (int i = 0; i < dataset.getSeriesCount(); i++) {
if (dataset.getSeriesKey(i).equals(seriesName)) {
return i;
}
}
return -1;
}
@Override
public List<SingleParameter> getParameters() {
LinkedList<SingleParameter> list = new LinkedList<SingleParameter>();
list.add(new SingleParameter(
"showLiveCharts?",
Datatypes.BOOLEAN,
true,
"If the charts are visualized in a frame during simulation. If not, charts are accessible by sending ChartEventStoreAsPDF events.",
this.id().toUpperCase()));
return list;
}
@Override
public String id() {
return AbstractDefaultPlugin.ALLROUND_PLUGIN_PREFIX + "-chartplugin";
}
@Override
public void runBeforeSimulation(final AbstractEnvironment<?> env, final ParCollection params) {
params.log(StaticMethods.LOG_INFO, "\n"
+ "***********************************************************************\n"
+ "This is '" + this.id().toUpperCase() + "':\n"
+ "To create a chart or add data to an existing chart "
+ "simply broadcast a 'ChartEvent' to the SimulationTime, e.g.,\n"
+ "*environment*.getSimTime().broadcastEvent(new ChartEvent(*...*));\n"
+ "To store an existing chart as PDF you can broadcast a 'ChartEventStoreAsPDF', e.g.,\n"
+ "*environment*.getSimTime().broadcastEvent(new ChartEventStoreAsPDF(*chartName*, *File*));\n"
+ "***********************************************************************\n");
env.getSimTime().requestEvents(this, new EventFilter() {
private static final long serialVersionUID = 3719917006278896507L;
@Override
public boolean isAcceptable(EASEvent event) {
return event.getClass().isAssignableFrom(ChartEvent.class);
}
});
}
@Override
public void runAfterSimulation(AbstractEnvironment<?> env, ParCollection params) {}
@Override
public void runDuringSimulation(AbstractEnvironment<?> env, Wink currentSimTime,
ParCollection params) {
}
private void resetVisibilityOfFrames() {
for (JFrame frame : this.lineChartFrameList.values()) {
frame.setVisible(true);
}
for (JFrame frame : this.pieFrameList.values()) {
frame.setVisible(true);
}
for (JFrame frame : this.histogramFrameList.values()) {
frame.setVisible(true);
}
}
private void handleChartEvent(ChartEvent event, Wink lastSimTime, ParCollection params) {
JFrame frame = null;
if (event.getChartType().equals(ChartEvent.LINE_CHART_NAME)) {
addDataToLineChart(event, lastSimTime, params);
frame = this.lineChartFrameList.get(event.getChartName());
} else if (event.getChartType().equals(ChartEvent.HISTOGRAM_CHART_NAME)) {
addDataToHistogramChart(event, params);
frame = this.histogramFrameList.get(event.getChartName());
} else if (event.getChartType().equals(ChartEvent.PIE_CHART_NAME)) {
addDataToPieChart(event, params);
} else if (event.getChartType().equals(ChartEvent.DELETE_CHART_NAME)) {
removeChart(event);
return;
}
// Set general chart frame settings.
if (event.getChartDim() != null) {
int x = (int) event.getChartDim().x;
int y = (int) event.getChartDim().y;
if (frame.getSize().width != x || frame.getSize().height != y) {
frame.setSize(x, y);
}
}
if (event.getChartPos() != null) {
int x = (int) event.getChartPos().x;
int y = (int) event.getChartPos().y;
if (frame.getLocation().x != x || frame.getLocation().y != y) {
frame.setLocation(x, y);
}
}
}
private void addDataToPieChart(ChartEvent event, ParCollection params) {
DefaultPieDataset dataset = this.pieDataSet.get(event.getChartName());
JFreeChart chart = this.chartList.get(event.getChartName());
if (dataset == null) {
dataset = new DefaultPieDataset();
if (event.isPaintChart3D()) {
chart = ChartFactory.createPieChart3D(event.getChartName(), dataset, true, true, true);
} else {
chart = ChartFactory.createPieChart(event.getChartName(), dataset, true, true, true);
}
createFrame(event.getChartName(), event.getChartType(), params, chart);
this.pieDataSet.put(event.getChartName(), dataset);
this.chartList.put(event.getChartName(), chart);
if (event.getSortOrderPieChartByKey() != null) {
dataset.sortByKeys(event.getSortOrderPieChartByKey());
}
if (event.getSortOrderPieChartByValue() != null) {
dataset.sortByKeys(event.getSortOrderPieChartByValue());
}
PiePlot plot = (PiePlot) chart.getPlot();
if (event.isShowPercentages()) {
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} = {2}"));
} else {
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} = {1}"));
}
}
for (int i = 0; i < event.getPieData().length; i++) {
dataset.setValue(event.getPieDataNames()[i], event.getPieData()[i]);
}
// Exploded segments.
PiePlot plot = (PiePlot) chart.getPlot();
for (String key : event.getExplodedSegmentsPie().keySet()) {
plot.setExplodePercent(key, event.getExplodedSegmentsPie().get(key));
}
// Segment paints.
for (String key : event.getPaintSegments().keySet()) {
plot.setSectionPaint(key, event.getPaintSegments().get(key));
}
}
private void removeChart(ChartEvent event) {
try {this.chartList.remove(event.getChartName());} catch(Exception e) {}
try {this.histogramDataSet.remove(event.getChartName());} catch(Exception e) {}
try {this.lineDataSet.remove(event.getChartName());} catch(Exception e) {}
try {this.histogramFrameList.get(event.getChartName()).dispose();} catch(Exception e) {}
try {this.lineChartFrameList.get(event.getChartName()).dispose();} catch(Exception e) {}
try {this.histogramFrameList.remove(event.getChartName());} catch(Exception e) {}
try {this.lineChartFrameList.remove(event.getChartName());} catch(Exception e) {}
try {this.pieDataSet.remove(event.getChartName());} catch (Exception e) {}
try {this.pieFrameList.remove(event.getChartName());} catch (Exception e) {}
}
private void addDataToHistogramChart(ChartEvent event, ParCollection params) {
// Histogram.
HistogramDataset dataset = this.histogramDataSet.get(event.getChartName());
JFreeChart chart = this.chartList.get(event.getChartName());
if (dataset == null) {
dataset = new HistogramDataset();
dataset.setType(event.getHistType());
dataset.addSeries(
event.getSeriesName(),
event.getHistogramValues(),
event.getHistogramBins(),
event.getHistogramMin(),
event.getHistogramMax());
chart = ChartFactory.createHistogram(
event.getChartName(),
event.getxAxisLabel(),
event.getyAxisLabel(),
dataset,
PlotOrientation.VERTICAL,
true,
true,
true);
createFrame(event.getChartName(), event.getChartType(), params, chart);
this.histogramDataSet.put(event.getChartName(), dataset);
this.chartList.put(event.getChartName(), chart);
}
if (!dataset.getType().equals(event.getHistType())) {
dataset.setType(event.getHistType());
}
if (this.containsSeriesHistogram(dataset, event.getSeriesName()) >= 0) {
/* Change existing series. This is done by awful reflections.
*/
try {
Field f = dataset.getClass().getDeclaredField("list"); //NoSuchFieldException
f.setAccessible(true);
@SuppressWarnings("unchecked")
List<Map<String, Object>> list = (List<Map<String, Object>>) f.get(dataset);
List<Map<String, Object>> list2 = new LinkedList<Map<String,Object>>();
Map<String, Object> removeMap = null;
for (Map<String, Object> map : list) {
String key = (String) map.get("key");
if (key != null && key.equals(event.getSeriesName())) {
removeMap = map;
}
}
list2.addAll(list);
list2.remove(removeMap);
this.addSeries(
list2,
event.getSeriesName(),
event.getHistogramValues(),
event.getHistogramBins(),
event.getHistogramMin(),
event.getHistogramMax());
f.set(dataset, list2);
} catch (Exception e) {
} finally {
dataset.setType(event.getHistType());
}
} else {
// Add new series.
dataset.addSeries(
event.getSeriesName(),
event.getHistogramValues(),
event.getHistogramBins(),
event.getHistogramMin(),
event.getHistogramMax());
dataset.setType(event.getHistType());
}
}
private void addDataToLineChart(ChartEvent event, Wink lastSimTime, ParCollection params) {
// Line Chart.
XYSeriesCollection dataset = this.lineDataSet.get(event.getChartName());
XYItemRenderer renderer;
XYPlot plot;
if (dataset == null) {
NumberAxis domain = new NumberAxis(event.getxAxisLabel());
NumberAxis range = new NumberAxis(event.getyAxisLabel());
if (event.isDrawSmoothSplinesInLineCharts()) {
renderer = new XYSplineRenderer();
} else {
renderer = new XYLineAndShapeRenderer(event.isDrawLinesInLineCharts(), event.isDrawSymbolsInLineCharts());
}
dataset = new XYSeriesCollection();
plot = new XYPlot(dataset, domain, range, renderer);
domain.setAutoRange(true);
domain.setLowerBound(lastSimTime.getCurrentTime());
domain.setTickLabelsVisible(true);
range.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
JFreeChart chart = new JFreeChart(
event.getChartName(),
new Font("SansSerif", Font.BOLD, 24),
plot,
true);
createFrame(event.getChartName(), event.getChartType(), params, chart);
this.lineDataSet.put(event.getChartName(), dataset);
this.chartList.put(event.getChartName(), chart);
} else {
plot = chartList.get(event.getChartName()).getXYPlot();
}
XYSeries series;
int foundSeriesNum = this.containsSeriesLinechart(dataset.getSeries(), event.getSeriesName());
if (foundSeriesNum < 0) {
dataset.addSeries(new XYSeries(event.getSeriesName()));
foundSeriesNum = this.containsSeriesLinechart(dataset.getSeries(), event.getSeriesName());
}
if (event.getLineStroke() != null) {
plot.getRenderer().setSeriesStroke(foundSeriesNum, event.getLineStroke());
}
if (event.getLineColor() != null) {
plot.getRenderer().setSeriesPaint(foundSeriesNum, event.getLineColor());
}
plot.getDomainAxis().setUpperBound(lastSimTime.getCurrentTime());
series = dataset.getSeries(event.getSeriesName());
if (event.getXValue() == ChartEvent.X_VALUE_NOT_SPECIFIED) {
series.add(new XYDataItem(lastSimTime.getCurrentTime(), event.getValue()));
} else {
series.add(new XYDataItem(event.getXValue(), event.getValue()));
}
}
private void createFrame(
String chartName,
String eventType,
ParCollection params,
JFreeChart chart) {
ChartPanel chartPanel = new ChartPanel(chart);
JFrame frame = new JFrame(eventType + " [" + chartName + "]");
frame.getContentPane().add(chartPanel, BorderLayout.CENTER);
frame.setBounds(200, 120, 600, 280);
if (params.getParValueBoolean("showLiveCharts?")) {
frame.setVisible(true);
RefineryUtilities.centerFrameOnScreen(frame);
}
this.lineChartFrameList.put(chartName, frame);
}
private void handleChartEventStoreAsPDF(ChartEventStoreAsPDF event) {
File file = event.getFile();
try {
StaticMethods.saveChartAsPDF(file, chartList.get(event.getChartNam()), 400, 300, new DefaultFontMapper());
} catch (IOException e) {
throw new RuntimeException(this.id().toUpperCase() + " - PDF not stored '" + event.getFile() + "'");
}
}
@Override
public void handleEvent(
EASEvent e,
AbstractEnvironment<?> env,
Wink lastSimTime,
ParCollection params) {
try {
ChartEvent event = (ChartEvent) e;
this.handleChartEvent(event, lastSimTime, params);
} catch (Exception e2) {}
try {
ChartEventStoreAsPDF event = (ChartEventStoreAsPDF) e;
this.handleChartEventStoreAsPDF(event);
} catch (Exception e2) {}
}
@Override
public List<String> getRequiredPlugins() {return null;}
@Override
public List<String> getSupportedPlugins() {
return null;
}
/**
* This method comes directly from
* {@link org.jfree.data.statistics.HistogramDataset}.
*
* @param liste The list to add the series to.
*/
private void addSeries(List<Map<String, Object>> liste, Comparable<?> key, double[] values, int bins,
double minimum, double maximum) {
if (key == null) {
throw new IllegalArgumentException("Null 'key' argument.");
}
if (values == null) {
throw new IllegalArgumentException("Null 'values' argument.");
} else if (bins < 1) {
throw new IllegalArgumentException(
"The 'bins' value must be at least 1.");
}
double binWidth = (maximum - minimum) / bins;
double lower = minimum;
double upper;
List<HistogramBin> binList = new ArrayList<HistogramBin>(bins);
for (int i = 0; i < bins; i++) {
HistogramBin bin;
// make sure bins[bins.length]'s upper boundary ends at maximum
// to avoid the rounding issue. the bins[0] lower boundary is
// guaranteed start from min
if (i == bins - 1) {
bin = new HistogramBin(lower, maximum);
} else {
upper = minimum + (i + 1) * binWidth;
bin = new HistogramBin(lower, upper);
lower = upper;
}
binList.add(bin);
}
// fill the bins
for (int i = 0; i < values.length; i++) {
int binIndex = bins - 1;
if (values[i] < maximum) {
double fraction = (values[i] - minimum) / (maximum - minimum);
if (fraction < 0.0) {
fraction = 0.0;
}
binIndex = (int) (fraction * bins);
// rounding could result in binIndex being equal to bins
// which will cause an IndexOutOfBoundsException - see bug
// report 1553088
if (binIndex >= bins) {
binIndex = bins - 1;
}
}
HistogramBin bin = binList.get(binIndex);
bin.incrementCount();
}
// generic map for each series
Map<String, Object> map = new HashMap<String, Object>();
map.put("key", key);
map.put("bins", binList);
map.put("values.length", new Integer(values.length));
map.put("bin width", new Double(binWidth));
liste.add(map);
}
@Override
public void onSimulationResumed(AbstractEnvironment<?> env, Wink resumeTime,
ParCollection params) {
resetVisibilityOfFrames();
}
}