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