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 }