View Javadoc
1   package org.opentrafficsim.draw.graphs;
2   
3   import java.awt.Color;
4   import java.awt.event.ActionEvent;
5   import java.awt.event.ActionListener;
6   
7   import org.djunits.value.vdouble.scalar.Time;
8   import org.djutils.exceptions.Throw;
9   import org.jfree.chart.JFreeChart;
10  import org.jfree.chart.LegendItem;
11  import org.jfree.chart.LegendItemCollection;
12  import org.jfree.chart.axis.NumberAxis;
13  import org.jfree.chart.plot.XYPlot;
14  import org.jfree.data.DomainOrder;
15  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
16  import org.opentrafficsim.draw.core.BoundsPaintScale;
17  import org.opentrafficsim.draw.graphs.ContourDataSource.ContourDataType;
18  import org.opentrafficsim.draw.graphs.ContourDataSource.Dimension;
19  
20  /**
21   * Class for contour plots. The data that is plotted is stored in a {@code ContourDataSource}, which may be shared among several
22   * contour plots along the same path. This abstract class takes care of the interactions between the plot and the data pool. Sub
23   * classes only need to specify a few plot specific variables and functionalities.
24   * <p>
25   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
27   * <p>
28   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 4 okt. 2018 <br>
29   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
30   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
31   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
32   * @param <Z> z-value type
33   */
34  public abstract class AbstractContourPlot<Z extends Number> extends AbstractSamplerPlot
35          implements XYInterpolatedDataset, ActionListener
36  {
37  
38      /** Color scale for the graph. */
39      private final BoundsPaintScale paintScale;
40  
41      /** Difference of successive values in the legend. */
42      private final Z legendStep;
43  
44      /** Format string used to create the captions in the legend. */
45      private final String legendFormat;
46  
47      /** Format string used to create status label (under the mouse). */
48      private final String valueFormat;
49  
50      /** Data pool. */
51      private final ContourDataSource<?> dataPool;
52  
53      /** Block renderer in chart. */
54      private XYInterpolatedBlockRenderer blockRenderer = null;
55  
56      /**
57       * Constructor with specified paint scale.
58       * @param caption String; caption
59       * @param simulator OTSSimulatorInterface; simulator
60       * @param dataPool ContourDataSource&lt;?&gt;; data pool
61       * @param paintScale BoundsPaintScale; paint scale
62       * @param legendStep Z; increment between color legend entries
63       * @param legendFormat String; format string for the captions in the color legend
64       * @param valueFormat String; format string used to create status label (under the mouse)
65       */
66      public AbstractContourPlot(final String caption, final OTSSimulatorInterface simulator, final ContourDataSource<?> dataPool,
67              final BoundsPaintScale paintScale, final Z legendStep, final String legendFormat, final String valueFormat)
68      {
69          super(caption, dataPool.getUpdateInterval(), simulator, dataPool.getSamplerData(), dataPool.getPath(), dataPool.getDelay());
70          dataPool.registerContourPlot(this);
71          this.dataPool = dataPool;
72          this.paintScale = paintScale;
73          this.legendStep = legendStep;
74          this.legendFormat = legendFormat;
75          this.valueFormat = valueFormat;
76          this.blockRenderer = new XYInterpolatedBlockRenderer(this);
77          this.blockRenderer.setPaintScale(this.paintScale);
78          this.blockRenderer.setBlockHeight(dataPool.getGranularity(Dimension.DISTANCE));
79          this.blockRenderer.setBlockWidth(dataPool.getGranularity(Dimension.TIME));
80          setChart(createChart());
81      }
82  
83      /**
84       * Constructor with default paint scale.
85       * @param caption String; caption
86       * @param simulator OTSSimulatorInterface; simulator
87       * @param dataPool ContourDataSource&lt;?&gt;; data pool
88       * @param legendStep Z; increment between color legend entries
89       * @param legendFormat String; format string for the captions in the color legend
90       * @param minValue Z; minimum value
91       * @param maxValue Z; maximum value
92       * @param valueFormat String; format string used to create status label (under the mouse)
93       */
94      @SuppressWarnings("parameternumber")
95      public AbstractContourPlot(final String caption, final OTSSimulatorInterface simulator, final ContourDataSource<?> dataPool,
96              final Z legendStep, final String legendFormat, final Z minValue, final Z maxValue, final String valueFormat)
97      {
98          this(caption, simulator, dataPool, createPaintScale(minValue, maxValue), legendStep, legendFormat, valueFormat);
99      }
100 
101     /**
102      * Creates a default paint scale from red, via yellow to green.
103      * @param minValue Number; minimum value
104      * @param maxValue Number; maximum value
105      * @return BoundsPaintScale; default paint scale
106      */
107     private static BoundsPaintScale createPaintScale(final Number minValue, final Number maxValue)
108     {
109         Throw.when(minValue.doubleValue() >= maxValue.doubleValue(), IllegalArgumentException.class,
110                 "Minimum value %s is below or equal to maxumum value %s.", minValue, maxValue);
111         double[] boundaries =
112                 {minValue.doubleValue(), (minValue.doubleValue() + maxValue.doubleValue()) / 2.0, maxValue.doubleValue()};
113         Color[] colorValues = {Color.RED, Color.YELLOW, Color.GREEN};
114         return new BoundsPaintScale(boundaries, colorValues);
115     }
116 
117     /**
118      * Create a chart.
119      * @return JFreeChart; chart
120      */
121     private JFreeChart createChart()
122     {
123         NumberAxis xAxis = new NumberAxis("Time [s] \u2192");
124         NumberAxis yAxis = new NumberAxis("Distance [m] \u2192");
125         XYPlot plot = new XYPlot(this, xAxis, yAxis, this.blockRenderer);
126         LegendItemCollection legend = new LegendItemCollection();
127         for (int i = 0;; i++)
128         {
129             double value = this.paintScale.getLowerBound() + i * this.legendStep.doubleValue();
130             if (value > this.paintScale.getUpperBound() + 1e-6)
131             {
132                 break;
133             }
134             legend.add(new LegendItem(String.format(this.legendFormat, scale(value)), this.paintScale.getPaint(value)));
135         }
136         legend.add(new LegendItem("No data", Color.BLACK));
137         plot.setFixedLegendItems(legend);
138         final JFreeChart chart = new JFreeChart(getCaption(), plot);
139         return chart;
140     }
141 
142     /**
143      * Returns the time granularity, just for information.
144      * @return double; time granularity
145      */
146     public double getTimeGranularity()
147     {
148         return this.dataPool.getGranularity(Dimension.TIME);
149     }
150 
151     /**
152      * Returns the space granularity, just for information.
153      * @return double; space granularity
154      */
155     public double getSpaceGranularity()
156     {
157         return this.dataPool.getGranularity(Dimension.DISTANCE);
158     }
159 
160     /**
161      * Sets the correct space granularity radio button to selected. This is done from a {@code DataPool} to keep multiple plots
162      * consistent.
163      * @param granularity double; space granularity
164      */
165     public final void setSpaceGranularity(final double granularity)
166     {
167         this.blockRenderer.setBlockHeight(granularity);
168         this.dataPool.spaceAxis.setGranularity(granularity); // XXX: AV added 16-5-2020
169     }
170 
171     /**
172      * Sets the correct time granularity radio button to selected. This is done from a {@code DataPool} to keep multiple plots
173      * consistent.
174      * @param granularity double; time granularity
175      */
176     public final void setTimeGranularity(final double granularity)
177     {
178         this.blockRenderer.setBlockWidth(granularity);
179         this.dataPool.timeAxis.setGranularity(granularity); // XXX: AV added 16-5-2020
180     }
181 
182     /**
183      * Sets the check box for interpolated rendering and block renderer setting. This is done from a {@code DataPool} to keep
184      * multiple plots consistent.
185      * @param interpolate boolean; selected or not
186      */
187     public final void setInterpolation(final boolean interpolate)
188     {
189         this.blockRenderer.setInterpolate(interpolate);
190         this.dataPool.timeAxis.setInterpolate(interpolate); // XXX: AV added 16-5-2020
191         this.dataPool.spaceAxis.setInterpolate(interpolate); // XXX: AV added 16-5-2020
192     } 
193     
194     /**
195      * Returns the data pool for sub classes.
196      * @return ContourDataSource; data pool for subclasses
197      */
198     public final ContourDataSource<?> getDataPool()
199     {
200         return this.dataPool;
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     public final int getItemCount(final int series)
206     {
207         return this.dataPool.getBinCount(Dimension.DISTANCE) * this.dataPool.getBinCount(Dimension.TIME);
208     }
209 
210     /** {@inheritDoc} */
211     @Override
212     public final Number getX(final int series, final int item)
213     {
214         return getXValue(series, item);
215     }
216 
217     /** {@inheritDoc} */
218     @Override
219     public final double getXValue(final int series, final int item)
220     {
221         return this.dataPool.getAxisValue(Dimension.TIME, item);
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public final Number getY(final int series, final int item)
227     {
228         return getYValue(series, item);
229     }
230 
231     /** {@inheritDoc} */
232     @Override
233     public final double getYValue(final int series, final int item)
234     {
235         return this.dataPool.getAxisValue(Dimension.DISTANCE, item);
236     }
237 
238     /** {@inheritDoc} */
239     @Override
240     public final Number getZ(final int series, final int item)
241     {
242         return getZValue(series, item);
243     }
244 
245     /** {@inheritDoc} */
246     @Override
247     public final Comparable<String> getSeriesKey(final int series)
248     {
249         return getCaption();
250     }
251 
252     /** {@inheritDoc} */
253     @SuppressWarnings("rawtypes")
254     @Override
255     public final int indexOf(final Comparable seriesKey)
256     {
257         return 0;
258     }
259 
260     /** {@inheritDoc} */
261     @Override
262     public final DomainOrder getDomainOrder()
263     {
264         return DomainOrder.ASCENDING;
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     public final double getZValue(final int series, final int item)
270     {
271         // default 1 series
272         return getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME));
273     }
274 
275     /** {@inheritDoc} */
276     @Override
277     public final int getSeriesCount()
278     {
279         return 1; // default
280     }
281 
282     /** {@inheritDoc} */
283     @Override
284     public int getRangeBinCount()
285     {
286         return this.dataPool.getBinCount(Dimension.DISTANCE);
287     }
288 
289     /**
290      * Returns the status label when the mouse is over the given location.
291      * @param domainValue double; domain value (x-axis)
292      * @param rangeValue double; range value (y-axis)
293      * @return String; status label when the mouse is over the given location
294      */
295     @Override
296     public final String getStatusLabel(final double domainValue, final double rangeValue)
297     {
298         if (this.dataPool == null)
299         {
300             return String.format("time %.0fs, distance %.0fm", domainValue, rangeValue);
301         }
302         int i = this.dataPool.getAxisBin(Dimension.DISTANCE, rangeValue);
303         int j = this.dataPool.getAxisBin(Dimension.TIME, domainValue);
304         int item = j * this.dataPool.getBinCount(Dimension.DISTANCE) + i;
305         double zValue = scale(
306                 getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME)));
307         return String.format("time %.0fs, distance %.0fm, " + this.valueFormat, domainValue, rangeValue, zValue);
308     }
309 
310     /** {@inheritDoc} */
311     @Override
312     protected final void increaseTime(final Time time)
313     {
314         if (this.dataPool != null) // dataPool is null at construction
315         {
316             this.dataPool.increaseTime(time);
317         }
318     }
319 
320     /**
321      * Obtain value for cell from the data pool.
322      * @param item int; item number
323      * @param cellLength double; cell length
324      * @param cellSpan double; cell duration
325      * @return double; value for cell from the data pool
326      */
327     protected abstract double getValue(int item, double cellLength, double cellSpan);
328 
329     /**
330      * Scale the value from SI to the desired unit for users.
331      * @param si double; SI value
332      * @return double; scaled value
333      */
334     protected abstract double scale(double si);
335 
336     /**
337      * Returns the contour data type for use in a {@code ContourDataSource}.
338      * @return CountorDataType; contour data type
339      */
340     protected abstract ContourDataType<Z, ?> getContourDataType();
341 
342     /**
343      * Returns the block renderer.
344      * @return block renderer
345      */
346     public XYInterpolatedBlockRenderer getBlockRenderer()
347     {
348         return this.blockRenderer;
349     }
350 
351     @Override
352     public final void actionPerformed(final ActionEvent actionEvent)
353     {
354         String command = actionEvent.getActionCommand();
355         if (command.equalsIgnoreCase("setSpaceGranularity"))
356         {
357             // The source field is abused to contain the granularity
358             double granularity = (double) actionEvent.getSource();
359             setSpaceGranularity(granularity);
360         }
361         else if (command.equalsIgnoreCase("setTimeGranularity"))
362         {
363             // The source field is abused to contain the granularity
364             double granularity = (double) actionEvent.getSource();
365             setTimeGranularity(granularity);
366         }
367         else
368         {
369             throw new RuntimeException("Unhandled ActionEvent (actionCommand is " + actionEvent.getActionCommand() + "\")");
370         }
371     }
372 
373 }