/* Copyright 2006 by Sean Luke and George Mason University Licensed under the Academic Free License version 3.0 See the file "LICENSE" for more information */ package sim.portrayal.inspector; import java.awt.*; import java.util.Iterator; import sim.util.*; import sim.display.*; import sim.engine.*; import sim.util.media.chart.*; import org.jfree.data.xy.*; import org.jfree.data.general.*; /** A property inspector which generates time series of data. Time series are extended in real-time as requested by the user. Data properties for which the TimeSeriesChartingPropertyInspector will operate include: <ul> <li>Any numerical value (byte, int, double, etc.) <li>Any sim.util.Valuable object <li>Any Number (Double, Integer, etc.) </ul> <p>TimeSeriesChartingPropertyInspector registers itself with the property menu option "Chart". */ public class TimeSeriesChartingPropertyInspector extends ChartingPropertyInspector { XYSeries chartSeries = null; XYSeries aggregateSeries = new XYSeries("ChartingPropertyInspector.temp", false); protected boolean validChartGenerator(ChartGenerator generator) { return generator instanceof TimeSeriesChartGenerator; } public static String name() { return "Chart"; } public static Class[] types() { return new Class[] { Number.class, Boolean.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Valuable.class }; } public TimeSeriesChartingPropertyInspector(Properties properties, int index, Frame parent, final GUIState simulation) { super(properties,index,parent,simulation); setupSeriesAttributes(); } public TimeSeriesChartingPropertyInspector(Properties properties, int index, final GUIState simulation, ChartGenerator generator) { super(properties, index, simulation, generator); setupSeriesAttributes(); } //I isolated this code from the constructor into this method because I have two constructors now. private void setupSeriesAttributes() { if (validInspector) { chartSeries = new XYSeries( properties.getName(index), false ); // add our series seriesAttributes = ((TimeSeriesChartGenerator)generator).addSeries(chartSeries, new SeriesChangeListener() { public void seriesChanged(SeriesChangeEvent event) { getStopper().stop(); } }); } } protected ChartGenerator createNewGenerator() { return new TimeSeriesChartGenerator() { public void quit() { super.quit(); Stoppable stopper = getStopper(); if (stopper!=null) stopper.stop(); // remove the chart from the GUIState's guiObjects if( simulation.guiObjects != null ) simulation.guiObjects.remove(this); } }; } protected double valueFor(Object o) { if (o instanceof java.lang.Number) // compiler complains unless I include the full classname!!! Huh? return ((Number)o).doubleValue(); else if (o instanceof Valuable) return ((Valuable)o).doubleValue(); else if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? 1 : 0; else return Double.NaN; // unknown } // public DataCuller dataCuller = null; //null means no culling public void addToMainSeries(double x, double y, boolean notify) { chartSeries.add(x, y, false); //I postpone <code>fireSeriesChanged</code> till after //the culling decision to save a repaint. DataCuller dataCuller = ((TimeSeriesChartGenerator)generator).getDataCuller(); if(dataCuller!=null && dataCuller.tooManyPoints(chartSeries.getItemCount())) deleteItems(dataCuller.cull(getXValues(), true)); else //no chage to chartSeries other then the add(), so if(notify) chartSeries.fireSeriesChanged(); } // public void setDataCuller(DataCuller dc) // { // dataCuller = dc; // } static Bag tmpBag = new Bag(); void deleteItems(IntBag items) { if(items.numObjs==0) return; // //I would sure hate to to do this (O(n^2), plus each remove causes a SeriesChangeEvent): // for(int i=items.numObjs-1;i>=0;i--) // chartSeries.remove(items.objs[i]); //here's the O(n) version (and just 2 SeriesChangeEvents) tmpBag.clear(); int currentTabooIndex = 0; int currentTaboo = items.objs[0]; Iterator iter = chartSeries.getItems().iterator(); int index=0; while(iter.hasNext()) { Object o = iter.next(); if(index==currentTaboo) { //skip the copy, let's move on to next taboo index if(currentTabooIndex<items.numObjs-1) { currentTabooIndex++; currentTaboo = items.objs[currentTabooIndex]; } else currentTaboo=-1;//no more taboos } else//save o tmpBag.add(o); index++; } //now we clear the chartSeries and then put back the saved objects only. chartSeries.clear(); //In my test this did not cause the chart to flicker. //But if it does, one could do an update for the part the will be refill and //only clear the rest using delete(start, end). for(int i=0;i<tmpBag.numObjs;i++) chartSeries.add((XYDataItem)tmpBag.objs[i], false);//no notifying just yet. tmpBag.clear(); //it doesn't matter that I clear this twice in a row //(once here, once at next time through this fn), the second time is O(1). chartSeries.fireSeriesChanged(); } double[] getXValues() { double[] xValues = new double[chartSeries.getItemCount()]; for(int i=0;i<xValues.length;i++) xValues[i]=chartSeries.getX(i).doubleValue(); return xValues; } protected void updateSeries(double time, double lastTime) { double d = 0; // FIRST, load the aggregate series with the items aggregateSeries.add(time, d = valueFor(properties.getValue(index)), false); int len = aggregateSeries.getItemCount(); // SECOND, determine if it's time to dump stuff into the main series long interval = globalAttributes.interval; double intervalMark = time % interval; if (! // I think these are the three cases for when we may need to update because // we've exceeded the next interval (intervalMark == 0 || (time - lastTime >= interval) || lastTime % interval > intervalMark)) return; // not yet // THIRD determine how and when to dump stuff into the main series double y = 0; // make compiler happy double temp; switch(globalAttributes.aggregationMethod) { case AGGREGATIONMETHOD_CURRENT: // in this case the aggregateSeries is sort of worthless addToMainSeries(time, d, false); break; case AGGREGATIONMETHOD_MAX: double maxX = 0; for(int i=0;i<len;i++) { XYDataItem item = (XYDataItem)(aggregateSeries.getDataItem(i)); y = item.getY().doubleValue(); temp = item.getX().doubleValue(); if( maxX < temp || i==0) maxX = temp; } addToMainSeries( maxX, y, false ); break; case AGGREGATIONMETHOD_MIN: double minX = 0; for(int i=0;i<len;i++) { XYDataItem item = (XYDataItem)(aggregateSeries.getDataItem(i)); y = item.getY().doubleValue(); temp = item.getX().doubleValue(); if( minX > temp || i==0) minX = temp; } addToMainSeries( minX, y, false ); break; case AGGREGATIONMETHOD_MEAN: double sumX = 0; int n = 0; for(int i=0;i<len;i++) { XYDataItem item = (XYDataItem)(aggregateSeries.getDataItem(i)); y = item.getY().doubleValue(); sumX += item.getX().doubleValue(); n++; } if (n == 0) System.err.println( "No element????" ); else addToMainSeries(sumX / n, y, false); break; default: System.err.println( "There are only four aggregation method implemented" ); } aggregateSeries.clear(); } }