diff --git a/microsim-gui/pom.xml b/microsim-gui/pom.xml index 00acb86..835790b 100644 --- a/microsim-gui/pom.xml +++ b/microsim-gui/pom.xml @@ -6,7 +6,7 @@ com.github.jasmineRepo JAS-mine-gui - 4.0.8 + 4.0.9 @@ -126,7 +126,7 @@ com.github.jasmineRepo JAS-mine-core - 4.0.8 + 4.0.10 diff --git a/microsim-gui/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java b/microsim-gui/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java new file mode 100644 index 0000000..e298c18 --- /dev/null +++ b/microsim-gui/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java @@ -0,0 +1,335 @@ +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.chart.util.ParamChecks; +import org.jfree.data.general.AbstractSeriesDataset; +import org.jfree.data.general.DatasetChangeListener; +import org.jfree.data.general.DatasetGroup; +import org.jfree.data.category.CategoryDataset; +import org.jfree.util.PublicCloneable; + +import microsim.gui.plot.Weighted_PyramidPlotter.GroupName; + +/** + * A weighted dataset that can be used for creating weighted pyramids. + * + * @see SimpleHistogramDataset + */ +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 key the ranges of each group to be generated (null not permitted). + * @param key the scaling factor for the weights (null not permitted). + */ + public Weighted_PyramidDataset(GroupName[] groupNames, double[][] groupRanges, double scalingFactor) { + ParamChecks.nullNotPermitted(groupNames, "groupNames"); + ParamChecks.nullNotPermitted(groupRanges, "groupRanges"); + ParamChecks.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 key 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) { + ParamChecks.nullNotPermitted(keys, "key"); + ParamChecks.nullNotPermitted(values, "values"); + ParamChecks.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/microsim-gui/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java b/microsim-gui/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java new file mode 100644 index 0000000..e837e8d --- /dev/null +++ b/microsim-gui/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java @@ -0,0 +1,646 @@ +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.DatasetUtilities; + +/** + * 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. + * + * + *

+ * Title: JAS-mine + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2020 Kostas Manios + *

+ * + * 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; + 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 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); + } + + /** + * 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); + } + + /** + * 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) { + if (step == 0) return; + fixTitles(title, xaxis, yaxis, leftCat, rightCat); + + // Create the groups based on the range, and save them to "this" + GroupDetails gd = makeGroupsFromRange(start, end, step, reverseOrder); + 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); + } + + /** + * 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) { + fixTitles(title, xaxis, yaxis, leftCat, rightCat); + + // 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) { + // 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] ? String.valueOf(groupRanges[i][0]) : groupRanges[i][asc?0:1] + " - " + 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 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); + 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 + DatasetUtilities.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 name + * The name of the series, which is shown in the legend. + * @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 name + * The name of the series, which is shown in the legend. + * @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 name + * The name of the series, which is shown in the legend. + * @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 name + * The name of the series, which is shown in the legend. + * @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/microsim-gui/src/main/java/microsim/gui/shell/MicrosimShell.java b/microsim-gui/src/main/java/microsim/gui/shell/MicrosimShell.java index 06ac3e5..eac9795 100644 --- a/microsim-gui/src/main/java/microsim/gui/shell/MicrosimShell.java +++ b/microsim-gui/src/main/java/microsim/gui/shell/MicrosimShell.java @@ -176,13 +176,12 @@ public MicrosimShell(SimulationEngine engine) { setInitButtonStatus(); // this.setExtendedState(java.awt.Frame.MAXIMIZED_BOTH); - jSplitInternalDesktop.setDividerLocation(this.getHeight() / 3 * 4); - currentShell = this; this.pack(); - this.setSize((int)Toolkit.getDefaultToolkit().getScreenSize().getWidth(), (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight()); + this.setSize((int)Toolkit.getDefaultToolkit().getScreenSize().getWidth(), (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight()-30); this.setVisible(true); + jSplitInternalDesktop.setDividerLocation(jSplitInternalDesktop.getHeight() * 4 / 5); } public SimulationController getController() { @@ -835,12 +834,7 @@ else if(isSetter(method)) { if (fields.size() > 0) { ParameterFrame parameterFrame = new ParameterFrame(model); parameterFrame.setResizable(false); //Now in scrollpane, cannot resize anyway, so set to false. - JScrollPane scrollP = new JScrollPane(parameterFrame); - JInternalFrame f = new JInternalFrame(); - f.add(scrollP); - f.setSize(parameterFrame.getWidth() + 10, Math.min(parameterFrame.getHeight() + 10, 550)); - f.setVisible(true); - GuiUtils.addWindow(f); + GuiUtils.addWindow(parameterFrame); // GuiUtils.addWindow(parameterFrame); parameterFrames.add(parameterFrame); } diff --git a/microsim-gui/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java b/microsim-gui/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java index d1b16a9..e28b597 100644 --- a/microsim-gui/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java +++ b/microsim-gui/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java @@ -6,6 +6,7 @@ import java.util.Map; import javax.swing.JInternalFrame; +import javax.swing.JScrollPane; import microsim.annotation.GUIparameter; import microsim.annotation.ModelParameter; import microsim.gui.shell.MicrosimShell; @@ -65,9 +66,11 @@ private void jbInit() throws Exception { metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); metawidget.setToInspect( target ); - setSize((int)(MicrosimShell.scale*320), (int)(MicrosimShell.scale*Math.max(30 + 26 * fields.size(), 90))); + setSize((int)(MicrosimShell.scale*320), Math.min((int)(MicrosimShell.scale*Math.max(30 + 26 * fields.size(), 90)), 500)); + JScrollPane scrollP = new JScrollPane(metawidget); - getContentPane().add(metawidget); + if (metawidget.getComponentCount()>0) scrollP.getViewport().setBackground(metawidget.getComponent(0).getBackground()); + getContentPane().add(scrollP); setVisible(true); }