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 import java.util.LinkedHashMap;
7 import java.util.Map;
8
9 import javax.swing.ButtonGroup;
10 import javax.swing.JCheckBoxMenuItem;
11 import javax.swing.JMenu;
12 import javax.swing.JPopupMenu;
13 import javax.swing.JRadioButtonMenuItem;
14
15 import org.djunits.value.vdouble.scalar.Time;
16 import org.djutils.exceptions.Throw;
17 import org.jfree.chart.JFreeChart;
18 import org.jfree.chart.LegendItem;
19 import org.jfree.chart.LegendItemCollection;
20 import org.jfree.chart.axis.NumberAxis;
21 import org.jfree.chart.plot.XYPlot;
22 import org.jfree.data.DomainOrder;
23 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
24 import org.opentrafficsim.draw.core.BoundsPaintScale;
25 import org.opentrafficsim.draw.graphs.ContourDataSource.ContourDataType;
26 import org.opentrafficsim.draw.graphs.ContourDataSource.Dimension;
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 public abstract class AbstractContourPlot<Z extends Number> extends AbstractSamplerPlot implements XYInterpolatedDataset
43 {
44
45
46 private static final long serialVersionUID = 20181004L;
47
48
49 private final BoundsPaintScale paintScale;
50
51
52 private final Z legendStep;
53
54
55 private final String legendFormat;
56
57
58 private final String valueFormat;
59
60
61 private final ContourDataSource<?> dataPool;
62
63
64 private Map<JRadioButtonMenuItem, Double> timeGranularityButtons = new LinkedHashMap<>();
65
66
67 private Map<JRadioButtonMenuItem, Double> spaceGranularityButtons = new LinkedHashMap<>();
68
69
70 private JCheckBoxMenuItem smoothCheckBox;
71
72
73 private JCheckBoxMenuItem interpolateCheckBox;
74
75
76 private XYInterpolatedBlockRenderer blockRenderer = null;
77
78
79
80
81
82
83
84
85
86
87
88 public AbstractContourPlot(final String caption, final OTSSimulatorInterface simulator, final ContourDataSource<?> dataPool,
89 final BoundsPaintScale paintScale, final Z legendStep, final String legendFormat, final String valueFormat)
90 {
91 super(caption, dataPool.getUpdateInterval(), simulator, dataPool.getSampler(), dataPool.getPath(), dataPool.getDelay());
92 dataPool.registerContourPlot(this);
93 this.dataPool = dataPool;
94 this.paintScale = paintScale;
95 this.legendStep = legendStep;
96 this.legendFormat = legendFormat;
97 this.valueFormat = valueFormat;
98 this.blockRenderer = new XYInterpolatedBlockRenderer(this);
99 this.blockRenderer.setPaintScale(this.paintScale);
100 this.blockRenderer.setBlockHeight(dataPool.getGranularity(Dimension.DISTANCE));
101 this.blockRenderer.setBlockWidth(dataPool.getGranularity(Dimension.TIME));
102 setChart(createChart());
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116 @SuppressWarnings("parameternumber")
117 public AbstractContourPlot(final String caption, final OTSSimulatorInterface simulator, final ContourDataSource<?> dataPool,
118 final Z legendStep, final String legendFormat, final Z minValue, final Z maxValue, final String valueFormat)
119 {
120 this(caption, simulator, dataPool, createPaintScale(minValue, maxValue), legendStep, legendFormat, valueFormat);
121 }
122
123
124
125
126
127
128
129 private static BoundsPaintScale createPaintScale(final Number minValue, final Number maxValue)
130 {
131 Throw.when(minValue.doubleValue() >= maxValue.doubleValue(), IllegalArgumentException.class,
132 "Minimum value %s is below or equal to maxumum value %s.", minValue, maxValue);
133 double[] boundaries =
134 { minValue.doubleValue(), (minValue.doubleValue() + maxValue.doubleValue()) / 2.0, maxValue.doubleValue() };
135 Color[] colorValues = { Color.RED, Color.YELLOW, Color.GREEN };
136 return new BoundsPaintScale(boundaries, colorValues);
137 }
138
139
140
141
142
143 private JFreeChart createChart()
144 {
145 NumberAxis xAxis = new NumberAxis("Time [s] \u2192");
146 NumberAxis yAxis = new NumberAxis("Distance [m] \u2192");
147 XYPlot plot = new XYPlot(this, xAxis, yAxis, this.blockRenderer);
148 LegendItemCollection legend = new LegendItemCollection();
149 for (int i = 0;; i++)
150 {
151 double value = this.paintScale.getLowerBound() + i * this.legendStep.doubleValue();
152 if (value > this.paintScale.getUpperBound() + 1e-6)
153 {
154 break;
155 }
156 legend.add(new LegendItem(String.format(this.legendFormat, scale(value)), this.paintScale.getPaint(value)));
157 }
158 legend.add(new LegendItem("No data", Color.BLACK));
159 plot.setFixedLegendItems(legend);
160 final JFreeChart chart = new JFreeChart(getCaption(), plot);
161 return chart;
162 }
163
164
165 @Override
166 protected void addPopUpMenuItems(final JPopupMenu popupMenu)
167 {
168 super.addPopUpMenuItems(popupMenu);
169 JMenu spaceGranularityMenu = buildMenu("Distance granularity", "%.0f m", 1000, "%.0f km", "setSpaceGranularity",
170 this.dataPool.getGranularities(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.DISTANCE),
171 this.spaceGranularityButtons);
172 popupMenu.insert(spaceGranularityMenu, 0);
173 JMenu timeGranularityMenu = buildMenu("Time granularity", "%.0f s", 60.0, "%.0f min", "setTimeGranularity",
174 this.dataPool.getGranularities(Dimension.TIME), this.dataPool.getGranularity(Dimension.TIME),
175 this.timeGranularityButtons);
176 popupMenu.insert(timeGranularityMenu, 1);
177 this.smoothCheckBox = new JCheckBoxMenuItem("Adaptive smoothing method", false);
178 this.smoothCheckBox.addActionListener(new ActionListener()
179 {
180
181 @SuppressWarnings("synthetic-access")
182 @Override
183 public void actionPerformed(final ActionEvent e)
184 {
185 AbstractContourPlot.this.dataPool.setSmooth(((JCheckBoxMenuItem) e.getSource()).isSelected());
186 notifyPlotChange();
187 }
188 });
189 popupMenu.insert(this.smoothCheckBox, 2);
190 this.interpolateCheckBox = new JCheckBoxMenuItem("Bilinear interpolation", true);
191 this.interpolateCheckBox.addActionListener(new ActionListener()
192 {
193
194 @SuppressWarnings("synthetic-access")
195 @Override
196 public void actionPerformed(final ActionEvent e)
197 {
198 boolean interpolate = ((JCheckBoxMenuItem) e.getSource()).isSelected();
199 AbstractContourPlot.this.blockRenderer.setInterpolate(interpolate);
200 AbstractContourPlot.this.dataPool.setInterpolate(interpolate);
201 notifyPlotChange();
202 }
203 });
204 popupMenu.insert(this.interpolateCheckBox, 3);
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218
219 private JMenu buildMenu(final String menuName, final String format1, final double formatValue, final String format2,
220 final String command, final double[] values, final double initialValue,
221 final Map<JRadioButtonMenuItem, Double> granularityButtons)
222 {
223 JMenu result = new JMenu(menuName);
224 ButtonGroup group = new ButtonGroup();
225 for (double value : values)
226 {
227 JRadioButtonMenuItem item = new JRadioButtonMenuItem(
228 String.format(value < formatValue ? format1 : format2, value < formatValue ? value : value / formatValue));
229 granularityButtons.put(item, value);
230 item.setSelected(value == initialValue);
231 item.setActionCommand(command);
232 item.addActionListener(new ActionListener()
233 {
234
235 @SuppressWarnings("synthetic-access")
236 @Override
237 public void actionPerformed(final ActionEvent actionEvent)
238 {
239 if (command.equalsIgnoreCase("setSpaceGranularity"))
240 {
241 double granularity = AbstractContourPlot.this.spaceGranularityButtons.get(actionEvent.getSource());
242 AbstractContourPlot.this.dataPool.setGranularity(Dimension.DISTANCE, granularity);
243 }
244 else if (command.equalsIgnoreCase("setTimeGranularity"))
245 {
246 double granularity = AbstractContourPlot.this.timeGranularityButtons.get(actionEvent.getSource());
247 AbstractContourPlot.this.dataPool.setGranularity(Dimension.TIME, granularity);
248 }
249 else
250 {
251 throw new RuntimeException("Unknown ActionEvent");
252 }
253 }
254 });
255 result.add(item);
256 group.add(item);
257 }
258 return result;
259 }
260
261
262
263
264
265 public double getTimeGranularity()
266 {
267 return this.dataPool.getGranularity(Dimension.TIME);
268 }
269
270
271
272
273
274 public double getSpaceGranularity()
275 {
276 return this.dataPool.getGranularity(Dimension.DISTANCE);
277 }
278
279
280
281
282
283
284 protected final void setSpaceGranularityRadioButton(final double granularity)
285 {
286 this.blockRenderer.setBlockHeight(granularity);
287 for (JRadioButtonMenuItem button : this.spaceGranularityButtons.keySet())
288 {
289 button.setSelected(this.spaceGranularityButtons.get(button) == granularity);
290 }
291 }
292
293
294
295
296
297
298 protected final void setTimeGranularityRadioButton(final double granularity)
299 {
300 this.blockRenderer.setBlockWidth(granularity);
301 for (JRadioButtonMenuItem button : this.timeGranularityButtons.keySet())
302 {
303 button.setSelected(this.timeGranularityButtons.get(button) == granularity);
304 }
305 }
306
307
308
309
310
311 protected final void setSmoothing(final boolean smooth)
312 {
313 this.smoothCheckBox.setSelected(smooth);
314 }
315
316
317
318
319
320
321 protected final void setInterpolation(final boolean interpolate)
322 {
323 this.blockRenderer.setInterpolate(interpolate);
324 this.interpolateCheckBox.setSelected(interpolate);
325 }
326
327
328
329
330
331 protected final ContourDataSource<?> getDataPool()
332 {
333 return this.dataPool;
334 }
335
336
337 @Override
338 public final int getItemCount(final int series)
339 {
340 return this.dataPool.getBinCount(Dimension.DISTANCE) * this.dataPool.getBinCount(Dimension.TIME);
341 }
342
343
344 @Override
345 public final Number getX(final int series, final int item)
346 {
347 return getXValue(series, item);
348 }
349
350
351 @Override
352 public final double getXValue(final int series, final int item)
353 {
354 return this.dataPool.getAxisValue(Dimension.TIME, item);
355 }
356
357
358 @Override
359 public final Number getY(final int series, final int item)
360 {
361 return getYValue(series, item);
362 }
363
364
365 @Override
366 public final double getYValue(final int series, final int item)
367 {
368 return this.dataPool.getAxisValue(Dimension.DISTANCE, item);
369 }
370
371
372 @Override
373 public final Number getZ(final int series, final int item)
374 {
375 return getZValue(series, item);
376 }
377
378
379 @Override
380 public final Comparable<String> getSeriesKey(final int series)
381 {
382 return getCaption();
383 }
384
385
386 @SuppressWarnings("rawtypes")
387 @Override
388 public final int indexOf(final Comparable seriesKey)
389 {
390 return 0;
391 }
392
393
394 @Override
395 public final DomainOrder getDomainOrder()
396 {
397 return DomainOrder.ASCENDING;
398 }
399
400
401 @Override
402 public final double getZValue(final int series, final int item)
403 {
404
405 return getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME));
406 }
407
408
409 @Override
410 public final int getSeriesCount()
411 {
412 return 1;
413 }
414
415
416 @Override
417 public int getRangeBinCount()
418 {
419 return this.dataPool.getBinCount(Dimension.DISTANCE);
420 }
421
422
423
424
425
426
427
428 protected final String getStatusLabel(final double domainValue, final double rangeValue)
429 {
430 if (this.dataPool == null)
431 {
432 return String.format("time %.0fs, distance %.0fm", domainValue, rangeValue);
433 }
434 int i = this.dataPool.getAxisBin(Dimension.DISTANCE, rangeValue);
435 int j = this.dataPool.getAxisBin(Dimension.TIME, domainValue);
436 int item = j * this.dataPool.getBinCount(Dimension.DISTANCE) + i;
437 double zValue = scale(
438 getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME)));
439 return String.format("time %.0fs, distance %.0fm, " + this.valueFormat, domainValue, rangeValue, zValue);
440 }
441
442
443 @Override
444 protected final void increaseTime(final Time time)
445 {
446 if (this.dataPool != null)
447 {
448 this.dataPool.increaseTime(time);
449 }
450 }
451
452
453
454
455
456
457
458
459 protected abstract double getValue(int item, double cellLength, double cellSpan);
460
461
462
463
464
465
466 protected abstract double scale(double si);
467
468
469
470
471
472 protected abstract ContourDataType<Z, ?> getContourDataType();
473
474 }