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