+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms
+ * of the GNU Lesser General Public License as published by the Free Software
+ * Foundation;
+ * either version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this
+ * library; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ *
+ */
+public interface ColorMap {
+
+ /**
+ * Return the components of the color stored at given index.
+ *
+ * @param index The index of the color. It is a 0-based index of the color
+ * corresponding to the adding order.
+ * @return An array of 3 integers representing the RGB components of the color.
+ */
+ public int[] getColorComponents(int index);
+
+ /**
+ * Return the index of the color mapped to the given value.
+ *
+ * @param value The value mapped to the color.
+ * @return The array index of the requested color.
+ */
+ public int getColorIndex(int value);
+
+ /**
+ * Return the index of the color mapped to the given value.
+ *
+ * @param value The value mapped to the color.
+ * @return The array index of the requested color.
+ */
+ public int getColorIndex(double value);
+
+}
diff --git a/src/main/java/microsim/gui/colormap/DoubleRangeColorMap.java b/src/main/java/microsim/gui/colormap/DoubleRangeColorMap.java
new file mode 100644
index 00000000..27b05dd6
--- /dev/null
+++ b/src/main/java/microsim/gui/colormap/DoubleRangeColorMap.java
@@ -0,0 +1,62 @@
+package microsim.gui.colormap;
+
+import java.awt.Color;
+
+/**
+ * It builds automatically a color map varying between two colors on a variable
+ * range.
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms
+ * of the GNU Lesser General Public License as published by the Free Software
+ * Foundation;
+ * either version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this
+ * library; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ *
+ */
+public class FixedColorMap implements ColorMap {
+ protected Color[] colorList;
+ protected int[][] colorComponents;
+ protected int colors = 0;
+ protected Map mapper;
+
+ /** Create a color map. */
+ public FixedColorMap() {
+ colorList = new Color[0];
+ colorComponents = new int[0][3];
+ mapper = new HashMap();
+ }
+
+ /**
+ * Create a color map with an initial capacity of mapping positions.
+ *
+ * @param colors The number of colors to be mapped. Must be a non-zero positive.
+ */
+ public FixedColorMap(int colors) {
+ if (colors <= 0)
+ throw new ArrayIndexOutOfBoundsException("ColorMap: 'colors' must be a positive number.");
+
+ colorList = new Color[colors];
+ colorComponents = new int[colors][3];
+ mapper = new HashMap(colors);
+ }
+
+ private void ensureCapacity(int unitsRequired) {
+ if (colorList.length > unitsRequired)
+ return;
+
+ Color[] c = new Color[unitsRequired];
+ int[][] cc = new int[unitsRequired][3];
+
+ System.arraycopy(colorList, 0, c, 0, colorList.length);
+ System.arraycopy(colorComponents, 0, cc, 0, colorComponents.length);
+
+ colorList = c;
+ colorComponents = cc;
+ }
+
+ /**
+ * Add a color to the map.
+ *
+ * @param value The integer value that maps to the given color.
+ * @param color The color to be mapped.
+ * @throws ArrayIndexOutOfBoundsException If value is greather than the defined
+ * mapped colors.
+ */
+ public void addColor(int value, Color color) {
+ if (value < 0)
+ throw new IllegalArgumentException("ColorMap.addColor: value parameter must be positive.");
+
+ if (mapper.containsKey(value))
+ throw new IllegalArgumentException("ColorMap.addColor: value " + value + " already added.");
+
+ ensureCapacity(colors + 1);
+
+ colorList[colors] = color;
+ colorComponents[colors][0] = color.getRed();
+ colorComponents[colors][1] = color.getGreen();
+ colorComponents[colors][2] = color.getBlue();
+ mapper.put(value, colors++);
+ }
+
+ /**
+ * Add a color to the map.
+ *
+ * @param value The integer value that maps to the given color.
+ * @param red The red component of the color to be mapped. [0-255] range
+ * accepted.
+ * @param green The green component of the color to be mapped. [0-255] range
+ * accepted.
+ * @param blue The blue component of the color to be mapped. [0-255] range
+ * accepted.
+ * @throws ArrayIndexOutOfBoundsException If one of the three color components
+ * is out of (0, 255) range.
+ */
+ public void addColor(int value, int red, int green, int blue) {
+ if (red < 0 || red > 255)
+ throw new ArrayIndexOutOfBoundsException("ColorMap.addColor: Red component must be in range [0, 255]");
+ if (green < 0 || green > 255)
+ throw new ArrayIndexOutOfBoundsException("ColorMap.addColor: Green component must be in range [0, 255]");
+ if (blue < 0 || blue > 255)
+ throw new ArrayIndexOutOfBoundsException("ColorMap.addColor: Blue component must be in range [0, 255]");
+
+ addColor(value, new Color(red, green, blue));
+ }
+
+ /**
+ * Return the color to at the given index position.
+ *
+ * @param index The value to be mapped.
+ * @return The color corresponding to the value.
+ */
+ public Color getColor(int index) {
+ return colorList[index];
+ }
+
+ /**
+ * Return the color list.
+ *
+ * @return An array of Color. The index represent the mapping value.
+ */
+ public Color[] toArray() {
+ return colorList;
+ }
+
+ public int[] getColorComponents(int index) {
+ return colorComponents[index];
+ }
+
+ /**
+ * Return the color index.
+ *
+ * @param value The value to be mapped.
+ * @return The index of the color list mapping the value.
+ */
+ public int getColorIndex(int value) {
+ int k = mapper.get(value);
+ if (k == Integer.MIN_VALUE)
+ throw new ArrayIndexOutOfBoundsException("ColorMap.getColorIndex: Value "
+ + value + " not mapped.");
+ return k;
+ }
+
+ /**
+ * Return the color index.
+ *
+ * @param value The value to be mapped.
+ * @return The index of the color list mapping the value.
+ */
+ public int getColorIndex(double value) {
+ int k = mapper.get((int) value);
+ if (k == Integer.MIN_VALUE)
+ throw new ArrayIndexOutOfBoundsException("ColorMap.getColorIndex: Value "
+ + value + " not mapped.");
+ return k;
+ }
+
+ /**
+ * Map the given value with the right color.
+ *
+ * @param value The value to be mapped.
+ * @return The color corresponding to the value.
+ */
+ public Color getMappedColor(int value) {
+ return colorList[getColorIndex(value)];
+ }
+}
diff --git a/src/main/java/microsim/gui/colormap/RangeColorMap.java b/src/main/java/microsim/gui/colormap/RangeColorMap.java
new file mode 100644
index 00000000..548cce94
--- /dev/null
+++ b/src/main/java/microsim/gui/colormap/RangeColorMap.java
@@ -0,0 +1,231 @@
+package microsim.gui.colormap;
+
+import java.awt.Color;
+
+/**
+ * It builds automatically a color map using a variable range.
+ * There are three types of range:
+ *
+ *
From black color to a given color, based on a linear range.
+ *
From given color to a given color, based on a linear range.
+ *
From given color to a given color, based on dual range, with a middle
+ * color.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms
+ * of the GNU Lesser General Public License as published by the Free Software
+ * Foundation;
+ * either version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this
+ * library; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ * @author Matteo Morini
+ */
+public class RangeColorMap extends FixedColorMap {
+ protected double rangeSize;
+ protected double minValue;
+
+ /**
+ * Create a color range map from black to given color.
+ *
+ * @param gradients The number of color gradients that are added to the map.
+ * @param minValue The lower bound of the range.
+ * @param maxValue The upper bound of the range.
+ * @param color The highest color. It will correspond to maxValue.
+ * @throws ArrayIndexOutOfBoundsException if {@code maxValue <= minValue}.
+ */
+ public RangeColorMap(int gradients, double minValue, double maxValue,
+ Color color) {
+ super(gradients);
+
+ if (maxValue <= minValue)
+ throw new ArrayIndexOutOfBoundsException("ColorRangeMap: range parameters"
+ + " are not corrected.");
+
+ this.rangeSize = gradients / (maxValue - minValue);
+ this.minValue = minValue;
+
+ double redGap = (double) color.getRed() / (double) gradients;
+ double greenGap = (double) color.getGreen() / (double) gradients;
+ double blueGap = (double) color.getBlue() / (double) gradients;
+
+ for (int i = 0; i < gradients; i++)
+ addColor(i, new Color(getBoundedCol(redGap * i),
+ getBoundedCol(greenGap * i),
+ getBoundedCol(blueGap * i)));
+
+ }
+
+ /**
+ * Create a color range map from given color to given color.
+ *
+ * @param gradients The number of color gradients that are added to the map.
+ * @param minValue The lower bound of the range.
+ * @param maxValue The upper bound of the range.
+ * @param bottomColor The lowest color. It will correspond to minValue.
+ * @param topColor The highest color. It will correspond to maxValue.
+ * @throws ArrayIndexOutOfBoundsException if {@code maxValue <= minValue}.
+ */
+ public RangeColorMap(int gradients, double minValue, double maxValue,
+ Color bottomColor, Color topColor) {
+ super(gradients);
+
+ if (maxValue <= minValue)
+ throw new ArrayIndexOutOfBoundsException("ColorRangeMap: range parameters are not corrected.");
+
+ this.minValue = minValue;
+ int redStart, blueStart, greenStart;
+ int redGap, blueGap, greenGap;
+
+ redStart = bottomColor.getRed();
+ blueStart = bottomColor.getBlue();
+ greenStart = bottomColor.getGreen();
+
+ redGap = topColor.getRed() - redStart;
+ blueGap = topColor.getBlue() - blueStart;
+ greenGap = topColor.getGreen() - greenStart;
+
+ // rangeSize = (maxValue - minValue) / (double) gradients;
+ rangeSize = (double) gradients / (maxValue - minValue);
+
+ int[] c = new int[] { 0, 0, 0 };
+ for (int i = 0; i < gradients; i++) {
+ // int[] c = getComponents(getBoundedCol(i * gap));
+ double delta = (double) i / (double) gradients;
+ c[0] = getBoundedCol(redStart + (int) (redGap * delta));
+ c[1] = getBoundedCol(greenStart + (int) (greenGap * delta));
+ c[2] = getBoundedCol(blueStart + (int) (blueGap * delta));
+
+ addColor(i, new Color(c[0], c[1], c[2]));
+ }
+ }
+
+ /**
+ * Create a color range map from lower given color to middle given color and
+ * from middle to the highest given one.
+ *
+ * @param gradients The number of color gradients that are added to the map.
+ * @param minValue The lower bound of the range.
+ * @param midValue The value at which color changes the range.
+ * @param maxValue The upper bound of the range.
+ * @param bottomColor The lowest color. It will correspond to minValue.
+ * @param middleColor The middle color. It will correspond to midValue.
+ * @param topColor The highest color. It will correspond to maxValue.
+ * @throws ArrayIndexOutOfBoundsException if
+ * {@code maxValue <= midValue || midValue <= minValue}.
+ */
+ public RangeColorMap(int gradients, double minValue, double midValue,
+ double maxValue, Color bottomColor, Color middleColor,
+ Color topColor) {
+ super(gradients);
+
+ if (maxValue <= midValue || midValue <= minValue)
+ throw new ArrayIndexOutOfBoundsException("ColorRangeMap: range parameters are not corrected.");
+
+ int redStart, blueStart, greenStart;
+ int redGap, blueGap, greenGap;
+ int[] c = new int[] { 0, 0, 0 };
+
+ this.minValue = minValue;
+
+ rangeSize = (double) gradients / (maxValue - minValue);
+ // LOW PART
+ int lowGradients = (int) (gradients * ((midValue - minValue) / (maxValue - minValue)));
+
+ redStart = bottomColor.getRed();
+ blueStart = bottomColor.getBlue();
+ greenStart = bottomColor.getGreen();
+
+ redGap = middleColor.getRed() - redStart;
+ blueGap = middleColor.getBlue() - blueStart;
+ greenGap = middleColor.getGreen() - greenStart;
+
+ for (int i = 0; i < lowGradients; i++) {
+ double delta = (double) i / (double) lowGradients;
+ c[0] = getBoundedCol(redStart + (int) (redGap * delta));
+ c[1] = getBoundedCol(greenStart + (int) (greenGap * delta));
+ c[2] = getBoundedCol(blueStart + (int) (blueGap * delta));
+
+ addColor(i, new Color(c[0], c[1], c[2]));
+ }
+
+ // HIGH PART
+ int highGradients = gradients - lowGradients;
+
+ redStart = middleColor.getRed();
+ blueStart = middleColor.getBlue();
+ greenStart = middleColor.getGreen();
+
+ redGap = topColor.getRed() - redStart;
+ blueGap = topColor.getBlue() - blueStart;
+ greenGap = topColor.getGreen() - greenStart;
+
+ for (int i = lowGradients; i < gradients; i++) {
+ double delta = (double) (i - lowGradients) / (double) highGradients;
+ c[0] = getBoundedCol(redStart + (int) (redGap * delta));
+ c[1] = getBoundedCol(greenStart + (int) (greenGap * delta));
+ c[2] = getBoundedCol(blueStart + (int) (blueGap * delta));
+
+ addColor(i, new Color(c[0], c[1], c[2]));
+ }
+ }
+
+ private int getBoundedCol(double d) {
+ return Math.min(255, (int) d);
+ }
+
+ /**
+ * Return the color index.
+ *
+ * @param value The value to be mapped. If it is outside the range bounds,
+ * the method returns its nearest bound.
+ * @return The index of the color list mapping the value.
+ */
+ public int getColorIndex(int value) {
+ int i = (int) ((value - minValue) * rangeSize);
+ if (i < 0)
+ i = 0;
+ if (i >= colorList.length)
+ i = colorList.length - 1;
+ return i;
+ }
+
+ /**
+ * Return the color index.
+ *
+ * @param value The value to be mapped. If it is outside the range bounds,
+ * the method returns its nearest bound.
+ * @return The index of the color list mapping the value.
+ */
+ public int getColorIndex(double value) {
+ int i = (int) ((value - minValue) * rangeSize);
+ if (i < 0)
+ i = 0;
+ if (i >= colorList.length)
+ i = colorList.length - 1;
+ return i;
+ }
+
+}
diff --git a/src/main/java/microsim/gui/colormap/TripleRangeColorMap.java b/src/main/java/microsim/gui/colormap/TripleRangeColorMap.java
new file mode 100644
index 00000000..99ca3b61
--- /dev/null
+++ b/src/main/java/microsim/gui/colormap/TripleRangeColorMap.java
@@ -0,0 +1,101 @@
+package microsim.gui.colormap;
+
+import java.awt.Color;
+
+/**
+ * It builds automatically a color map oscillating from a bottom color to a
+ * middle one and from the middle
+ * to a top one, on a variable range.
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms
+ * of the GNU Lesser General Public License as published by the Free Software
+ * Foundation;
+ * either version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this
+ * library; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ *
+ */
+public class TripleRangeColorMap extends FixedColorMap {
+
+ private int redStart, blueStart, greenStart;
+ private int redMiddle, blueMiddle, greenMiddle;
+ private int redEnd, blueEnd, greenEnd;
+ private double rangeSize;
+
+ public TripleRangeColorMap(int gradients, Color bottomColor, Color middleColor, Color topColor,
+ double minValue, double midValue, double maxValue) {
+ super(gradients);
+
+ if (maxValue <= midValue || midValue <= minValue)
+ throw new ArrayIndexOutOfBoundsException("ColorTripleRangeMap: range parameters are not corrected.");
+
+ redStart = bottomColor.getRed();
+ blueStart = bottomColor.getBlue();
+ greenStart = bottomColor.getGreen();
+
+ redMiddle = middleColor.getRed();
+ blueMiddle = middleColor.getBlue();
+ greenMiddle = middleColor.getGreen();
+
+ redEnd = topColor.getRed();
+ blueEnd = topColor.getBlue();
+ greenEnd = topColor.getGreen();
+
+ rangeSize = (maxValue - minValue) / gradients;
+
+ int lowGradients = (int) ((midValue - minValue) / (maxValue - minValue) * gradients);
+
+ for (int i = 0; i < lowGradients; i++) {
+ // int[] c = getComponents(getBoundedCol(i * gap));
+ int[] c = new int[] { 0, 0, 0 };
+ c[0] = redStart + (int) ((redMiddle - redStart) * i / gradients);
+ c[1] = greenStart + (int) ((greenMiddle - greenStart) * i / gradients);
+ c[2] = blueStart + (int) ((blueMiddle - blueStart) * i / gradients);
+
+ addColor(i, new Color(c[0], c[1], c[2]));
+ }
+
+ for (int i = lowGradients; i < gradients; i++) {
+ // int[] c = getComponents(getBoundedCol(i * gap));
+ int[] c = new int[] { 0, 0, 0 };
+ c[0] = redMiddle + (int) ((redEnd - redMiddle) * i / gradients);
+ c[1] = greenMiddle + (int) ((greenEnd - greenMiddle) * i / gradients);
+ c[2] = blueMiddle + (int) ((blueEnd - blueMiddle) * i / gradients);
+
+ addColor(i, new Color(c[0], c[1], c[2]));
+ }
+
+ }
+
+ public int getColorIndex(double value) {
+ int i = (int) (value * rangeSize);
+ if (i < 0)
+ i = 0;
+ if (i >= colorList.length)
+ i = colorList.length - 1;
+ return i;
+ }
+}
diff --git a/src/main/java/microsim/gui/plot/CollectionBarSimulationPlotter.java b/src/main/java/microsim/gui/plot/CollectionBarSimulationPlotter.java
new file mode 100644
index 00000000..4a8357db
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/CollectionBarSimulationPlotter.java
@@ -0,0 +1,316 @@
+package microsim.gui.plot;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+import javax.swing.JInternalFrame;
+
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.statistics.IDoubleArraySource;
+import microsim.statistics.IFloatArraySource;
+import microsim.statistics.IIntArraySource;
+import microsim.statistics.ILongArraySource;
+import microsim.statistics.IUpdatableSource;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.CategoryAxis;
+import org.jfree.chart.axis.CategoryLabelPositions;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.CategoryPlot;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.renderer.category.BarRenderer;
+import org.jfree.data.category.DefaultCategoryDataset;
+
+/**
+ * A bar chart plotter showing elements manually added by user. It is based on
+ * JFreeChart library. It is compatible with the microsim.statistics.* classes.
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ *
+ */
+public class CollectionBarSimulationPlotter extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private ArrayList sources;
+ private ArrayList categories;
+
+ private DefaultCategoryDataset dataset;
+
+ private BarRenderer renderer;
+
+ private Integer maxBars;
+
+ private abstract class ArraySource {
+ // public String label;
+ protected boolean isUpdatable;
+
+ public abstract double[] getDoubleArray();
+
+ }
+
+ private class DArraySource extends ArraySource {
+ public IDoubleArraySource source;
+
+ public DArraySource(String label, IDoubleArraySource source) {
+ // super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleArray();
+ }
+ }
+
+ private class FArraySource extends ArraySource {
+ public IFloatArraySource source;
+
+ public FArraySource(String label, IFloatArraySource source) {
+ // super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ float[] array = source.getFloatArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+ }
+
+ private class IArraySource extends ArraySource {
+ public IIntArraySource source;
+
+ public IArraySource(String label, IIntArraySource source) {
+ // super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ int[] array = source.getIntArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+ }
+
+ private class LArraySource extends ArraySource {
+ public ILongArraySource source;
+
+ public LArraySource(String label, ILongArraySource source) {
+ // super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ long[] array = source.getLongArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+ }
+
+ public CollectionBarSimulationPlotter(String title, String yaxis) {
+ super();
+ this.setResizable(true);
+ this.setTitle(title);
+
+ sources = new ArrayList();
+ categories = new ArrayList();
+
+ dataset = new DefaultCategoryDataset();
+
+ final JFreeChart chart = ChartFactory.createBarChart(
+ title, // chart title
+ "Categories", // x axis label
+ yaxis, // y axis label
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ false, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final CategoryPlot plot = chart.getCategoryPlot();
+ plot.setBackgroundPaint(Color.lightGray);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+
+ // set the range axis to display integers only... Ross: Why???
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ // rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+
+ // disable bar outlines...
+ renderer = (BarRenderer) plot.getRenderer();
+ renderer.setDrawBarOutline(false);
+
+ final CategoryAxis domainAxis = plot.getDomainAxis();
+ domainAxis.setCategoryLabelPositions(
+ CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0));
+ // OPTIONAL CUSTOMISATION COMPLETED.
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ public void update() {
+ for (int i = 0; i < sources.size(); i++) {
+ ArraySource cs = (ArraySource) sources.get(i);
+ final String category = categories.get(i);
+
+ double[] vals = cs.getDoubleArray();
+ for (int j = 0; j < vals.length && (j < (maxBars == null ? Integer.MAX_VALUE : maxBars)); j++)
+ dataset.addValue(vals[j], category, "" + j);
+ }
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IDoubleArraySource source) {
+ DArraySource sequence = new DArraySource(name, source);
+ sources.add(sequence);
+ categories.add(name);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IFloatArraySource source) {
+ FArraySource sequence = new FArraySource(name, source);
+ sources.add(sequence);
+ categories.add(name);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IIntArraySource source) {
+ IArraySource sequence = new IArraySource(name, source);
+ sources.add(sequence);
+ categories.add(name);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, ILongArraySource source) {
+ LArraySource sequence = new LArraySource(name, source);
+ sources.add(sequence);
+ categories.add(name);
+ }
+
+ public Integer getMaxBars() {
+ return maxBars;
+ }
+
+ public void setMaxBars(Integer maxBars) {
+ this.maxBars = maxBars;
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/HistogramSimulationPlotter.java b/src/main/java/microsim/gui/plot/HistogramSimulationPlotter.java
new file mode 100644
index 00000000..51d1daa5
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/HistogramSimulationPlotter.java
@@ -0,0 +1,406 @@
+package microsim.gui.plot;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+import javax.swing.JInternalFrame;
+
+import microsim.engine.SimulationEngine;
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.statistics.IDoubleArraySource;
+import microsim.statistics.IFloatArraySource;
+import microsim.statistics.IIntArraySource;
+import microsim.statistics.ILongArraySource;
+import microsim.statistics.IUpdatableSource;
+
+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.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.StandardXYBarPainter;
+import org.jfree.chart.renderer.xy.XYBarRenderer;
+import org.jfree.data.general.SeriesChangeEvent;
+import org.jfree.data.statistics.HistogramDataset;
+import org.jfree.data.statistics.HistogramType;
+
+/**
+ * A HistogramSimulationPlotter is able to display a histogram of one or more
+ * data
+ * sources, which can be updated during the simulation. It is based on
+ * JFreeChart
+ * library and uses data sources based on the microsim.statistics.*
+ * interfaces.
+ *
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Ross Richardson
+ *
+ */
+public class HistogramSimulationPlotter extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ final JFreeChart chart;
+
+ private ArrayList sources;
+
+ private HistogramDataset dataset;
+
+ private HistogramType type;
+
+ private int bins;
+
+ private Double minimum;
+
+ private Double maximum;
+
+ /**
+ * Constructor for histogram chart objects with chart legend displayed by
+ * default and
+ * all data samples shown, showing only the latest population data as time moves
+ * forward.
+ * Note - values falling on the boundary of adjacent bins will be assigned to
+ * the higher
+ * indexed bin. If it is desired set the minimum and maximum values displayed,
+ * or to turn
+ * the legend off, use the constructor:
+ * HistogramSimulationPlotter(String title, String xaxis, HistogramType type,
+ * int bins, double minimum, double maximum, boolean includeLegend)
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param type - the type of the histogram: either FREQUENCY,
+ * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1
+ * @param bins - the number of bins in the histogram
+ *
+ */
+ public HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins) { // Includes legend by
+ // default and will
+ // accumulate data
+ // samples by default
+ // (if wanting only
+ // the most recent
+ // data points, use
+ // the other
+ // constructor)
+ this(title, xaxis, type, bins, null, null, true);
+ }
+
+ /**
+ * Constructor for scatterplot chart objects, featuring a toggle to hide the
+ * chart legend
+ * and to set the minimum and maximum values displayed in the chart, with values
+ * below the minimum
+ * assigned to the first bin, and values above the maximum assigned to the last
+ * bin. Note -
+ * values falling on the boundary of adjacent bins will be assigned to the
+ * higher indexed bin.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param type - the type of the histogram: either FREQUENCY,
+ * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1
+ * @param bins - the number of bins in the histogram
+ * @param minimum - any data value less than minimum will be assigned to
+ * the first bin
+ * @param maximum - any data value greater than maximum will be assigned
+ * to the last bin
+ * @param includeLegend - toggles whether to include the legend. If displaying a
+ * very large number of different series in the chart, it
+ * may be useful to turn
+ * the legend off as it will occupy a lot of space in the
+ * GUI.
+ */
+ public HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins, Double minimum,
+ Double maximum, boolean includeLegend) { // Can specify whether to include legend and how many samples
+ // (updates) to display
+ super();
+ this.setResizable(true);
+ this.setTitle(title);
+ this.type = type;
+ this.bins = bins;
+ this.minimum = minimum;
+ this.maximum = maximum;
+
+ sources = new ArrayList();
+
+ dataset = new HistogramDataset();
+
+ String yaxis;
+ if (type.equals(HistogramType.FREQUENCY)) {
+ yaxis = "Frequency";
+ } else if (type.equals(HistogramType.RELATIVE_FREQUENCY)) {
+ yaxis = "Relative Frequency";
+ } else if (type.equals(HistogramType.SCALE_AREA_TO_1)) {
+ yaxis = "Density (area scaled to 1)";
+ } else
+ throw new IllegalArgumentException(
+ "Incorrect HistogramType argument when calling HistogramSimulationPlotter constructor!");
+
+ chart = ChartFactory.createHistogram(
+ title, // chart title
+ xaxis, // x axis label
+ yaxis, // y axis label
+ // type.toString(), //y axis label based on the type of the histogram
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ includeLegend, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final XYPlot plot = chart.getXYPlot();
+ // plot.setBackgroundPaint(Color.lightGray);
+ plot.setBackgroundPaint(Color.white);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+ plot.setForegroundAlpha(0.85f);
+
+ final XYBarRenderer renderer = new XYBarRenderer();
+ renderer.setDrawBarOutline(false);
+ renderer.setBarPainter(new StandardXYBarPainter());
+ renderer.setShadowVisible(false);
+ plot.setRenderer(renderer);
+
+ final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
+ domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ public void update() {
+
+ dataset = new HistogramDataset();
+ dataset.setType(type);
+ chart.getXYPlot().setDataset(dataset);
+
+ // int s = 0;
+ // Color color = (Color) chart.getXYPlot().getRenderer().getItemPaint(s, 0);
+ // int r = color.getRed();
+ // int g = color.getGreen();
+ // int b = color.getBlue();
+ // chart.getXYPlot().getRenderer().setSeriesPaint(s, new Color(r, g, b, 130));
+
+ for (int i = 0; i < sources.size(); i++) {
+ ArraySource cs = (ArraySource) sources.get(i);
+ double[] vals = cs.getDoubleArray();
+
+ if (minimum != null && maximum != null) {
+ dataset.addSeries(cs.label, vals, bins, minimum, maximum);
+ } else
+ dataset.addSeries(cs.label, vals, bins);
+
+ }
+ dataset.seriesChanged(
+ new SeriesChangeEvent(new String("Update at time " + SimulationEngine.getInstance().getTime())));
+
+ }
+
+ private abstract class ArraySource {
+ public String label;
+ protected boolean isUpdatable;
+
+ public abstract double[] getDoubleArray();
+
+ }
+
+ private class DArraySource extends ArraySource {
+ public IDoubleArraySource source;
+
+ public DArraySource(String label, IDoubleArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleArray();
+ }
+ }
+
+ private class FArraySource extends ArraySource {
+ public IFloatArraySource source;
+
+ public FArraySource(String label, IFloatArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ float[] array = source.getFloatArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+ }
+
+ private class IArraySource extends ArraySource {
+ public IIntArraySource source;
+
+ public IArraySource(String label, IIntArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ int[] array = source.getIntArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+ }
+
+ private class LArraySource extends ArraySource {
+ public ILongArraySource source;
+
+ public LArraySource(String label, ILongArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ long[] array = source.getLongArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IDoubleArraySource source) {
+ DArraySource sequence = new DArraySource(name, source);
+ sources.add(sequence);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IFloatArraySource source) {
+ FArraySource sequence = new FArraySource(name, source);
+ sources.add(sequence);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IIntArraySource source) {
+ IArraySource sequence = new IArraySource(name, source);
+ sources.add(sequence);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IDoubleSource objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, ILongArraySource source) {
+ LArraySource sequence = new LArraySource(name, source);
+ sources.add(sequence);
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/IndividualBarSimulationPlotter.java b/src/main/java/microsim/gui/plot/IndividualBarSimulationPlotter.java
new file mode 100644
index 00000000..8b36f33a
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/IndividualBarSimulationPlotter.java
@@ -0,0 +1,506 @@
+package microsim.gui.plot;
+
+import java.awt.Color;
+import java.awt.Paint;
+import java.util.ArrayList;
+
+import javax.swing.JInternalFrame;
+
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.gui.colormap.ColorMap;
+import microsim.gui.colormap.FixedColorMap;
+import microsim.reflection.ReflectionUtils;
+import microsim.statistics.IDoubleSource;
+import microsim.statistics.IFloatSource;
+import microsim.statistics.IIntSource;
+import microsim.statistics.ILongSource;
+import microsim.statistics.IUpdatableSource;
+import microsim.statistics.reflectors.DoubleInvoker;
+import microsim.statistics.reflectors.FloatInvoker;
+import microsim.statistics.reflectors.IntegerInvoker;
+import microsim.statistics.reflectors.LongInvoker;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.CategoryAxis;
+import org.jfree.chart.axis.CategoryLabelPositions;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.CategoryPlot;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.renderer.category.BarRenderer;
+import org.jfree.data.category.DefaultCategoryDataset;
+
+/**
+ * A bar chart plotter showing elements manually added by user. It is based on
+ * JFreeChart library. It is compatible with the microsim.statistics.* classes.
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ */
+public class IndividualBarSimulationPlotter extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private ArrayList sources;
+ private ArrayList categories;
+
+ private DefaultCategoryDataset dataset;
+
+ private BarRenderer renderer;
+
+ private String yaxis;
+
+ private FixedColorMap colorMap;
+
+ public IndividualBarSimulationPlotter(String title, String yaxis) {
+ super();
+ this.setResizable(true);
+ this.setTitle(title);
+ this.yaxis = yaxis;
+ colorMap = new FixedColorMap();
+
+ sources = new ArrayList();
+ categories = new ArrayList();
+
+ dataset = new DefaultCategoryDataset();
+
+ final JFreeChart chart = ChartFactory.createBarChart(
+ title, // chart title
+ "Categories", // x axis label
+ yaxis, // y axis label
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ false, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final CategoryPlot plot = chart.getCategoryPlot();
+ plot.setBackgroundPaint(Color.lightGray);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+
+ // set the range axis to display integers only... Ross: Why???
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ // rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+
+ // disable bar outlines...
+ // renderer = (BarRenderer) plot.getRenderer();
+ renderer = new ColoredBarRenderer(colorMap);
+ plot.setRenderer(renderer);
+ renderer.setDrawBarOutline(false);
+
+ final CategoryAxis domainAxis = plot.getDomainAxis();
+ domainAxis.setCategoryLabelPositions(
+ CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0));
+ // OPTIONAL CUSTOMISATION COMPLETED.
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ public void update() {
+ for (int i = 0; i < sources.size(); i++) {
+ Source source = sources.get(i);
+ double d = source.getDouble();
+ String category = categories.get(i);
+
+ dataset.addValue(d, yaxis, category);
+ }
+ }
+
+ private abstract class Source {
+ // public String label;
+ public Enum> vId;
+ protected boolean isUpdatable;
+
+ public abstract double getDouble();
+
+ }
+
+ private class DSource extends Source {
+ public IDoubleSource source;
+
+ public DSource(String label, IDoubleSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleValue(vId);
+ }
+ }
+
+ private class FSource extends Source {
+ public IFloatSource source;
+
+ public FSource(String label, IFloatSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getFloatValue(vId);
+ }
+ }
+
+ private class ISource extends Source {
+ public IIntSource source;
+
+ public ISource(String label, IIntSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getIntValue(vId);
+ }
+ }
+
+ private class LSource extends Source {
+ public ILongSource source;
+
+ public LSource(String label, ILongSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getLongValue(vId);
+ }
+ }
+
+ /**
+ * Build a series retrieving data from a IDoubleSource object, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDoubleSource
+ * interface.
+ */
+ public void addSources(String legend, IDoubleSource plottableObject) {
+ sources.add(new DSource(legend, plottableObject, IDoubleSource.Variables.Default));
+ // set up gradient paints for series...
+ categories.add(legend);
+ }
+
+ /**
+ * Build a series from a generic object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param target
+ * The data source object.
+ * @param variableName
+ * The variable or method name of the source object.
+ * @param getFromMethod
+ * Specifies if the variableName is a field or a method.
+ */
+ public void addSources(String legend, Object target, String variableName,
+ boolean getFromMethod) {
+ Source source = null;
+ if (ReflectionUtils.isDoubleSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new DSource(legend, new DoubleInvoker(target,
+ variableName, getFromMethod), IDoubleSource.Variables.Default);
+ else if (ReflectionUtils.isFloatSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new FSource(legend, new FloatInvoker(target, variableName,
+ getFromMethod), IFloatSource.Variables.Default);
+ else if (ReflectionUtils.isIntSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new ISource(legend, new IntegerInvoker(target,
+ variableName, getFromMethod), IIntSource.Variables.Default);
+ else if (ReflectionUtils.isLongSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new LSource(legend, new LongInvoker(target, variableName,
+ getFromMethod), ILongSource.Variables.Default);
+ else
+ throw new IllegalArgumentException("The target object " + target
+ + " does not provide a value of a valid data type.");
+
+ }
+
+ // --------------------------------------------------------------------------
+ //
+ // Methods to specify colour of each bar (source)
+ //
+ // --------------------------------------------------------------------------
+
+ /**
+ * Build a series retrieving data from a IDoubleSource object, using the
+ * default variableId and specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDoubleSource
+ * interface.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, IDoubleSource plottableObject, Color color) {
+ addSources(legend, plottableObject);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series retrieving data from a IDoubleSource object and specifying the
+ * colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDoubleSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, IDoubleSource plottableObject,
+ Enum> variableID, Color color) {
+ sources.add(new DSource(legend, plottableObject, variableID));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a IFloatSource object, using the default variableId and
+ * specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IFloatSource
+ * interface.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, IFloatSource plottableObject, Color color) {
+ sources.add(new FSource(legend, plottableObject, IFloatSource.Variables.Default));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a IFloatSource object and specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IFloatSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, IFloatSource plottableObject,
+ Enum> variableID, Color color) {
+ sources.add(new FSource(legend, plottableObject, variableID));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a ILongSource object, using the default variableId and
+ * specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the ILongSource
+ * interface.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, ILongSource plottableObject, Color color) {
+ sources.add(new LSource(legend, plottableObject, ILongSource.Variables.Default));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a ILongSource object and specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDblSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, ILongSource plottableObject,
+ Enum> variableID, Color color) {
+ sources.add(new LSource(legend, plottableObject, variableID));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a IIntSource object, using the default variableId and
+ * specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IIntSource
+ * interface.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, IIntSource plottableObject, Color color) {
+ sources.add(new ISource(legend, plottableObject, IIntSource.Variables.Default));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a IIntSource object and specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IIntSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, IIntSource plottableObject,
+ Enum> variableID, Color color) {
+ sources.add(new ISource(legend, plottableObject, variableID));
+ categories.add(legend);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ /**
+ * Build a series from a generic object and specifying the colour.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param target
+ * The data source object.
+ * @param variableName
+ * The variable or method name of the source object.
+ * @param getFromMethod
+ * Specifies if the variableName is a field or a method.
+ * @param color
+ * Specifies the color of the bar
+ */
+ public void addSources(String legend, Object target, String variableName,
+ boolean getFromMethod, Color color) {
+ addSources(legend, target, variableName, getFromMethod);
+ int seriesNum = sources.size() - 1; // Start with value of 0
+ colorMap.addColor(seriesNum, color);
+ }
+
+ class ColoredBarRenderer extends BarRenderer {
+
+ private static final long serialVersionUID = -7678490515617294057L;
+
+ private FixedColorMap colormap;
+
+ ColoredBarRenderer(FixedColorMap colormap) {
+ this.colormap = colormap;
+ }
+
+ public Paint getItemPaint(final int row, final int column) {
+ // returns color for each column
+ return (colormap.getColor(column));
+ // return (colormap.getMappedColor(column));
+ }
+ }
+}
diff --git a/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotter.java b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotter.java
new file mode 100644
index 00000000..4789115b
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotter.java
@@ -0,0 +1,764 @@
+package microsim.gui.plot;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+import javax.swing.JInternalFrame;
+
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.reflection.ReflectionUtils;
+import microsim.statistics.IDoubleSource;
+import microsim.statistics.IFloatSource;
+import microsim.statistics.IIntSource;
+import microsim.statistics.ILongSource;
+import microsim.statistics.IUpdatableSource;
+import microsim.statistics.reflectors.DoubleInvoker;
+import microsim.statistics.reflectors.FloatInvoker;
+import microsim.statistics.reflectors.IntegerInvoker;
+import microsim.statistics.reflectors.LongInvoker;
+
+import org.apache.commons.math3.util.Pair;
+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.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.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+/**
+ * A ScatterplotSimulationPlotter is able to trace one or more pairs of data
+ * sources
+ * over time, creating a scatterplot chart. It is based on JFreeChart library
+ * and
+ * uses data sources based on the microsim.statistics.* interfaces.
+ *
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Ross Richardson
+ *
+ */
+public class ScatterplotSimulationPlotter extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private ArrayList> sources;
+
+ private XYSeriesCollection dataset;
+
+ private int maxSamples;
+
+ /**
+ * Constructor for scatterplot chart objects with chart legend displayed by
+ * default and
+ * all data samples shown, accumulating as time moves forward. If it is desired
+ * to turn
+ * the legend off, or set a limit to the number of previous time-steps of data
+ * displayed
+ * in the chart, use the constructor
+ * ScatterplotSimulationPlotter(String title, String xaxis, String yaxis,
+ * boolean includeLegend, int maxSamples)
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ *
+ */
+ public ScatterplotSimulationPlotter(String title, String xaxis, String yaxis) { // Includes legend by default and
+ // will accumulate data samples by
+ // default (if wanting only the most
+ // recent data points, use the other
+ // constructor)
+ this(title, xaxis, yaxis, true, 0);
+ }
+
+ /**
+ * Constructor for scatterplot chart objects, featuring a toggle to hide the
+ * chart legend
+ * and to set the number of previous time-steps of data to display in the chart.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ * @param includeLegend - toggles whether to include the legend. If displaying a
+ * very large number of different series in the chart, it
+ * may be useful to turn
+ * the legend off as it will occupy a lot of space in the
+ * GUI.
+ * @param maxSamples - the number of 'snapshots' of data displayed in the
+ * chart.
+ * Only data from the last 'maxSamples' updates will be
+ * displayed in the chart,
+ * so if the chart is updated at each 'time-step', then
+ * only the most recent
+ * 'maxSamples' time-steps will be shown on the chart. If
+ * the user wishes to
+ * accumulate all data points from the simulation run, i.e.
+ * to display all
+ * available data from all previous time-steps, set this to
+ * 0.
+ */
+ public ScatterplotSimulationPlotter(String title, String xaxis, String yaxis, boolean includeLegend,
+ int maxSamples) { // Can specify whether to include legend and how many samples (updates) to
+ // display
+ super();
+ this.setResizable(true);
+ this.setTitle(title);
+ this.maxSamples = maxSamples;
+
+ sources = new ArrayList>();
+
+ dataset = new XYSeriesCollection();
+
+ final JFreeChart chart = ChartFactory.createScatterPlot(
+ title, // chart title
+ xaxis, // x axis label
+ yaxis, // y axis label
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ includeLegend, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final XYPlot plot = chart.getXYPlot();
+ plot.setBackgroundPaint(Color.lightGray);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+
+ final XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true); // Shapes only
+ // renderer.setSeriesLinesVisible(0, false);
+ // renderer.setSeriesShapesVisible(1, false);
+ plot.setRenderer(renderer);
+
+ final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
+ domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ public void update() {
+ double x = 0.0, y = 0.0;
+ for (int i = 0; i < sources.size(); i++) {
+ Source source_X = sources.get(i).getFirst();
+ Source source_Y = sources.get(i).getSecond();
+ XYSeries series = dataset.getSeries(i);
+ x = source_X.getDouble();
+ y = source_Y.getDouble();
+ series.add(x, y);
+ // if (maxSamples > 0 && series.getItemCount() > maxSamples ) { //Should no
+ // longer be necessary if using XYSeries.setMaximumItemCount()
+ // XYDataItem xy = series.remove(0);
+ // System.out.println(series.getItemCount() + ", (" + xy.getXValue() + ", " +
+ // xy.getYValue() + ")");
+ // }
+ }
+ }
+
+ private abstract class Source {
+ // public String label;
+ public Enum> vId;
+ protected boolean isUpdatable;
+
+ public abstract double getDouble();
+
+ // public String getLabel() {
+ // return label;
+ // }
+ //
+ // public void setLabel(String string) {
+ // label = string;
+ // }
+
+ }
+
+ private class DSource extends Source {
+ public IDoubleSource source;
+
+ public DSource(String label, IDoubleSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleValue(vId);
+ }
+ }
+
+ private class FSource extends Source {
+ public IFloatSource source;
+
+ public FSource(String label, IFloatSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getFloatValue(vId);
+ }
+ }
+
+ private class ISource extends Source {
+ public IIntSource source;
+
+ public ISource(String label, IIntSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getIntValue(vId);
+ }
+ }
+
+ private class LSource extends Source {
+ public ILongSource source;
+
+ public LSource(String label, ILongSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getLongValue(vId);
+ }
+ }
+
+ /**
+ * Build a series of paired values, retrieving data from two IDoubleSource
+ * objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X, IDoubleSource plottableObject_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default);
+ DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving data from two IDoubleSource
+ * objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X,
+ Enum> variableID_X, IDoubleSource plottableObject_Y, Enum> variableID_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, variableID_X);
+ DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IFloatSource objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IFloatSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IFloatSource
+ * interface to produce values for the y-axis (range).
+ *
+ */
+ public void addSeries(String legend, IFloatSource plottableObject_X, IFloatSource plottableObject_Y) {
+ // sources.add(new FSource(legend, plottableObject,
+ // IFloatSource.Variables.Default));
+ FSource sourceX = new FSource(legend, plottableObject_X, IFloatSource.Variables.Default);
+ FSource sourceY = new FSource(legend, plottableObject_Y, IFloatSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IFloatSource objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IFloatSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IFloatSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, IFloatSource plottableObject_X,
+ Enum> variableID_X, IFloatSource plottableObject_Y, Enum> variableID_Y) {
+ FSource sourceX = new FSource(legend, plottableObject_X, variableID_X);
+ FSource sourceY = new FSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two ILongSource objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface producing values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface producing values of the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X, ILongSource plottableObject_Y) {
+ // sources.add(new LSource(legend, plottableObject,
+ // ILongSource.Variables.Default));
+ LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default);
+ LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two ILongSource objects
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X,
+ Enum> variableID_X, ILongSource plottableObject_Y, Enum> variableID_Y) {
+ LSource sourceX = new LSource(legend, plottableObject_X, variableID_X);
+ LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IIntSource objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the y-axis (range).
+ */
+ public void addSeries(String legend, IIntSource plottableObject_X, IIntSource plottableObject_Y) {
+ // sources.add(new ISource(legend, plottableObject,
+ // IIntSource.Variables.Default));
+ ISource sourceX = new ISource(legend, plottableObject_X, IIntSource.Variables.Default);
+ ISource sourceY = new ISource(legend, plottableObject_Y, IIntSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IIntSource objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ *
+ */
+ public void addSeries(String legend, IIntSource plottableObject_X,
+ Enum> variableID_X, IIntSource plottableObject_Y, Enum> variableID_Y) {
+ // sources.add(new ISource(legend, plottableObject, variableID));
+ ISource sourceX = new ISource(legend, plottableObject_X, variableID_X);
+ ISource sourceY = new ISource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two generic objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param target_X
+ * The data source object for x-axis values (domain).
+ * @param variableName_X
+ * The variable or method name of the source object
+ * producing
+ * values for the x-axis (domain).
+ * @param getFromMethod_X
+ * Specifies if the variableName_X is a field or a
+ * method.
+ * @param target_Y
+ * The data source object for y-axis values (range).
+ * @param variableName_Y
+ * The variable or method name of the source object
+ * producing
+ * values for the y-axis (range).
+ * @param getFromMethod_Y
+ * Specifies if the variableName_Y is a field or a
+ * method.
+ */
+ public void addSeries(String legend, Object target_X, String variableName_X,
+ boolean getFromMethod_X, Object target_Y, String variableName_Y,
+ boolean getFromMethod_Y) {
+
+ // First, look at X values
+ Source sourceX = null;
+ if (ReflectionUtils.isDoubleSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new DSource(legend, new DoubleInvoker(target_X,
+ variableName_X, getFromMethod_X), IDoubleSource.Variables.Default);
+ else if (ReflectionUtils.isFloatSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new FSource(legend, new FloatInvoker(target_X, variableName_X,
+ getFromMethod_X), IFloatSource.Variables.Default);
+ else if (ReflectionUtils.isIntSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new ISource(legend, new IntegerInvoker(target_X,
+ variableName_X, getFromMethod_X), IIntSource.Variables.Default);
+ else if (ReflectionUtils.isLongSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new LSource(legend, new LongInvoker(target_X, variableName_X,
+ getFromMethod_X), ILongSource.Variables.Default);
+ else
+ throw new IllegalArgumentException("The target_X object " + target_X
+ + " does not provide a value of a valid data type.");
+
+ // Now for Y values
+ Source sourceY = null;
+ if (ReflectionUtils.isDoubleSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new DSource(legend, new DoubleInvoker(target_Y,
+ variableName_Y, getFromMethod_Y), IDoubleSource.Variables.Default);
+ else if (ReflectionUtils.isFloatSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new FSource(legend, new FloatInvoker(target_Y, variableName_Y,
+ getFromMethod_Y), IFloatSource.Variables.Default);
+ else if (ReflectionUtils.isIntSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new ISource(legend, new IntegerInvoker(target_Y,
+ variableName_Y, getFromMethod_Y), IIntSource.Variables.Default);
+ else if (ReflectionUtils.isLongSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new LSource(legend, new LongInvoker(target_Y, variableName_Y,
+ getFromMethod_Y), ILongSource.Variables.Default);
+ else
+ throw new IllegalArgumentException("The target_Y object " + target_Y
+ + " does not provide a value of a valid data type.");
+
+ // sources.add(source);
+ sources.add(new Pair(sourceX, sourceY));
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ // plot.addLegend(sources.size() - 1, legend);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an IDoubleSource
+ * object and y-axis data
+ * from an ILongSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface to produce values for the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X, ILongSource plottableObject_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default);
+ LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an IDoubleSource
+ * object and y-axis data
+ * from an ILongSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X,
+ Enum> variableID_X, ILongSource plottableObject_Y, Enum> variableID_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, variableID_X);
+ LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an ILongSource
+ * object and
+ * y-axis data from an IDoubleSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X, IDoubleSource plottableObject_Y) {
+ LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default);
+ DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an ILongSource
+ * object and
+ * y-axis data from an IDoubleSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X,
+ Enum> variableID_X, IDoubleSource plottableObject_Y, Enum> variableID_Y) {
+ LSource sourceX = new LSource(legend, plottableObject_X, variableID_X);
+ DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Max samples parameters allow to define a maximum number of time-steps used
+ * in the scatter plot. When set, the oldest data points are removed as time
+ * moves forward to maintain the number of samples (time-steps) in the chart.
+ */
+ public int getMaxSamples() {
+ return maxSamples;
+ }
+
+ /**
+ * Set the max sample parameter.
+ *
+ * @param maxSamples Maximum number of time-steps rendered on x axis.
+ */
+ public void setMaxSamples(int maxSamples) {
+ this.maxSamples = maxSamples;
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotterRefreshable.java b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotterRefreshable.java
new file mode 100644
index 00000000..83613320
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotterRefreshable.java
@@ -0,0 +1,791 @@
+package microsim.gui.plot;
+
+//package microsim.gui.plot;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.reflection.ReflectionUtils;
+import microsim.statistics.IDoubleSource;
+import microsim.statistics.IFloatSource;
+import microsim.statistics.IIntSource;
+import microsim.statistics.ILongSource;
+import microsim.statistics.IUpdatableSource;
+import microsim.statistics.reflectors.DoubleInvoker;
+import microsim.statistics.reflectors.FloatInvoker;
+import microsim.statistics.reflectors.IntegerInvoker;
+import microsim.statistics.reflectors.LongInvoker;
+
+import org.apache.commons.math3.util.Pair;
+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.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.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+/**
+ * A ScatterplotSimulationPlotterRefreshable is able to trace one or more pairs
+ * of data sources
+ * between scheduled simulation time-steps, creating a scatterplot chart that is
+ * updated on
+ * demand (e.g. for visualising the progress of the simulation between
+ * time-steps).
+ * It is based on ScatterplotSimulationPlotter, which is itself based on
+ * JFreeChart library and
+ * uses data sources based on the microsim.statistics.* interfaces.
+ *
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Ross Richardson
+ *
+ */
+public class ScatterplotSimulationPlotterRefreshable extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private ArrayList> sources;
+
+ private XYSeriesCollection dataset;
+
+ private int maxSamples;
+
+ /**
+ * Constructor for scatterplot chart objects with chart legend displayed by
+ * default and
+ * all data samples shown, accumulating as time moves forward. If it is desired
+ * to turn
+ * the legend off, or set a limit to the number of previous time-steps of data
+ * displayed
+ * in the chart, use the constructor
+ * ScatterplotSimulationPlotter(String title, String xaxis, String yaxis,
+ * boolean includeLegend, int maxSamples)
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ *
+ */
+ public ScatterplotSimulationPlotterRefreshable(String title, String xaxis, String yaxis) { // Includes legend by
+ // default and will
+ // accumulate data
+ // samples by default (if
+ // wanting only the most
+ // recent data points,
+ // use the other
+ // constructor)
+ this(title, xaxis, yaxis, true, 0);
+ }
+
+ /**
+ * Constructor for scatterplot chart objects, featuring a toggle to hide the
+ * chart legend
+ * and to set the number of previous time-steps of data to display in the chart.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ * @param includeLegend - toggles whether to include the legend. If displaying a
+ * very large number of different series in the chart, it
+ * may be useful to turn
+ * the legend off as it will occupy a lot of space in the
+ * GUI.
+ * @param maxSamples - the number of 'snapshots' of data displayed in the
+ * chart.
+ * Only data from the last 'maxSamples' updates will be
+ * displayed in the chart,
+ * so if the chart is updated at each 'time-step', then
+ * only the most recent
+ * 'maxSamples' time-steps will be shown on the chart. If
+ * the user wishes to
+ * accumulate all data points from the simulation run, i.e.
+ * to display all
+ * available data from all previous time-steps, set this to
+ * 0.
+ */
+ public ScatterplotSimulationPlotterRefreshable(String title, String xaxis, String yaxis, boolean includeLegend,
+ int maxSamples) { // Can specify whether to include legend and how many samples (updates) to
+ // display
+ super();
+ this.setResizable(true);
+ this.setTitle(title);
+ this.maxSamples = maxSamples;
+
+ sources = new ArrayList>();
+
+ dataset = new XYSeriesCollection();
+
+ final JFreeChart chart = ChartFactory.createScatterPlot(
+ title, // chart title
+ xaxis, // x axis label
+ yaxis, // y axis label
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ includeLegend, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final XYPlot plot = chart.getXYPlot();
+ plot.setBackgroundPaint(Color.lightGray);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+
+ final XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true); // Shapes only
+ // renderer.setSeriesLinesVisible(0, false);
+ // renderer.setSeriesShapesVisible(1, false);
+ plot.setRenderer(renderer);
+
+ final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
+ domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ public void update() {
+ double x = 0.0, y = 0.0;
+ for (int i = 0; i < sources.size(); i++) {
+ Source source_X = sources.get(i).getFirst();
+ Source source_Y = sources.get(i).getSecond();
+ XYSeries series = dataset.getSeries(i);
+ x = source_X.getDouble();
+ y = source_Y.getDouble();
+ series.add(x, y);
+ // if (maxSamples > 0 && series.getItemCount() > maxSamples ) { //Should no
+ // longer be necessary if using XYSeries.setMaximumItemCount()
+ // XYDataItem xy = series.remove(0);
+ // System.out.println(series.getItemCount() + ", (" + xy.getXValue() + ", " +
+ // xy.getYValue() + ")");
+ // }
+ }
+ }
+
+ private abstract class Source {
+ // public String label;
+ public Enum> vId;
+ protected boolean isUpdatable;
+
+ public abstract double getDouble();
+
+ // public String getLabel() {
+ // return label;
+ // }
+ //
+ // public void setLabel(String string) {
+ // label = string;
+ // }
+
+ }
+
+ private class DSource extends Source {
+ public IDoubleSource source;
+
+ public DSource(String label, IDoubleSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleValue(vId);
+ }
+ }
+
+ private class FSource extends Source {
+ public IFloatSource source;
+
+ public FSource(String label, IFloatSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getFloatValue(vId);
+ }
+ }
+
+ private class ISource extends Source {
+ public IIntSource source;
+
+ public ISource(String label, IIntSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getIntValue(vId);
+ }
+ }
+
+ private class LSource extends Source {
+ public ILongSource source;
+
+ public LSource(String label, ILongSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getLongValue(vId);
+ }
+ }
+
+ /**
+ * Build a series of paired values, retrieving data from two IDoubleSource
+ * objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X, IDoubleSource plottableObject_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default);
+ DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving data from two IDoubleSource
+ * objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X,
+ Enum> variableID_X, IDoubleSource plottableObject_Y, Enum> variableID_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, variableID_X);
+ DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IFloatSource objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IFloatSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IFloatSource
+ * interface to produce values for the y-axis (range).
+ *
+ */
+ public void addSeries(String legend, IFloatSource plottableObject_X, IFloatSource plottableObject_Y) {
+ // sources.add(new FSource(legend, plottableObject,
+ // IFloatSource.Variables.Default));
+ FSource sourceX = new FSource(legend, plottableObject_X, IFloatSource.Variables.Default);
+ FSource sourceY = new FSource(legend, plottableObject_Y, IFloatSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IFloatSource objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IFloatSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IFloatSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, IFloatSource plottableObject_X,
+ Enum> variableID_X, IFloatSource plottableObject_Y, Enum> variableID_Y) {
+ FSource sourceX = new FSource(legend, plottableObject_X, variableID_X);
+ FSource sourceY = new FSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two ILongSource objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface producing values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface producing values of the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X, ILongSource plottableObject_Y) {
+ // sources.add(new LSource(legend, plottableObject,
+ // ILongSource.Variables.Default));
+ LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default);
+ LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two ILongSource objects
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X,
+ Enum> variableID_X, ILongSource plottableObject_Y, Enum> variableID_Y) {
+ LSource sourceX = new LSource(legend, plottableObject_X, variableID_X);
+ LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IIntSource objects, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the y-axis (range).
+ */
+ public void addSeries(String legend, IIntSource plottableObject_X, IIntSource plottableObject_Y) {
+ // sources.add(new ISource(legend, plottableObject,
+ // IIntSource.Variables.Default));
+ ISource sourceX = new ISource(legend, plottableObject_X, IIntSource.Variables.Default);
+ ISource sourceY = new ISource(legend, plottableObject_Y, IIntSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two IIntSource objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the IIntSource
+ * interface
+ * producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ *
+ */
+ public void addSeries(String legend, IIntSource plottableObject_X,
+ Enum> variableID_X, IIntSource plottableObject_Y, Enum> variableID_Y) {
+ // sources.add(new ISource(legend, plottableObject, variableID));
+ ISource sourceX = new ISource(legend, plottableObject_X, variableID_X);
+ ISource sourceY = new ISource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values from two generic objects.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param target_X
+ * The data source object for x-axis values (domain).
+ * @param variableName_X
+ * The variable or method name of the source object
+ * producing
+ * values for the x-axis (domain).
+ * @param getFromMethod_X
+ * Specifies if the variableName_X is a field or a
+ * method.
+ * @param target_Y
+ * The data source object for y-axis values (range).
+ * @param variableName_Y
+ * The variable or method name of the source object
+ * producing
+ * values for the y-axis (range).
+ * @param getFromMethod_Y
+ * Specifies if the variableName_Y is a field or a
+ * method.
+ */
+ public void addSeries(String legend, Object target_X, String variableName_X,
+ boolean getFromMethod_X, Object target_Y, String variableName_Y,
+ boolean getFromMethod_Y) {
+
+ // First, look at X values
+ Source sourceX = null;
+ if (ReflectionUtils.isDoubleSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new DSource(legend, new DoubleInvoker(target_X,
+ variableName_X, getFromMethod_X), IDoubleSource.Variables.Default);
+ else if (ReflectionUtils.isFloatSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new FSource(legend, new FloatInvoker(target_X, variableName_X,
+ getFromMethod_X), IFloatSource.Variables.Default);
+ else if (ReflectionUtils.isIntSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new ISource(legend, new IntegerInvoker(target_X,
+ variableName_X, getFromMethod_X), IIntSource.Variables.Default);
+ else if (ReflectionUtils.isLongSource(target_X.getClass(), variableName_X,
+ getFromMethod_X))
+ sourceX = new LSource(legend, new LongInvoker(target_X, variableName_X,
+ getFromMethod_X), ILongSource.Variables.Default);
+ else
+ throw new IllegalArgumentException("The target_X object " + target_X
+ + " does not provide a value of a valid data type.");
+
+ // Now for Y values
+ Source sourceY = null;
+ if (ReflectionUtils.isDoubleSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new DSource(legend, new DoubleInvoker(target_Y,
+ variableName_Y, getFromMethod_Y), IDoubleSource.Variables.Default);
+ else if (ReflectionUtils.isFloatSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new FSource(legend, new FloatInvoker(target_Y, variableName_Y,
+ getFromMethod_Y), IFloatSource.Variables.Default);
+ else if (ReflectionUtils.isIntSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new ISource(legend, new IntegerInvoker(target_Y,
+ variableName_Y, getFromMethod_Y), IIntSource.Variables.Default);
+ else if (ReflectionUtils.isLongSource(target_Y.getClass(), variableName_Y,
+ getFromMethod_Y))
+ sourceY = new LSource(legend, new LongInvoker(target_Y, variableName_Y,
+ getFromMethod_Y), ILongSource.Variables.Default);
+ else
+ throw new IllegalArgumentException("The target_Y object " + target_Y
+ + " does not provide a value of a valid data type.");
+
+ // sources.add(source);
+ sources.add(new Pair(sourceX, sourceY));
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ // plot.addLegend(sources.size() - 1, legend);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an IDoubleSource
+ * object and y-axis data
+ * from an ILongSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface to produce values for the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X, ILongSource plottableObject_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default);
+ LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an IDoubleSource
+ * object and y-axis data
+ * from an ILongSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the ILongSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject_X,
+ Enum> variableID_X, ILongSource plottableObject_Y, Enum> variableID_Y) {
+ DSource sourceX = new DSource(legend, plottableObject_X, variableID_X);
+ LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an ILongSource
+ * object and
+ * y-axis data from an IDoubleSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface to produce values for the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface to produce values for the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X, IDoubleSource plottableObject_Y) {
+ LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default);
+ DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series of paired values, retrieving x-axis data from an ILongSource
+ * object and
+ * y-axis data from an IDoubleSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject_X
+ * The data source object implementing the ILongSource
+ * interface producing values of the x-axis (domain).
+ * @param variableID_X
+ * The variable id of the source object producing
+ * values of the x-axis (domain).
+ * @param plottableObject_Y
+ * The data source object implementing the
+ * IDoubleSource
+ * interface producing values of the y-axis (range).
+ * @param variableID_Y
+ * The variable id of the source object producing
+ * values of the y-axis (range).
+ */
+ public void addSeries(String legend, ILongSource plottableObject_X,
+ Enum> variableID_X, IDoubleSource plottableObject_Y, Enum> variableID_Y) {
+ LSource sourceX = new LSource(legend, plottableObject_X, variableID_X);
+ DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y);
+ sources.add(new Pair(sourceX, sourceY));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Max samples parameters allow to define a maximum number of time-steps used
+ * in the scatter plot. When set, the oldest data points are removed as time
+ * moves forward to maintain the number of samples (time-steps) in the chart.
+ */
+ public int getMaxSamples() {
+ return maxSamples;
+ }
+
+ /**
+ * Set the max sample parameter.
+ *
+ * @param maxSamples Maximum number of time-steps rendered on x axis.
+ */
+ public void setMaxSamples(int maxSamples) {
+ this.maxSamples = maxSamples;
+ }
+
+ public void refresh() {
+
+ List data = dataset.getSeries();
+ for (int i = 0; i < data.size(); i++) {
+ XYSeries series = (XYSeries) data.get(i);
+ series.clear();
+ }
+
+ }
+
+ public void reset() {
+
+ dataset.removeAllSeries();
+ sources.clear();
+
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/TimeSeriesSimulationPlotter.java b/src/main/java/microsim/gui/plot/TimeSeriesSimulationPlotter.java
new file mode 100644
index 00000000..74386e3e
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/TimeSeriesSimulationPlotter.java
@@ -0,0 +1,550 @@
+package microsim.gui.plot;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+
+import javax.swing.JInternalFrame;
+
+import microsim.engine.SimulationEngine;
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.reflection.ReflectionUtils;
+import microsim.statistics.IDoubleSource;
+import microsim.statistics.IFloatSource;
+import microsim.statistics.IIntSource;
+import microsim.statistics.ILongSource;
+import microsim.statistics.IUpdatableSource;
+import microsim.statistics.reflectors.DoubleInvoker;
+import microsim.statistics.reflectors.FloatInvoker;
+import microsim.statistics.reflectors.IntegerInvoker;
+import microsim.statistics.reflectors.LongInvoker;
+
+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.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+/**
+ * A time series plotter is able to trace one or more data sources over time. It
+ * is based on JFreeChart library and uses data sources based on the
+ * microsim.statistics.* interfaces.
+ *
+ *
+ *
+ * Copyright (C) 2002-17 Michele Sonnessa and Ross Richardson
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa and Ross Richardson
+ *
+ */
+public class TimeSeriesSimulationPlotter extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private ArrayList sources;
+
+ private XYSeriesCollection dataset;
+
+ private XYPlot plot;
+
+ private XYLineAndShapeRenderer renderer;
+
+ private int maxSamples = 0;
+
+ public TimeSeriesSimulationPlotter(String title, String yaxis) { // Include legend by default
+ this(title, yaxis, true, 0);
+ }
+
+ public TimeSeriesSimulationPlotter(String title, String yaxis, boolean includeLegend, int maxSamples) { // Can
+ // specify
+ // whether
+ // to
+ // include
+ // legend
+ super();
+ this.setResizable(true);
+ this.setTitle(title);
+ this.maxSamples = maxSamples;
+
+ sources = new ArrayList();
+
+ dataset = new XYSeriesCollection();
+
+ final JFreeChart chart = ChartFactory.createXYLineChart(
+ title, // chart title
+ "Simulation time", // x axis label
+ yaxis, // y axis label
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ includeLegend, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // String fontName = chart.getLegend().getItemFont().getFontName();
+ // int style = chart.getLegend().getItemFont().getStyle();
+ // int size = chart.getLegend().getItemFont().getSize();
+ // chart.getLegend().setItemFont(new Font(fontName, style,
+ // (int)MicrosimShell.scale*size));
+
+ // get a reference to the plot for further customisation...
+ plot = chart.getXYPlot();
+ plot.setBackgroundPaint(Color.lightGray);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+
+ renderer = new XYLineAndShapeRenderer();
+ // renderer.setSeriesLinesVisible(0, false);
+ // renderer.setSeriesShapesVisible(1, false);
+
+ plot.setRenderer(renderer);
+
+ // change the auto tick unit selection to integer units only...
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ // rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); // Ross - made this change to allow units
+ // on Y axis for finer ticks, which is
+ // especially important for timeseries
+ // with values < 1.
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ double d = 0.0;
+ for (int i = 0; i < sources.size(); i++) {
+ Source source = sources.get(i);
+ XYSeries series = dataset.getSeries(i);
+ d = source.getDouble();
+ series.add(SimulationEngine.getInstance().getTime(), d);
+ // if (maxSamples > 0 && series.getItemCount() > maxSamples ) {
+ // series.remove(0);
+ // }
+ }
+ }
+ }
+
+ private abstract class Source {
+ // public String label;
+ public Enum> vId;
+ protected boolean isUpdatable;
+
+ public abstract double getDouble();
+
+ // public String getLabel() {
+ // return label;
+ // }
+ //
+ // public void setLabel(String string) {
+ // label = string;
+ // }
+
+ }
+
+ private class DSource extends Source {
+ public IDoubleSource source;
+
+ public DSource(String label, IDoubleSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleValue(vId);
+ }
+ }
+
+ private class FSource extends Source {
+ public IFloatSource source;
+
+ public FSource(String label, IFloatSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getFloatValue(vId);
+ }
+ }
+
+ private class ISource extends Source {
+ public IIntSource source;
+
+ public ISource(String label, IIntSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getIntValue(vId);
+ }
+ }
+
+ private class LSource extends Source {
+ public ILongSource source;
+
+ public LSource(String label, ILongSource source, Enum> varId) {
+ // super.label = label;
+ this.source = source;
+ super.vId = varId;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double getDouble() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getLongValue(vId);
+ }
+ }
+
+ /**
+ * Build a series retrieving data from a IDoubleSource object, using the
+ * default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDoubleSource
+ * interface.
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject) {
+ sources.add(new DSource(legend, plottableObject, IDoubleSource.Variables.Default));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ public void addSeries(String legend, IDoubleSource plottableObject, Color lineColor, boolean shapesFilled,
+ boolean isDashed, Shape shape) {
+ sources.add(new DSource(legend, plottableObject, IDoubleSource.Variables.Default));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+
+ int seriesIndex = dataset.getSeriesIndex(series.getKey()); // Get int Index of series using its key
+ Stroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f,
+ new float[] { 10.0f }, 0.0f);
+ getRenderer().setSeriesPaint(seriesIndex, lineColor); // Set color of the series in the renderer to what was
+ // requested
+ getRenderer().setSeriesShapesFilled(seriesIndex, shapesFilled); // Set if shapes should be filled or not
+ if (isDashed) {
+ getRenderer().setSeriesStroke(seriesIndex, dashed);
+ }
+ getRenderer().setSeriesShape(seriesIndex, shape);
+ }
+
+ /**
+ * Build a series retrieving data from a IDoubleSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDoubleSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ */
+ public void addSeries(String legend, IDoubleSource plottableObject,
+ Enum> variableID) {
+ sources.add(new DSource(legend, plottableObject, variableID));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ public void addSeries(String legend, IDoubleSource plottableObject, Enum> variableID, Color lineColor,
+ boolean shapesFilled, boolean isDashed, Shape shape) {
+ sources.add(new DSource(legend, plottableObject, variableID));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ int seriesIndex = dataset.getSeriesIndex(series.getKey()); // Get int Index of series using its key
+
+ Stroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f,
+ new float[] { 10.0f }, 0.0f);
+ getRenderer().setSeriesPaint(seriesIndex, lineColor); // Set color of the series in the renderer to what was
+ // requested
+ getRenderer().setSeriesShapesFilled(seriesIndex, shapesFilled); // Set if shapes should be filled or not
+ if (isDashed) {
+ getRenderer().setSeriesStroke(seriesIndex, dashed);
+ }
+ getRenderer().setSeriesShape(seriesIndex, shape);
+
+ }
+
+ public void addSeries(String legend, IDoubleSource plottableObject, Enum> variableID, Color lineColor,
+ boolean validation) {
+ if (validation) {
+ Shape myRectangle = new Rectangle2D.Float(-3, -3, 6, 6);
+ addSeries(legend, plottableObject, variableID, lineColor, false, true, myRectangle);
+ } else {
+ Shape myCircle = new Ellipse2D.Float(-3, -3, 6, 6);
+ addSeries(legend, plottableObject, lineColor, true, false, myCircle);
+
+ }
+
+ }
+
+ /**
+ * Build a series from a IFloatSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IFloatSource
+ * interface.
+ */
+ public void addSeries(String legend, IFloatSource plottableObject) {
+ sources.add(new FSource(legend, plottableObject, IFloatSource.Variables.Default));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series from a IFloatSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IFloatSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ */
+ public void addSeries(String legend, IFloatSource plottableObject,
+ Enum> variableID) {
+ sources.add(new FSource(legend, plottableObject, variableID));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series from a ILongSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the ILongSource
+ * interface.
+ */
+ public void addSeries(String legend, ILongSource plottableObject) {
+ sources.add(new LSource(legend, plottableObject, ILongSource.Variables.Default));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series from a ILongSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IDblSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ */
+ public void addSeries(String legend, ILongSource plottableObject,
+ Enum> variableID) {
+ sources.add(new LSource(legend, plottableObject, variableID));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series from a IIntSource object, using the default variableId.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IIntSource
+ * interface.
+ */
+ public void addSeries(String legend, IIntSource plottableObject) {
+ sources.add(new ISource(legend, plottableObject, IIntSource.Variables.Default));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series from a IIntSource object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param plottableObject
+ * The data source object implementing the IIntSource
+ * interface.
+ * @param variableID
+ * The variable id of the source object.
+ */
+ public void addSeries(String legend, IIntSource plottableObject,
+ Enum> variableID) {
+ sources.add(new ISource(legend, plottableObject, variableID));
+ // plot.addLegend(sources.size() - 1, legend);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ }
+
+ /**
+ * Build a series from a generic object.
+ *
+ * @param legend
+ * The legend name of the series.
+ * @param target
+ * The data source object.
+ * @param variableName
+ * The variable or method name of the source object.
+ * @param getFromMethod
+ * Specifies if the variableName is a field or a method.
+ */
+ public void addSeries(String legend, Object target, String variableName,
+ boolean getFromMethod) {
+ Source source = null;
+ if (ReflectionUtils.isDoubleSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new DSource(legend, new DoubleInvoker(target,
+ variableName, getFromMethod), IDoubleSource.Variables.Default);
+ else if (ReflectionUtils.isFloatSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new FSource(legend, new FloatInvoker(target, variableName,
+ getFromMethod), IFloatSource.Variables.Default);
+ else if (ReflectionUtils.isIntSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new ISource(legend, new IntegerInvoker(target,
+ variableName, getFromMethod), IIntSource.Variables.Default);
+ else if (ReflectionUtils.isLongSource(target.getClass(), variableName,
+ getFromMethod))
+ source = new LSource(legend, new LongInvoker(target, variableName,
+ getFromMethod), ILongSource.Variables.Default);
+ else
+ throw new IllegalArgumentException("The target object " + target
+ + " does not provide a value of a valid data type.");
+
+ sources.add(source);
+ XYSeries series = new XYSeries(legend);
+ if (maxSamples > 0)
+ series.setMaximumItemCount(maxSamples);
+ dataset.addSeries(series);
+ // plot.addLegend(sources.size() - 1, legend);
+ }
+
+ /**
+ * Max samples parameters allow to define a maximum number of points.
+ * When set the plotting window shifts automatically along with time.
+ */
+ public int getMaxSamples() {
+ return maxSamples;
+ }
+
+ /**
+ * Set the max sample parameter.
+ *
+ * @param maxSamples Maximum number of time-steps rendered on x axis.
+ */
+ public void setMaxSamples(int maxSamples) {
+ this.maxSamples = maxSamples;
+ }
+
+ public XYLineAndShapeRenderer getRenderer() {
+ return (XYLineAndShapeRenderer) plot.getRenderer();
+ }
+
+ public void setRenderer(XYLineAndShapeRenderer renderer) {
+ plot.setRenderer(renderer);
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/Weighted_HistogramBin.java b/src/main/java/microsim/gui/plot/Weighted_HistogramBin.java
new file mode 100644
index 00000000..c0f2a248
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/Weighted_HistogramBin.java
@@ -0,0 +1,159 @@
+package microsim.gui.plot;
+
+/* Based on JFreeChart : a free chart library for the Java(tm) platform
+* ===========================================================
+*
+* (C) Copyright 2017 by Ross Richardson
+*
+* Based on JFreeChart's HistogramBin by Object Refinery Limited and Contributors.
+*
+* Project Info: http://www.jfree.org/jfreechart/index.html
+*
+* This library is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2.1 of the License, or
+* (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+* License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+* USA.
+*
+* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+* Other names may be trademarks of their respective owners.]
+*
+* -----------------
+* Weighted_HistogramBin.java
+* -----------------
+* (C) Copyright 2017, by Ross Richardson
+* Based on JFreeChart's HistogramBin by Jelai Wang and Contributors.
+*
+* Original Author: Jelai Wang (jelaiw AT mindspring.com);
+* Contributor(s): David Gilbert (for Object Refinery Limited);
+*
+*/
+
+import java.io.Serializable;
+
+/**
+ * A bin for the {@link Weighted_HistogramDataset} class.
+ */
+public class Weighted_HistogramBin implements Cloneable, Serializable {
+
+ /** For serialisation. */
+ private static final long serialVersionUID = 7614685080015589931L;
+
+ /**
+ * The (weighted) number of items in the bin, the weights can be double,
+ * meaning that the count can also be double.
+ **/
+ private double count;
+
+ /** The start boundary. */
+ private double startBoundary;
+
+ /** The end boundary. */
+ private double endBoundary;
+
+ /**
+ * Creates a new bin.
+ *
+ * @param startBoundary the start boundary.
+ * @param endBoundary the end boundary.
+ */
+ public Weighted_HistogramBin(double startBoundary, double endBoundary) {
+ if (startBoundary > endBoundary) {
+ throw new IllegalArgumentException(
+ "HistogramBin(): startBoundary > endBoundary.");
+ }
+ this.count = 0.;
+ this.startBoundary = startBoundary;
+ this.endBoundary = endBoundary;
+ }
+
+ /**
+ * Returns the number of items in the bin.
+ *
+ * @return The item count.
+ */
+ public double getCount() {
+ return this.count;
+ }
+
+ /**
+ * Increments the item count.
+ */
+ public void incrementCount(double weight) {
+ this.count += weight;
+ }
+
+ /**
+ * Returns the start boundary.
+ *
+ * @return The start boundary.
+ */
+ public double getStartBoundary() {
+ return this.startBoundary;
+ }
+
+ /**
+ * Returns the end boundary.
+ *
+ * @return The end boundary.
+ */
+ public double getEndBoundary() {
+ return this.endBoundary;
+ }
+
+ /**
+ * Returns the bin width.
+ *
+ * @return The bin width.
+ */
+ public double getBinWidth() {
+ return this.endBoundary - this.startBoundary;
+ }
+
+ /**
+ * Tests this object for equality with an arbitrary object.
+ *
+ * @param obj the object to test against.
+ *
+ * @return A boolean.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Weighted_HistogramBin) {
+ Weighted_HistogramBin bin = (Weighted_HistogramBin) obj;
+ boolean b0 = bin.startBoundary == this.startBoundary;
+ boolean b1 = bin.endBoundary == this.endBoundary;
+ boolean b2 = bin.count == this.count;
+ return b0 && b1 && b2;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a clone of the bin.
+ *
+ * @return A clone.
+ *
+ * @throws CloneNotSupportedException not thrown by this class.
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/Weighted_HistogramDataset.java b/src/main/java/microsim/gui/plot/Weighted_HistogramDataset.java
new file mode 100644
index 00000000..d0b762fc
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/Weighted_HistogramDataset.java
@@ -0,0 +1,504 @@
+package microsim.gui.plot;
+
+/* (C) Copyright 2017, by Ross Richardson based on JFreeChart's
+ * HistogramDataset.java by Object Refinery Limited and Contributors.
+ *
+ * Project Info: http://www.jfree.org/jfreechart/index.html
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *
+ * ---------------------
+ * Weighted_HistogramDataset.java
+ * ---------------------
+ * (C) Copyright 2017, by Ross Richardson
+ *
+ * Based on JFreeChart's HistogramDataset.java:
+ * (C) Copyright 2003-2013, by Jelai Wang and Contributors.
+ *
+ * Original Author: Jelai Wang (jelaiw AT mindspring.com);
+ * Contributor(s): David Gilbert (for Object Refinery Limited);
+ * Cameron Hayne;
+ * Rikard Bj?rklind;
+ * Thomas A Caswell (patch 2902842);
+ *
+ *
+ */
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.jfree.chart.util.Args;
+import org.jfree.chart.util.PublicCloneable;
+import org.jfree.data.general.DatasetChangeEvent;
+import org.jfree.data.statistics.HistogramType;
+import org.jfree.data.xy.AbstractIntervalXYDataset;
+import org.jfree.data.xy.IntervalXYDataset;
+
+/**
+ * A weighted dataset that can be used for creating weighted histograms.
+ */
+public class Weighted_HistogramDataset extends AbstractIntervalXYDataset
+ implements IntervalXYDataset, Cloneable, PublicCloneable,
+ Serializable {
+ /** For serialization. */
+ private static final long serialVersionUID = -6341668077370231153L;
+
+ /** A list of maps. */
+ private List list;
+
+ /** The histogram type. */
+ private HistogramType type;
+
+ /**
+ * Sum of the total weightings of each value, in order to correctly calculate
+ * the chart for RELATIVE_FREQUENCY type.
+ */
+ private double totalWeight;
+
+ /**
+ * Creates a new (empty) dataset with a default type of
+ * {@link HistogramType}.FREQUENCY.
+ */
+ public Weighted_HistogramDataset() {
+ this.list = new ArrayList();
+ this.type = HistogramType.FREQUENCY;
+ totalWeight = 0.;
+ }
+
+ /**
+ * Returns the histogram type.
+ *
+ * @return The type (never null).
+ */
+ public HistogramType getType() {
+ return this.type;
+ }
+
+ /**
+ * Sets the histogram type and sends a {@link DatasetChangeEvent} to all
+ * registered listeners.
+ *
+ * @param type the type (null not permitted).
+ */
+ public void setType(HistogramType type) {
+ Args.nullNotPermitted(type, "type");
+ this.type = type;
+ fireDatasetChanged();
+ }
+
+ /**
+ * Adds a series to the dataset, using the specified number of bins,
+ * and sends a {@link DatasetChangeEvent} to all registered listeners.
+ *
+ * @param key the series key (null not permitted).
+ * @param values the values (null not permitted).
+ * @param weightings the weights associated with the values, i.e.
+ * weight i indicates the number of times the value i appears
+ * (null not permitted).
+ * @param bins the number of bins (must be at least 1).
+ */
+ public void addSeries(Comparable key, double[] values, double[] weightings, int bins) {
+ // defer argument checking...
+ double minimum = getMinimum(values);
+ double maximum = getMaximum(values);
+ addSeries(key, values, weightings, bins, minimum, maximum);
+ }
+
+ /**
+ * Adds a series to the dataset. Any data value less than minimum will be
+ * assigned to the first bin, and any data value greater than maximum will
+ * be assigned to the last bin. Values falling on the boundary of
+ * adjacent bins will be assigned to the higher indexed bin.
+ *
+ * @param key the series key (null not permitted).
+ * @param values the raw observations.
+ * @param weightings the weights associated with the values, i.e.
+ * weight i indicates the number of times the value i appears
+ * (null not permitted).
+ * @param bins the number of bins (must be at least 1).
+ * @param minimum the lower bound of the bin range.
+ * @param maximum the upper bound of the bin range.
+ */
+ public void addSeries(Comparable key, double[] values, double[] weightings, int bins,
+ double minimum, double maximum) {
+
+ Args.nullNotPermitted(key, "key");
+ Args.nullNotPermitted(values, "values");
+ Args.nullNotPermitted(weightings, "weightings");
+ if (bins < 1) {
+ throw new IllegalArgumentException(
+ "The 'bins' value must be at least 1.");
+ }
+ if (values.length != weightings.length) {
+ throw new IllegalArgumentException(
+ "The length of weightings array must be the same as the values array!");
+ }
+
+ double binWidth = (maximum - minimum) / bins;
+
+ double lower = minimum;
+ double upper;
+ List binList = new ArrayList(bins);
+ for (int i = 0; i < bins; i++) {
+ Weighted_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 Weighted_HistogramBin(lower, maximum);
+ } else {
+ upper = minimum + (i + 1) * binWidth;
+ bin = new Weighted_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;
+ }
+ }
+ Weighted_HistogramBin bin = (Weighted_HistogramBin) binList.get(binIndex);
+ bin.incrementCount(weightings[i]);
+ totalWeight += weightings[i];
+ }
+ // generic map for each series
+ Map map = new LinkedHashMap();
+ map.put("key", key);
+ map.put("bins", binList);
+ map.put("values.length", new Integer(values.length));
+ map.put("bin width", new Double(binWidth));
+ this.list.add(map);
+ fireDatasetChanged();
+ }
+
+ /**
+ * Returns the minimum value in an array of values.
+ *
+ * @param values the values (null not permitted and
+ * zero-length array not permitted).
+ *
+ * @return The minimum value.
+ */
+ private double getMinimum(double[] values) {
+ if (values == null || values.length < 1) {
+ throw new IllegalArgumentException(
+ "Null or zero length 'values' argument.");
+ }
+ double min = Double.MAX_VALUE;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ min = values[i];
+ }
+ }
+ return min;
+ }
+
+ /**
+ * Returns the maximum value in an array of values.
+ *
+ * @param values the values (null not permitted and
+ * zero-length array not permitted).
+ *
+ * @return The maximum value.
+ */
+ private double getMaximum(double[] values) {
+ if (values == null || values.length < 1) {
+ throw new IllegalArgumentException(
+ "Null or zero length 'values' argument.");
+ }
+ double max = -Double.MAX_VALUE;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] > max) {
+ max = values[i];
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Returns the bins for a series.
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ *
+ * @return A list of bins.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ List getBins(int series) {
+ Map map = (Map) this.list.get(series);
+ return (List) map.get("bins");
+ }
+
+ /**
+ * Returns the total number of observations for a series.
+ *
+ * @param series the series index.
+ *
+ * @return The total.
+ */
+ private int getTotal(int series) {
+ Map map = (Map) this.list.get(series);
+ return ((Integer) map.get("values.length")).intValue();
+ }
+
+ /**
+ * Returns the bin width for a series.
+ *
+ * @param series the series index (zero based).
+ *
+ * @return The bin width.
+ */
+ private double getBinWidth(int series) {
+ Map map = (Map) this.list.get(series);
+ return ((Double) map.get("bin width")).doubleValue();
+ }
+
+ /**
+ * Returns the number of series in the dataset.
+ *
+ * @return The series count.
+ */
+ @Override
+ public int getSeriesCount() {
+ return this.list.size();
+ }
+
+ /**
+ * Returns the key for a series.
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ *
+ * @return The series key.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Comparable getSeriesKey(int series) {
+ Map map = (Map) this.list.get(series);
+ return (Comparable) map.get("key");
+ }
+
+ /**
+ * Returns the number of data items for a series.
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ *
+ * @return The item count.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public int getItemCount(int series) {
+ return getBins(series).size();
+ }
+
+ /**
+ * Returns the X value for a bin. This value won't be used for plotting
+ * histograms, since the renderer will ignore it. But other renderers can
+ * use it (for example, you could use the dataset to create a line
+ * chart).
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ * @param item the item index (zero based).
+ *
+ * @return The start value.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Number getX(int series, int item) {
+ List bins = getBins(series);
+ Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item);
+ double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2.;
+ return new Double(x);
+ }
+
+ /**
+ * Returns the y-value for a bin (calculated to take into account the
+ * histogram type).
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ * @param item the item index (zero based).
+ *
+ * @return The y-value.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Number getY(int series, int item) {
+ List bins = getBins(series);
+ Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item);
+ double total = getTotal(series);
+ double binWidth = getBinWidth(series);
+
+ if (this.type == HistogramType.FREQUENCY) {
+ return new Double(bin.getCount());
+ } else if (this.type == HistogramType.RELATIVE_FREQUENCY) {
+ return new Double(bin.getCount() / total);
+ } else if (this.type == HistogramType.SCALE_AREA_TO_1) {
+ return new Double(bin.getCount() / (binWidth * total));
+ } else { // pretty sure this shouldn't ever happen
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Returns the start value for a bin.
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ * @param item the item index (zero based).
+ *
+ * @return The start value.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Number getStartX(int series, int item) {
+ List bins = getBins(series);
+ Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item);
+ return new Double(bin.getStartBoundary());
+ }
+
+ /**
+ * Returns the end value for a bin.
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ * @param item the item index (zero based).
+ *
+ * @return The end value.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Number getEndX(int series, int item) {
+ List bins = getBins(series);
+ Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item);
+ return new Double(bin.getEndBoundary());
+ }
+
+ /**
+ * Returns the start y-value for a bin (which is the same as the y-value,
+ * this method exists only to support the general form of the
+ * {@link IntervalXYDataset} interface).
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ * @param item the item index (zero based).
+ *
+ * @return The y-value.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Number getStartY(int series, int item) {
+ return getY(series, item);
+ }
+
+ /**
+ * Returns the end y-value for a bin (which is the same as the y-value,
+ * this method exists only to support the general form of the
+ * {@link IntervalXYDataset} interface).
+ *
+ * @param series the series index (in the range 0 to
+ * getSeriesCount() - 1).
+ * @param item the item index (zero based).
+ *
+ * @return The Y value.
+ *
+ * @throws IndexOutOfBoundsException if series is outside the
+ * specified range.
+ */
+ @Override
+ public Number getEndY(int series, int item) {
+ return getY(series, item);
+ }
+
+ /**
+ * Tests this dataset for equality with an arbitrary object.
+ *
+ * @param obj the object to test against (null permitted).
+ *
+ * @return A boolean.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Weighted_HistogramDataset)) {
+ return false;
+ }
+ Weighted_HistogramDataset that = (Weighted_HistogramDataset) obj;
+ if (!Objects.equals(this.type, that.type)) {
+ return false;
+ }
+ if (!Objects.equals(this.list, that.list)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a clone of the dataset.
+ *
+ * @return A clone of the dataset.
+ *
+ * @throws CloneNotSupportedException if the object cannot be cloned.
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ Weighted_HistogramDataset clone = (Weighted_HistogramDataset) super.clone();
+ int seriesCount = getSeriesCount();
+ clone.list = new java.util.ArrayList(seriesCount);
+ for (int i = 0; i < seriesCount; i++) {
+ clone.list.add(new LinkedHashMap((Map) this.list.get(i)));
+ }
+ return clone;
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/Weighted_HistogramSimulationPlotter.java b/src/main/java/microsim/gui/plot/Weighted_HistogramSimulationPlotter.java
new file mode 100644
index 00000000..00ea4be8
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/Weighted_HistogramSimulationPlotter.java
@@ -0,0 +1,452 @@
+package microsim.gui.plot;
+
+//package microsim.gui.plot;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+import javax.swing.JInternalFrame;
+
+import microsim.engine.SimulationEngine;
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.statistics.IUpdatableSource;
+import microsim.statistics.weighted.IWeightedDoubleArraySource;
+import microsim.statistics.weighted.IWeightedFloatArraySource;
+import microsim.statistics.weighted.IWeightedIntArraySource;
+import microsim.statistics.weighted.IWeightedLongArraySource;
+
+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.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.StandardXYBarPainter;
+import org.jfree.chart.renderer.xy.XYBarRenderer;
+import org.jfree.data.general.SeriesChangeEvent;
+import org.jfree.data.statistics.HistogramType;
+
+/**
+ * A Weighted_HistogramSimulationPlotter is able to display a histogram of one
+ * or more
+ * data sources that each implements the Weight interface,
+ * and can be updated during the simulation.
+ * It is based on JFreeChart library and uses data sources based on the
+ * microsim.statistics.weighted* interfaces.
+ * Note that the weights are taken into account by adding the weight to the
+ * count
+ * of the histogram bin corresponding to the value associated with the weight.
+ * E.g, if a weighted object has value of 1.6 and weight of 5.3, the count of
+ * 5.3
+ * is placed in the histogram bin appropriate for the value of 1.6.
+ *
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Ross Richardson
+ *
+ */
+public class Weighted_HistogramSimulationPlotter extends JInternalFrame implements EventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ final JFreeChart chart;
+
+ private ArrayList sources;
+
+ private Weighted_HistogramDataset dataset;
+
+ private HistogramType type;
+
+ private int bins;
+
+ private Double minimum;
+
+ private Double maximum;
+
+ /**
+ * Constructor for histogram chart objects with chart legend displayed by
+ * default and
+ * all data samples shown, showing only the latest population data as time moves
+ * forward.
+ * Note - values falling on the boundary of adjacent bins will be assigned to
+ * the higher
+ * indexed bin. If it is desired set the minimum and maximum values displayed,
+ * or to turn
+ * the legend off, use the constructor:
+ * HistogramSimulationPlotter(String title, String xaxis, HistogramType type,
+ * int bins, double minimum, double maximum, boolean includeLegend)
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param type - the type of the histogram: either FREQUENCY,
+ * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1
+ * @param bins - the number of bins in the histogram
+ *
+ */
+ public Weighted_HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins) { // Includes
+ // legend by
+ // default
+ // and will
+ // accumulate
+ // data
+ // samples by
+ // default
+ // (if
+ // wanting
+ // only the
+ // most
+ // recent
+ // data
+ // points,
+ // use the
+ // other
+ // constructor)
+ this(title, xaxis, type, bins, null, null, true);
+ }
+
+ /**
+ * Constructor for scatterplot chart objects, featuring a toggle to hide the
+ * chart legend
+ * and to set the minimum and maximum values displayed in the chart, with values
+ * below the minimum
+ * assigned to the first bin, and values above the maximum assigned to the last
+ * bin. Note -
+ * values falling on the boundary of adjacent bins will be assigned to the
+ * higher indexed bin.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param type - the type of the histogram: either FREQUENCY,
+ * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1
+ * @param bins - the number of bins in the histogram
+ * @param minimum - any data value less than minimum will be assigned to
+ * the first bin
+ * @param maximum - any data value greater than maximum will be assigned
+ * to the last bin
+ * @param includeLegend - toggles whether to include the legend. If displaying a
+ * very large number of different series in the chart, it
+ * may be useful to turn
+ * the legend off as it will occupy a lot of space in the
+ * GUI.
+ */
+ public Weighted_HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins, Double minimum,
+ Double maximum, boolean includeLegend) { // Can specify whether to include legend and how many samples
+ // (updates) to display
+ // super(title, xaxis, type, bins, minimum, maximum, includeLegend); //invoke
+ // HistogramSimulationPlotter constructor
+ this.setResizable(true);
+ this.setTitle(title);
+ this.type = type;
+ this.bins = bins;
+ this.minimum = minimum;
+ this.maximum = maximum;
+
+ sources = new ArrayList();
+
+ dataset = new Weighted_HistogramDataset();
+
+ String yaxis;
+ if (type.equals(HistogramType.FREQUENCY)) {
+ yaxis = "Frequency";
+ } else if (type.equals(HistogramType.RELATIVE_FREQUENCY)) {
+ yaxis = "Relative Frequency";
+ throw new IllegalArgumentException(
+ "ERROR - RELATIVE_FREQUENCY Histogram Type is not currently available for Weighted_HistogramSimulationPlotter! Please use FREQUENCY (or possibly SCALE_AREA_TO_1) as the Histogram Type instead.");
+ } else if (type.equals(HistogramType.SCALE_AREA_TO_1)) {
+ System.out.println(
+ "WARNING - the SCALE_AREA_TO_1 Weighted_HistogramSimulationPlotter has not been tested and may produce incorrect output!");
+ yaxis = "Density (area scaled to 1)";
+ } else
+ throw new IllegalArgumentException(
+ "Incorrect HistogramType argument when calling HistogramSimulationPlotter constructor!");
+
+ chart = ChartFactory.createHistogram(
+ title, // chart title
+ xaxis, // x axis label
+ yaxis, // y axis label
+ // type.toString(), //y axis label based on the type of the histogram
+ dataset, // data
+ PlotOrientation.VERTICAL,
+ includeLegend, // include legend
+ true, // tooltips
+ false // urls
+ );
+
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final XYPlot plot = chart.getXYPlot();
+ // plot.setBackgroundPaint(Color.lightGray);
+ plot.setBackgroundPaint(Color.white);
+ plot.setDomainGridlinePaint(Color.white);
+ plot.setRangeGridlinePaint(Color.white);
+ plot.setForegroundAlpha(0.85f);
+
+ final XYBarRenderer renderer = new XYBarRenderer();
+ renderer.setDrawBarOutline(false);
+ renderer.setBarPainter(new StandardXYBarPainter());
+ renderer.setShadowVisible(false);
+ plot.setRenderer(renderer);
+
+ final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
+ domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+ final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
+ rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ public void update() {
+
+ dataset = new Weighted_HistogramDataset();
+ dataset.setType(type);
+ chart.getXYPlot().setDataset(dataset);
+
+ // int s = 0;
+ // Color color = (Color) chart.getXYPlot().getRenderer().getItemPaint(s, 0);
+ // int r = color.getRed();
+ // int g = color.getGreen();
+ // int b = color.getBlue();
+ // chart.getXYPlot().getRenderer().setSeriesPaint(s, new Color(r, g, b, 130));
+
+ for (int i = 0; i < sources.size(); i++) {
+ WeightedArraySource cs = (WeightedArraySource) sources.get(i);
+ double[] vals = cs.getDoubleArray();
+ double[] weights = cs.getWeights();
+ if (minimum != null && maximum != null) {
+ dataset.addSeries(cs.label, vals, weights, bins, minimum, maximum);
+ } else
+ dataset.addSeries(cs.label, vals, weights, bins);
+
+ }
+ dataset.seriesChanged(
+ new SeriesChangeEvent(new String("Update at time " + SimulationEngine.getInstance().getTime())));
+
+ }
+
+ private abstract class WeightedArraySource {
+ public String label;
+ protected boolean isUpdatable;
+
+ public abstract double[] getDoubleArray();
+
+ public abstract double[] getWeights();
+ }
+
+ private class DWeightedArraySource extends WeightedArraySource {
+ public IWeightedDoubleArraySource source;
+
+ public DWeightedArraySource(String label, IWeightedDoubleArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleArray();
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ private class FWeightedArraySource extends WeightedArraySource {
+ public IWeightedFloatArraySource source;
+
+ public FWeightedArraySource(String label, IWeightedFloatArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ float[] array = source.getFloatArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ private class IWeightedArraySource extends WeightedArraySource {
+ public IWeightedIntArraySource source;
+
+ public IWeightedArraySource(String label, IWeightedIntArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ int[] array = source.getIntArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ private class LWeightedArraySource extends WeightedArraySource {
+ public IWeightedLongArraySource source;
+
+ public LWeightedArraySource(String label, IWeightedLongArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ long[] array = source.getLongArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedDoubleSource objects
+ * in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IWeightedDoubleArraySource source) {
+ DWeightedArraySource sequence = new DWeightedArraySource(name, source);
+ sources.add(sequence);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedFloatSource objects
+ * in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IWeightedFloatArraySource source) {
+ FWeightedArraySource sequence = new FWeightedArraySource(name, source);
+ sources.add(sequence);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedIntArraySource
+ * objects in a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IWeightedIntArraySource source) {
+ IWeightedArraySource sequence = new IWeightedArraySource(name, source);
+ sources.add(sequence);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedLongSource objects in
+ * a
+ * collection.
+ *
+ * @param name
+ * The name of the series, which is shown in the legend.
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(String name, IWeightedLongArraySource source) {
+ LWeightedArraySource sequence = new LWeightedArraySource(name, source);
+ sources.add(sequence);
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java b/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java
new file mode 100644
index 00000000..840b6b59
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java
@@ -0,0 +1,334 @@
+package microsim.gui.plot;
+
+/* (C) Copyright 2020, by Kostas Manios
+ * Based on Ross Richardson's Weighted_HistogramDataset.java, which in turn
+ * was based on JFreeChart's HistogramDataset.java by Object Refinery Limited
+ * and Contributors.
+ *
+ * Project Info: http://www.jfree.org/jfreechart/index.html
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *
+ * ---------------------
+ * Weighted_PyramidDataset.java
+ * ---------------------
+ * (C) Copyright 2020, by Kostas Manios
+ *
+ * (C) Copyright 2017, by Ross Richardson
+ *
+ * Based on JFreeChart's HistogramDataset.java:
+ * (C) Copyright 2003-2013, by Jelai Wang and Contributors.
+ *
+ * Original Author: Jelai Wang (jelaiw AT mindspring.com);
+ * Contributor(s): David Gilbert (for Object Refinery Limited);
+ * Cameron Hayne;
+ * Rikard Bj?rklind;
+ * Thomas A Caswell (patch 2902842);
+ *
+ *
+ */
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jfree.data.general.AbstractSeriesDataset;
+import org.jfree.data.general.DatasetChangeListener;
+import org.jfree.data.general.DatasetGroup;
+import org.jfree.chart.util.Args;
+import org.jfree.chart.util.PublicCloneable;
+import org.jfree.data.category.CategoryDataset;
+
+import microsim.gui.plot.Weighted_PyramidPlotter.GroupName;
+
+/**
+ * A weighted dataset that can be used for creating weighted pyramids.
+ */
+public class Weighted_PyramidDataset extends AbstractSeriesDataset
+ implements CategoryDataset, Cloneable, PublicCloneable,
+ Serializable {
+ /** For serialization. */
+ private static final long serialVersionUID = -6875925093485823495L;
+
+ /** A list of maps. */
+ private Map> dataMap;
+ private double[][] groupRanges;
+ private GroupName[] groupNames;
+ private double scalingFactor = 1.0;
+
+ /**
+ * Creates a new dataset using the provided groupNames and
+ * groupRanges to build a HashMap of total group weight.
+ * The weights are adjusted by the provided scalingFactor.
+ *
+ * @param groupNames the names of each group to be generated
+ * (null not permitted).
+ * @param groupRanges the ranges of each group to be generated
+ * (null not permitted).
+ * @param scalingFactor the scaling factor for the weights (null
+ * not permitted).
+ */
+ public Weighted_PyramidDataset(GroupName[] groupNames, double[][] groupRanges, double scalingFactor) {
+ Args.nullNotPermitted(groupNames, "groupNames");
+ Args.nullNotPermitted(groupRanges, "groupRanges");
+ Args.nullNotPermitted(scalingFactor, "scalingFactor");
+ this.dataMap = new HashMap>();
+ this.groupNames = groupNames;
+ this.groupRanges = groupRanges;
+ this.scalingFactor = scalingFactor;
+ }
+
+ /**
+ * Adds the couple of series to the dataMap. Each value is assigned
+ * to a group when it matches the group's min/max limits.
+ *
+ * @param keys the series key (null not permitted).
+ * @param values the raw observations. (null not permitted).
+ * @param weightings the weights associated with the values, i.e.
+ * weight i indicates the number of times the value i appears
+ * (null not permitted).
+ */
+ public void addSeries(String[] keys, double[][] values, double[][] weightings) {
+ Args.nullNotPermitted(keys, "key");
+ Args.nullNotPermitted(values, "values");
+ Args.nullNotPermitted(weightings, "weightings");
+ if (values.length != 2 || weightings.length != 2) {
+ throw new IllegalArgumentException(
+ "You must provide a pair of series!");
+ }
+ if (values[0].length != weightings[0].length || values[1].length != weightings[1].length) {
+ throw new IllegalArgumentException(
+ "The length of weightings array must be the same as the values array for each series!");
+ }
+
+ // Create and add the two series to the dataMap
+ for (int s = 0; s < 2; s++) {
+ // for each series create a new bucket to store the variable sums
+ Map bucket = new HashMap();
+
+ for (int v = 0; v < values[s].length; v++) { // for each value
+ for (int g = 0; g < this.groupNames.length; g++) { // for each group
+ // if the value matches the group, add to the correct bucket element
+ if (values[s][v] >= this.groupRanges[g][0] && values[s][v] <= this.groupRanges[g][1]) {
+ // if the element does not exist, create it
+ if (!bucket.containsKey(this.groupNames[g]))
+ bucket.put(this.groupNames[g], 0.);
+ // multiply the weight by the scaling factor and add to the existing sum (negate
+ // if this is the left side),
+ bucket.put(this.groupNames[g], bucket.get(this.groupNames[g])
+ + weightings[s][v] * (s == 1 ? scalingFactor : -scalingFactor));
+ // do not check any more groups for this value
+ break;
+ }
+ }
+ }
+ // store the series bucket
+ dataMap.put(keys[s], bucket);
+ }
+ }
+
+ /**
+ * Returns the minimum value in an array of values.
+ *
+ * @param values the values (null not permitted and
+ * zero-length array not permitted).
+ *
+ * @return The minimum value.
+ */
+ private double getMinimum(double[] values) {
+ if (values == null || values.length < 1) {
+ throw new IllegalArgumentException(
+ "Null or zero length 'values' argument.");
+ }
+ double min = Double.MAX_VALUE;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ min = values[i];
+ }
+ }
+ return min;
+ }
+
+ /**
+ * Returns the maximum value in an array of values.
+ *
+ * @param values the values (null not permitted and
+ * zero-length array not permitted).
+ *
+ * @return The maximum value.
+ */
+ private double getMaximum(double[] values) {
+ if (values == null || values.length < 1) {
+ throw new IllegalArgumentException(
+ "Null or zero length 'values' argument.");
+ }
+ double max = -Double.MAX_VALUE;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] > max) {
+ max = values[i];
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Tests this dataset for equality with an arbitrary object.
+ *
+ * @param obj the object to test against (null permitted).
+ *
+ * @return A boolean.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Weighted_PyramidDataset)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a clone of the dataset.
+ *
+ * @return A clone of the dataset.
+ *
+ * @throws CloneNotSupportedException if the object cannot be cloned.
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ Weighted_PyramidDataset clone = (Weighted_PyramidDataset) super.clone();
+ return clone;
+ }
+
+ public double[][] getDataArray() {
+ double[][] data = new double[dataMap.keySet().size()][groupNames.length];
+ int i = 0;
+ for (Map v : dataMap.values()) {
+ int j = 0;
+ for (GroupName entry : groupNames)
+ // make sure that if either side of the dataset is missing a value, this is
+ // filled with 0
+ data[i][j++] = v.containsKey(entry) ? v.get(entry) : 0;
+ i++;
+ }
+
+ return data;
+ }
+
+ @Override
+ public List getColumnKeys() {
+ // TODO Auto-generated method stub
+ return Arrays.asList(this.groupNames);
+ }
+
+ @Override
+ public Comparable getColumnKey(int column) {
+ return this.groupNames[column];
+ }
+
+ public String[] getSeriesKeys() {
+ return dataMap.keySet().toArray(new String[] {});
+ }
+
+ @Override
+ public Comparable getRowKey(int row) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getRowIndex(Comparable key) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public List getRowKeys() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getColumnIndex(Comparable key) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public Number getValue(Comparable rowKey, Comparable columnKey) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getRowCount() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int getColumnCount() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public Number getValue(int row, int column) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void addChangeListener(DatasetChangeListener listener) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeChangeListener(DatasetChangeListener listener) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public DatasetGroup getGroup() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setGroup(DatasetGroup group) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public int getSeriesCount() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public Comparable getSeriesKey(int series) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java b/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java
new file mode 100644
index 00000000..83aebea5
--- /dev/null
+++ b/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java
@@ -0,0 +1,702 @@
+package microsim.gui.plot;
+
+import java.awt.Color;
+import java.text.DecimalFormat;
+import java.util.Arrays;
+
+import javax.swing.JInternalFrame;
+
+import microsim.event.CommonEventType;
+import microsim.event.EventListener;
+import microsim.statistics.IUpdatableSource;
+import microsim.statistics.weighted.IWeightedDoubleArraySource;
+import microsim.statistics.weighted.IWeightedFloatArraySource;
+import microsim.statistics.weighted.IWeightedIntArraySource;
+import microsim.statistics.weighted.IWeightedLongArraySource;
+
+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.plot.CategoryPlot;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.renderer.category.StackedBarRenderer;
+import org.jfree.chart.renderer.category.StandardBarPainter;
+import org.jfree.data.general.DatasetUtils;
+
+/**
+ * A PyramidPlotter is able to display a pyramid using two weighted
+ * cross-sections of a variable (e.g. dag males/females for a
+ * population pyramid). It can be updated during the simulation. It
+ * is based on JFreeChart library and uses data sources based on the
+ * microsim.statistics.weighted* interfaces.
+ * Note that the weights are taken into account by adding the weight to the
+ * count
+ * of each group. Groups can be optionally provided by the caller.
+ *
+ *
+ *
+ *
+ * This work is based on "Weighted_HistogramSimulationPlotter.java" by Ross
+ * Richardson
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Kostas Manios
+ *
+ *
+ */
+public class Weighted_PyramidPlotter extends JInternalFrame implements EventListener {
+
+ /**
+ * Default values
+ */
+ public static String DEFAULT_TITLE = "Population Chart";
+ public static String DEFAULT_XAXIS = "Age Group";
+ public static String DEFAULT_YAXIS = "Population";
+ public static String DEFAULT_LEFT_CAT = "Males";
+ public static String DEFAULT_RIGHT_CAT = "Females";
+ public static Boolean DEFAULT_REVERSE_ORDER = false;
+ public static String DEFAULT_YAXIS_FORMAT = "#.##";
+ private static int MAXIMUM_VISIBLE_CATEGORIES = 20;
+
+ /**
+ * Variables
+ */
+
+ private static final long serialVersionUID = 1L;
+
+ private JFreeChart chart;
+
+ private WeightedArraySource[] sources;
+
+ private Weighted_PyramidDataset dataset;
+
+ private String xaxis;
+
+ private String yaxis;
+
+ private String yaxisFormat = DEFAULT_YAXIS_FORMAT;
+
+ private final String[] catNames = new String[2];
+
+ private GroupName[] groupNames;
+
+ private double[][] groupRanges; // These need to be doubles for the DatasetUtilities.createCategoryDataset
+ // method
+
+ private double scalingFactor; // This scales the sample (e.g. to the whole population)
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * Default values are used for all parameters: title, x-axis, y-axis, category
+ * names, age group names/ranges, reverseOrder
+ * It generates one age group per unique age, whose title is that age.
+ *
+ */
+ public Weighted_PyramidPlotter() {
+ this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT);
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * Default values are used for the following parameters: x-axis, y-axis,
+ * category names, age group names/ranges, reverseOrder
+ * It generates one age group per unique age, whose title is that age.
+ *
+ * @param title - title of the chart
+ *
+ */
+ public Weighted_PyramidPlotter(String title) {
+ this(title, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT);
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * Default values are used for the following parameters: category names, age
+ * group names/ranges, reverseOrder
+ * It generates one age group per unique age, whose title is that age.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ *
+ *
+ * public PopulationPyramidPlotter(String title, String xaxis,
+ * String yaxis) {
+ * this(title, xaxis, yaxis, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT);
+ * }
+ */
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * Default values are used for the following parameters: age group names/ranges,
+ * reverseOrder
+ * It generates one age group per unique age, whose title is that age.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ * @param leftCat - the name of the left category
+ * @param rightCat - the name of the right category
+ *
+ */
+ public Weighted_PyramidPlotter(String title, String xaxis, String yaxis, String leftCat, String rightCat) {
+ // fix the titles and prepare the plotter, leaving the groups null
+ fixTitles(title, xaxis, yaxis, leftCat, rightCat);
+ preparePlotter();
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * It generates groups names and ranges using the start/end/step values
+ * provided.
+ * Default values are used for the following parameters: x-axis, y-axis,
+ * category names, age group names/ranges, reverseOrder
+ *
+ * @param start - the minimum accepted value in groups
+ * @param end - the maximum accepted value in groups
+ * @param step - the step used to separate value into groups
+ *
+ */
+ public Weighted_PyramidPlotter(int start, int end, int step) {
+ this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT, start, end, step,
+ DEFAULT_REVERSE_ORDER, DEFAULT_YAXIS_FORMAT);
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * It generates groups names and ranges using the start/end/step and order
+ * values provided.
+ * Default values are used for the following parameters: x-axis, y-axis,
+ * category names, age group names/ranges
+ *
+ * @param start - the minimum accepted value in groups
+ * @param end - the maximum accepted value in groups
+ * @param step - the step used to separate value into groups
+ * @param reverseOrder - if true, it will reverse the groups
+ *
+ */
+ public Weighted_PyramidPlotter(int start, int end, int step, Boolean reverseOrder) {
+ this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT, start, end, step,
+ reverseOrder, DEFAULT_YAXIS_FORMAT);
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * It generates groups names and ranges using the start/end/step values
+ * provided.
+ * Descending order is used by default.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ * @param leftCat - the name of the left category
+ * @param rightCat - the name of the right category
+ * @param start - the minimum accepted value in groups
+ * @param end - the maximum accepted value in groups
+ * @param step - the step used to separate value into groups
+ * @param reverseOrder - if true, it will reverse the groups
+ *
+ */
+ public Weighted_PyramidPlotter(String title, String xaxis, String yaxis, String leftCat, String rightCat, int start,
+ int end, int step, Boolean reverseOrder, String format) {
+ if (step == 0)
+ return;
+ fixTitles(title, xaxis, yaxis, leftCat, rightCat);
+ yaxisFormat = format;
+
+ // Create the groups based on the range, and save them to "this"
+ GroupDetails gd = makeGroupsFromRange(start, end, step, reverseOrder, format);
+ this.groupNames = gd.groupNames;
+ this.groupRanges = gd.groupRanges;
+
+ preparePlotter();
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * It generates groups based on the names and ranges provided.
+ * Default values are used for the following parameters: title, x-axis, y-axis,
+ * category names
+ *
+ * @param groupNames - an array of the name of each group
+ * @param groupRanges - an array of the min/max values of each group
+ *
+ */
+ public Weighted_PyramidPlotter(String[] groupNames, double[][] groupRanges) {
+ this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT, groupNames, groupRanges,
+ DEFAULT_YAXIS_FORMAT);
+ }
+
+ /**
+ * Constructor for pyramid objects, showing only the latest data as time moves
+ * forward.
+ * It generates groups based on the names and ranges provided.
+ *
+ * @param title - title of the chart
+ * @param xaxis - name of the x-axis
+ * @param yaxis - name of the y-axis
+ * @param leftCat - the name of the left category
+ * @param rightCat - the name of the right category
+ * @param groupNames - an array of the name of each group
+ * @param groupRanges - an array of the min/max values of each group
+ *
+ */
+ public Weighted_PyramidPlotter(String title, String xaxis, String yaxis, String leftCat, String rightCat,
+ String[] groupNames, double[][] groupRanges, String format) {
+ fixTitles(title, xaxis, yaxis, leftCat, rightCat);
+ yaxisFormat = format;
+
+ // Fix names
+ this.groupNames = groupNames == null ? null : getGroupNamesFromStrings(groupNames);
+ this.groupRanges = groupRanges;
+
+ preparePlotter();
+ }
+
+ // The function that prepares the titles
+ private void fixTitles(String title, String xaxis, String yaxis, String leftCat, String rightCat) {
+ this.setTitle(title);
+ this.xaxis = xaxis;
+ this.yaxis = yaxis;
+ this.catNames[0] = leftCat;
+ this.catNames[1] = rightCat;
+ }
+
+ // the function that calculates groups from a range
+ private GroupDetails makeGroupsFromRange(int start, int end, int step, Boolean reverseOrder, String format) {
+ // First we calculate the optimal (visually at least!) number of groups, so that
+ // the last group ends with "max" and its size is "(0.5 * step) < size <
+ // (1.5*step)"
+ int noOfGroups = (int) Math
+ .max(Math.round((double) (end - start) / (double) step) + (Math.abs(step) == 1 ? 1 : 0), 1);
+ // *Note: should we enforce equal groups sizes?
+
+ // Then, if required, we reverse the order
+ if (reverseOrder) {
+ int temp = start;
+ start = end;
+ end = temp;
+ step = -step;
+ }
+
+ // Then we calculate the group ranges & names
+ String[] groupNames = new String[noOfGroups];
+ double[][] groupRanges = new double[noOfGroups][2];
+
+ // asc checks whether we are ascending or descending
+ Boolean asc = start <= end;
+ for (int i = 0; i < noOfGroups; i++) {
+ // The range needs to always be stored in ascending order, hence the extended
+ // use of "asc" here. Sorry! :)
+ // is calculated based on the current step value
+ groupRanges[i][asc ? 0 : 1] = start + i * step;
+ // is equal to the next group's " - 1", but for the last group it is
+ // equal to "end"
+ groupRanges[i][asc ? 1 : 0] = (i == noOfGroups - 1) ? end : (start + (i + 1) * step) - (asc ? 1 : -1);
+ // for the name, if step=1 use the step value, else show as "from - to"
+ // (inclusive)
+ groupNames[i] = groupRanges[i][0] == groupRanges[i][1] ? new DecimalFormat(format).format(groupRanges[i][0])
+ : new DecimalFormat(format).format(groupRanges[i][asc ? 0 : 1]) + " - "
+ + new DecimalFormat(format).format(groupRanges[i][asc ? 1 : 0]);
+
+ }
+
+ return new GroupDetails(getGroupNamesFromStrings(groupNames), groupRanges);
+ }
+
+ private static GroupName[] getGroupNamesFromStrings(String[] groupStrings) {
+ GroupName[] groupNames = new GroupName[groupStrings.length];
+
+ int stepShow = (int) Math.ceil((double) groupStrings.length / (double) MAXIMUM_VISIBLE_CATEGORIES);
+
+ // Show only every Nth string and always the first & last
+ for (int i = 0; i < groupStrings.length; i++) {
+ groupNames[i] = new GroupName(groupStrings[i], (i % stepShow == 0 || i == groupStrings.length - 1));
+ }
+
+ return groupNames;
+ }
+
+ private void preparePlotter() {
+ this.setResizable(true);
+ sources = new WeightedArraySource[2];
+
+ chart = ChartFactory.createStackedBarChart(
+ title, // chart title
+ this.xaxis, // x axis label
+ this.yaxis, // y axis label
+ DatasetUtils.createCategoryDataset(this.catNames, new String[] { "" },
+ new double[][] { { 0 }, { 0 } }),
+ PlotOrientation.HORIZONTAL,
+ true, // include legend
+ true,
+ true);
+
+ setChartProperties();
+
+ chart.getCategoryPlot().getRangeAxis().setVisible(false);
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ this.setSize(400, 400);
+ }
+
+ public void onEvent(Enum> type) {
+ if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) {
+ update();
+ }
+ }
+
+ // This function generates a new chart based on the latest data
+ public void update() {
+ if (sources.length != 2 || catNames.length != 2)
+ return;
+ GroupName[] groupNames = null;
+ double[][] groupRanges = null;
+
+ // Get the source data
+ WeightedArraySource leftData = (WeightedArraySource) sources[0];
+ WeightedArraySource rightData = (WeightedArraySource) sources[1];
+ final double[][] vals = new double[][] { leftData.getDoubleArray(), rightData.getDoubleArray() };
+ final double[][] weights = new double[][] { leftData.getWeights(), rightData.getWeights() };
+
+ // If there are no groups defined, create one for each age between the min/max
+ // found in the data
+ // *Note: do we want this done in every repetition, or should we save to "this"?
+ if (this.groupNames == null || this.groupRanges == null) {
+ int min = (int) Math.min(Arrays.stream(vals[0]).min().orElse(0), Arrays.stream(vals[1]).min().orElse(0)); // if
+ // there
+ // is
+ // no
+ // data,
+ // set
+ // min
+ // to
+ // 0
+ int max = (int) Math.min(Arrays.stream(vals[0]).max().orElse(100),
+ Arrays.stream(vals[1]).max().orElse(100)); // if there is no data, set max to 100
+ // Create the groups based on the range, and save them to the local variables
+ GroupDetails gd = makeGroupsFromRange(min, max, 1, true, yaxisFormat);
+ groupNames = gd.groupNames;
+ groupRanges = gd.groupRanges;
+ } else {
+ // else, just use the existing groups
+ groupNames = this.groupNames;
+ groupRanges = this.groupRanges;
+ }
+
+ // Create the dataset and add the data
+ dataset = new Weighted_PyramidDataset(groupNames, groupRanges, scalingFactor);
+ dataset.addSeries(this.catNames, vals, weights);
+
+ chart = ChartFactory.createStackedBarChart(
+ this.title, // chart title
+ this.xaxis, // x axis label
+ this.yaxis, // y axis label
+ DatasetUtils.createCategoryDataset(
+ dataset.getSeriesKeys(),
+ groupNames,
+ dataset.getDataArray()), // data
+ PlotOrientation.HORIZONTAL,
+ true, // include legend
+ true,
+ true);
+
+ setChartProperties();
+
+ final ChartPanel chartPanel = new ChartPanel(chart);
+
+ chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
+
+ setContentPane(chartPanel);
+
+ }
+
+ /**
+ * This function sets the default Chart Properties.
+ */
+ private void setChartProperties() {
+ // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
+ chart.setBackgroundPaint(Color.white);
+
+ // get a reference to the plot for further customisation...
+ final CategoryPlot plot = chart.getCategoryPlot();
+ plot.setBackgroundPaint(Color.lightGray);
+ plot.setRangeGridlinePaint(Color.white);
+ plot.setForegroundAlpha(0.85f);
+ plot.setShadowGenerator(null);
+ final StackedBarRenderer renderer = new StackedBarRenderer();
+ renderer.setDrawBarOutline(false);
+ renderer.setBarPainter(new StandardBarPainter());
+ renderer.setShadowVisible(false);
+ plot.setRenderer(renderer);
+
+ // hide the sign for negative numbers in yAxis
+ NumberAxis yAxis = (NumberAxis) plot.getRangeAxis();
+ yAxis.setNumberFormatOverride(new DecimalFormat("0; 0 "));
+
+ }
+
+ private class GroupDetails {
+ public GroupName[] groupNames;
+ public double[][] groupRanges;
+
+ public GroupDetails(GroupName[] groupNames, double[][] groupRanges) {
+ this.groupNames = groupNames;
+ this.groupRanges = groupRanges;
+ }
+ }
+
+ public void setScalingFactor(double scalingFactor) {
+ this.scalingFactor = scalingFactor;
+ }
+
+ public static class GroupName implements Comparable {
+ String value;
+ Boolean show;
+
+ GroupName(String val, Boolean sh) {
+ value = val;
+ show = sh;
+ }
+
+ public int compareTo(GroupName key) {
+ return value.compareTo(key.value);
+ }
+
+ public String toString() {
+ return show ? value : "";
+ }
+ }
+
+ private abstract class WeightedArraySource {
+ public String label;
+ protected boolean isUpdatable;
+
+ public abstract double[] getDoubleArray();
+
+ public abstract double[] getWeights();
+ }
+
+ private class DWeightedArraySource extends WeightedArraySource {
+ public IWeightedDoubleArraySource source;
+
+ public DWeightedArraySource(String label, IWeightedDoubleArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ return source.getDoubleArray();
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ private class FWeightedArraySource extends WeightedArraySource {
+ public IWeightedFloatArraySource source;
+
+ public FWeightedArraySource(String label, IWeightedFloatArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ float[] array = source.getFloatArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ private class IWeightedArraySource extends WeightedArraySource {
+ public IWeightedIntArraySource source;
+
+ public IWeightedArraySource(String label, IWeightedIntArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ int[] array = source.getIntArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ private class LWeightedArraySource extends WeightedArraySource {
+ public IWeightedLongArraySource source;
+
+ public LWeightedArraySource(String label, IWeightedLongArraySource source) {
+ super.label = label;
+ this.source = source;
+ isUpdatable = (source instanceof IUpdatableSource);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see jas.plot.TimePlot.Source#getDouble()
+ */
+ public double[] getDoubleArray() {
+ if (isUpdatable)
+ ((IUpdatableSource) source).updateSource();
+ long[] array = source.getLongArray();
+ double[] output = new double[array.length];
+ for (int i = 0; i < array.length; i++)
+ output[i] = array[i];
+
+ return output;
+ }
+
+ @Override
+ public double[] getWeights() {
+ return source.getWeights();
+ }
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedDoubleSource objects
+ * in a
+ * collection.
+ *
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(IWeightedDoubleArraySource[] source) {
+ if (source.length != 2)
+ return;
+ if (catNames.length != 2)
+ return;
+ sources[0] = new DWeightedArraySource(catNames[0], source[0]);
+ sources[1] = new DWeightedArraySource(catNames[1], source[1]);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedFloatSource objects
+ * in a
+ * collection.
+ *
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(IWeightedFloatArraySource[] source) {
+ if (source.length != 2)
+ return;
+ if (catNames.length != 2)
+ return;
+ sources[0] = new FWeightedArraySource(catNames[0], source[0]);
+ sources[1] = new FWeightedArraySource(catNames[1], source[1]);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedIntArraySource
+ * objects in a
+ * collection.
+ *
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(IWeightedIntArraySource[] source) {
+ if (source.length != 2)
+ return;
+ if (catNames.length != 2)
+ return;
+ sources[0] = new IWeightedArraySource(catNames[0], source[0]);
+ sources[1] = new IWeightedArraySource(catNames[1], source[1]);
+ }
+
+ /**
+ * Add a new series buffer, retrieving value from IWeightedLongSource objects in
+ * a
+ * collection.
+ *
+ * @param source
+ * A collection containing the sources.
+ */
+ public void addCollectionSource(IWeightedLongArraySource[] source) {
+ if (source.length != 2)
+ return;
+ if (catNames.length != 2)
+ return;
+ sources[0] = new LWeightedArraySource(catNames[0], source[0]);
+ sources[1] = new LWeightedArraySource(catNames[1], source[1]);
+ }
+
+}
diff --git a/src/main/java/microsim/gui/probe/IProbeFields.java b/src/main/java/microsim/gui/probe/IProbeFields.java
new file mode 100644
index 00000000..674c0ef9
--- /dev/null
+++ b/src/main/java/microsim/gui/probe/IProbeFields.java
@@ -0,0 +1,49 @@
+package microsim.gui.probe;
+
+import java.util.List;
+
+/**
+ * This interface allows the object implementing it to
+ * show only some properties and methods when probed.
+ *
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms
+ * of the GNU Lesser General Public License as published by the Free Software
+ * Foundation;
+ * either version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this
+ * library; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * @author Michele Sonnessa
+ *
+ */
+
+public interface IProbeFields {
+ /**
+ * Return a list contaning strings corresponding to
+ * the properties and the methods shown by a probe.
+ *
+ * @return A list of String objects.
+ */
+ public List