1 package org.opentrafficsim.animation.graphs;
2
3 import java.awt.Color;
4
5 import org.djunits.value.vdouble.scalar.Duration;
6 import org.djunits.value.vdouble.scalar.Length;
7 import org.djutils.event.EventListener;
8 import org.djutils.event.EventType;
9 import org.djutils.exceptions.Throw;
10 import org.jfree.chart.JFreeChart;
11 import org.jfree.chart.LegendItem;
12 import org.jfree.chart.LegendItemCollection;
13 import org.jfree.chart.axis.NumberAxis;
14 import org.jfree.chart.plot.XYPlot;
15 import org.jfree.data.DomainOrder;
16 import org.opentrafficsim.animation.BoundsPaintScale;
17 import org.opentrafficsim.animation.graphs.AbstractContourPlot.ContourPaintState;
18 import org.opentrafficsim.animation.graphs.AbstractPlot.PaintState;
19 import org.opentrafficsim.animation.graphs.ContourDataSource.ContourAdditionalDataType;
20 import org.opentrafficsim.animation.graphs.ContourDataSource.ContourDataType;
21 import org.opentrafficsim.animation.graphs.ContourDataSource.Dimension;
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 public abstract class AbstractContourPlot<Z extends Number> extends AbstractSpaceTimePlot<ContourPaintState>
37 implements XyInterpolatedDataset
38 {
39
40
41 private final BoundsPaintScale paintScale;
42
43
44 private final LabelData<Z> labelData;
45
46
47 private final ContourDataSource source;
48
49
50 private final ContourDataType<Z> contourDataType;
51
52
53 private XyInterpolatedBlockRenderer blockRenderer = null;
54
55
56
57
58
59
60
61
62
63 public AbstractContourPlot(final String caption, final ContourDataSource source, final ContourDataType<Z> contourDataType,
64 final BoundsPaintScale paintScale, final LabelData<Z> labelData)
65 {
66 super(caption, source.getInitialUpdateInterval(), source.getPlotScheduler(), source.getDelay(),
67 DEFAULT_INITIAL_UPPER_TIME_BOUND);
68 this.source = Throw.whenNull(source, "dataPool");
69 this.contourDataType = Throw.whenNull(contourDataType, "contourDataType");
70 this.paintScale = Throw.whenNull(paintScale, "paintScale");
71 this.labelData = Throw.whenNull(labelData, "labelData");
72 this.blockRenderer = new XyInterpolatedBlockRenderer(this);
73 this.blockRenderer.setPaintScale(this.paintScale);
74 this.blockRenderer.setBlockHeight(source.getGranularity(Dimension.DISTANCE));
75 this.blockRenderer.setBlockWidth(source.getGranularity(Dimension.TIME));
76 source.addPlot(this);
77 setChart(createChart());
78 }
79
80
81
82
83
84
85
86
87
88 public AbstractContourPlot(final String caption, final ContourDataSource source,
89 final ContourAdditionalDataType<Z, ?> contourDataType, final Bounds<Z> bounds, final LabelData<Z> labelData)
90 {
91 this(caption, source, contourDataType, createPaintScale(bounds), labelData);
92 }
93
94
95
96
97
98
99 private static BoundsPaintScale createPaintScale(final Bounds<?> bounds)
100 {
101 Throw.when(bounds.minimum().doubleValue() >= bounds.maximum().doubleValue(), IllegalArgumentException.class,
102 "Minimum value %s is above or equal to maximum value %s.", bounds.minimum(), bounds.maximum());
103 double[] boundaries = {bounds.minimum().doubleValue(),
104 (bounds.minimum().doubleValue() + bounds.maximum().doubleValue()) / 2.0, bounds.maximum().doubleValue()};
105 Color[] colorValues = {Color.RED, Color.YELLOW, Color.GREEN};
106 return new BoundsPaintScale(boundaries, colorValues);
107 }
108
109
110
111
112
113 private JFreeChart createChart()
114 {
115 NumberAxis xAxis = new NumberAxis("Time [s] \u2192");
116 NumberAxis yAxis = new NumberAxis("Distance [m] \u2192");
117 XYPlot plot = new XYPlot(this, xAxis, yAxis, this.blockRenderer);
118 LegendItemCollection legend = new LegendItemCollection();
119 for (int i = 0;; i++)
120 {
121 double value = this.paintScale.getLowerBound() + i * this.labelData.legendStep().doubleValue();
122 if (value > this.paintScale.getUpperBound() + 1e-6)
123 {
124 break;
125 }
126 legend.add(new LegendItem(String.format(this.labelData.legendFormat(), scale(value)),
127 this.paintScale.getPaint(value)));
128 }
129 legend.add(new LegendItem("No data", Color.BLACK));
130 plot.setFixedLegendItems(legend);
131 final JFreeChart chart = new JFreeChart(getCaption(), plot);
132 return chart;
133 }
134
135 @Override
136 protected ContourPaintState emptyPaintState()
137 {
138 return new ContourPaintState(new float[0], 1.0, 1.0, 0, false, Duration.ZERO);
139 }
140
141
142
143
144
145 protected ContourDataType<Z> getContourDataType()
146 {
147 return this.contourDataType;
148 }
149
150
151
152
153
154
155 protected abstract double scale(double si);
156
157 @Override
158 protected void setPaintState()
159 {
160 super.setPaintState();
161 this.blockRenderer.setInterpolate(getPaintState().interpolate());
162 this.blockRenderer.setBlockHeight(getPaintState().dx());
163 this.blockRenderer.setBlockWidth(getPaintState().dt());
164 }
165
166
167
168
169
170
171
172 @Override
173 public String getStatusLabel(final double domainValue, final double rangeValue)
174 {
175 if (getPaintState().data().length == 0)
176 {
177 return String.format("time %.0fs, distance %.0fm", domainValue, rangeValue);
178 }
179 int i = getPaintState().getSpaceSlice(rangeValue);
180 int j = getPaintState().getTimeSlice(domainValue);
181 int item = j * getPaintState().nSpaceSlices() + i;
182 double zValue = scale(getZValue(1, item));
183 return String.format("time %.0fs, distance %.0fm, " + this.labelData.labelFormat(), domainValue, rangeValue, zValue);
184 }
185
186
187
188 @Override
189 public int getItemCount(final int series)
190 {
191 return getPaintState().getItemCount();
192 }
193
194 @Override
195 public Number getX(final int series, final int item)
196 {
197 return getXValue(series, item);
198 }
199
200 @Override
201 public double getXValue(final int series, final int item)
202 {
203 return getPaintState().getTimeValue(item);
204 }
205
206 @Override
207 public Number getY(final int series, final int item)
208 {
209 return getYValue(series, item);
210 }
211
212 @Override
213 public double getYValue(final int series, final int item)
214 {
215 return getPaintState().getSpaceValue(item);
216 }
217
218 @Override
219 public Number getZ(final int series, final int item)
220 {
221 return getZValue(series, item);
222 }
223
224 @Override
225 public Comparable<String> getSeriesKey(final int series)
226 {
227 return getCaption();
228 }
229
230 @SuppressWarnings("rawtypes")
231 @Override
232 public int indexOf(final Comparable seriesKey)
233 {
234 return 0;
235 }
236
237 @Override
238 public DomainOrder getDomainOrder()
239 {
240 return DomainOrder.ASCENDING;
241 }
242
243 @Override
244 public double getZValue(final int series, final int item)
245 {
246
247 return getPaintState().data()[item];
248 }
249
250 @Override
251 public int getSeriesCount()
252 {
253 return 1;
254 }
255
256 @Override
257 public int getRangeBinCount()
258 {
259 return getPaintState().nSpaceSlices();
260 }
261
262
263
264
265
266
267
268
269
270
271 public void addListener(final EventListener listener, final EventType eventType)
272 {
273 this.source.addListener(listener, eventType);
274 }
275
276
277
278
279
280
281 public double[] getGranularities(final Dimension dimension)
282 {
283 return this.source.getGranularities(dimension);
284 }
285
286
287
288
289
290
291 public double getGranularity(final Dimension dimension)
292 {
293 return this.source.getGranularity(dimension);
294 }
295
296
297
298
299
300
301 public void setGranularity(final Dimension dimension, final double granularity)
302 {
303 this.source.setGranularity(dimension, granularity);
304 invalidate();
305 }
306
307
308
309
310
311 public void setInterpolate(final boolean interpolate)
312 {
313 this.source.setInterpolate(interpolate);
314 invalidate();
315 }
316
317
318
319
320
321 public void setSmooth(final boolean smooth)
322 {
323 this.source.setSmooth(smooth);
324 invalidate();
325 }
326
327 @Override
328 protected void calculatePaintState(final Duration time)
329 {
330 this.source.calculatePaintStateSafe(time);
331 }
332
333 @Override
334 protected Length getEndLocation()
335 {
336 return this.source.getPath().getTotalLength();
337 }
338
339
340
341
342
343
344
345
346
347 public record Bounds<Z extends Number>(Z minimum, Z maximum)
348 {
349 }
350
351
352
353
354
355
356
357
358 public record LabelData<Z extends Number>(Z legendStep, String legendFormat, String labelFormat)
359 {
360 }
361
362
363
364
365
366
367
368
369
370
371 public record ContourPaintState(float[] data, double dx, double dt, int nSpaceSlices, boolean interpolate,
372 Duration getAvailableTime) implements PaintState
373 {
374
375
376
377
378
379 public int getItemCount()
380 {
381 return data().length;
382 }
383
384
385
386
387
388
389 public double getTimeValue(final int item)
390 {
391 return dt() * (item / nSpaceSlices());
392 }
393
394
395
396
397
398
399 public double getSpaceValue(final int item)
400 {
401 return dx() * (item % nSpaceSlices());
402 }
403
404
405
406
407
408
409 public int getTimeSlice(final double t)
410 {
411 int n = (int) (t / dt());
412 int nMax = data().length / nSpaceSlices();
413 return n < 0 ? 0 : (n > nMax ? nMax : n);
414 }
415
416
417
418
419
420
421 public int getSpaceSlice(final double x)
422 {
423 int n = (int) (x / dx());
424 return n < 0 ? 0 : (n > nSpaceSlices() ? nSpaceSlices() : n);
425 }
426
427 }
428
429 }