1 package org.opentrafficsim.graphs;
2
3 import java.awt.BorderLayout;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.io.Serializable;
7 import java.util.ArrayList;
8
9 import javax.swing.ButtonGroup;
10 import javax.swing.JFrame;
11 import javax.swing.JLabel;
12 import javax.swing.JMenu;
13 import javax.swing.JPopupMenu;
14 import javax.swing.JRadioButtonMenuItem;
15 import javax.swing.SwingConstants;
16 import javax.swing.event.EventListenerList;
17
18 import org.djunits.unit.FrequencyUnit;
19 import org.djunits.unit.LinearDensityUnit;
20 import org.djunits.unit.SpeedUnit;
21 import org.djunits.value.vdouble.scalar.Duration;
22 import org.djunits.value.vdouble.scalar.Frequency;
23 import org.djunits.value.vdouble.scalar.LinearDensity;
24 import org.djunits.value.vdouble.scalar.Speed;
25 import org.jfree.chart.ChartFactory;
26 import org.jfree.chart.ChartPanel;
27 import org.jfree.chart.JFreeChart;
28 import org.jfree.chart.StandardChartTheme;
29 import org.jfree.chart.axis.NumberAxis;
30 import org.jfree.chart.axis.ValueAxis;
31 import org.jfree.chart.event.AxisChangeEvent;
32 import org.jfree.chart.plot.PlotOrientation;
33 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
34 import org.jfree.data.DomainOrder;
35 import org.jfree.data.general.DatasetChangeEvent;
36 import org.jfree.data.general.DatasetChangeListener;
37 import org.jfree.data.general.DatasetGroup;
38 import org.jfree.data.xy.XYDataset;
39 import org.opentrafficsim.core.compatibility.Compatible;
40 import org.opentrafficsim.core.gtu.RelativePosition;
41 import org.opentrafficsim.core.network.NetworkException;
42 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
43 import org.opentrafficsim.road.network.lane.CrossSectionElement;
44 import org.opentrafficsim.road.network.lane.Lane;
45 import org.opentrafficsim.road.network.lane.object.sensor.AbstractSensor;
46
47 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
48 import nl.tudelft.simulation.dsol.SimRuntimeException;
49 import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
50 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
51 import nl.tudelft.simulation.language.Throw;
52
53
54
55
56
57
58
59
60
61
62
63
64 public class FundamentalDiagramLane extends JFrame implements XYDataset, ActionListener
65 {
66
67 private static final long serialVersionUID = 20140701L;
68
69
70 private JFreeChart chartPanel;
71
72
73 private final String caption;
74
75
76 private final JLabel statusLabel;
77
78
79 private final Duration aggregationTime;
80
81
82 private ArrayList<Sample> samples = new ArrayList<Sample>();
83
84
85 private Axis densityAxis = new Axis(new LinearDensity(0, LinearDensityUnit.PER_KILOMETER), new LinearDensity(200,
86 LinearDensityUnit.PER_KILOMETER), null, 0d, "Density [veh/km]", "Density", "density %.1f veh/km");
87
88
89
90
91 public final Axis getDensityAxis()
92 {
93 return this.densityAxis;
94 }
95
96
97 private Axis speedAxis = new Axis(new Speed(0, SpeedUnit.KM_PER_HOUR), new Speed(120, SpeedUnit.KM_PER_HOUR), null, 0d,
98 "Speed [km/h]", "Speed", "speed %.0f km/h");
99
100
101
102
103 public final Axis getSpeedAxis()
104 {
105 return this.speedAxis;
106 }
107
108
109
110
111 public final Axis getFlowAxis()
112 {
113 return this.flowAxis;
114 }
115
116
117 private Axis flowAxis = new Axis(new Frequency(0, FrequencyUnit.PER_HOUR), new Frequency(3000d, FrequencyUnit.HERTZ),
118 null, 0d, "Flow [veh/h]", "Flow", "flow %.0f veh/h");
119
120
121 private Axis xAxis;
122
123
124 private Axis yAxis;
125
126
127 private transient EventListenerList listenerList = new EventListenerList();
128
129
130 private DatasetGroup datasetGroup = null;
131
132
133 private final Lane lane;
134
135
136 private final DEVSSimulatorInterface.TimeDoubleUnit simulator;
137
138
139 int flow = 0;
140
141
142
143
144
145 public final String getYAxisFormat()
146 {
147 return this.yAxis.getFormat();
148 }
149
150
151
152
153
154 public final String getXAxisFormat()
155 {
156 return this.xAxis.getFormat();
157 }
158
159
160
161
162
163
164
165
166
167
168
169
170 public FundamentalDiagramLane(final String caption, final Duration aggregationTime, final Lane lane,
171 final Compatible detectedGTUTypes, final DEVSSimulatorInterface.TimeDoubleUnit simulator) throws NetworkException,
172 SimRuntimeException
173 {
174 if (aggregationTime.getSI() <= 0)
175 {
176 throw new Error("Aggregation time must be > 0 (got " + aggregationTime + ")");
177 }
178 this.aggregationTime = aggregationTime;
179 this.caption = caption;
180 this.lane = lane;
181 this.simulator = simulator;
182 ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
183 this.chartPanel =
184 ChartFactory.createXYLineChart(this.caption, "", "", this, PlotOrientation.VERTICAL, false, false, false);
185 FixCaption.fixCaption(this.chartPanel);
186 final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) this.chartPanel.getXYPlot().getRenderer();
187 renderer.setDefaultShapesVisible(true);
188
189 final ChartPanel cp = new ChartPanel(this.chartPanel);
190 PointerHandler ph = new PointerHandler()
191 {
192
193
194 @Override
195 void updateHint(final double domainValue, final double rangeValue)
196 {
197 if (Double.isNaN(domainValue))
198 {
199 setStatusText(" ");
200 return;
201 }
202 String s1 = String.format(getXAxisFormat(), domainValue);
203 String s2 = String.format(getYAxisFormat(), rangeValue);
204 setStatusText(s1 + ", " + s2);
205 }
206
207 };
208 cp.addMouseMotionListener(ph);
209 cp.addMouseListener(ph);
210 cp.setMouseWheelEnabled(true);
211 final JMenu subMenu = new JMenu("Set layout");
212 final ButtonGroup group = new ButtonGroup();
213 final JRadioButtonMenuItem defaultItem = addMenuItem(subMenu, group, getDensityAxis(), this.flowAxis, true);
214 addMenuItem(subMenu, group, this.flowAxis, this.speedAxis, false);
215 addMenuItem(subMenu, group, this.densityAxis, this.speedAxis, false);
216 actionPerformed(new ActionEvent(this, 0, defaultItem.getActionCommand()));
217 final JPopupMenu popupMenu = cp.getPopupMenu();
218 popupMenu.insert(subMenu, 0);
219 this.add(cp, BorderLayout.CENTER);
220 this.statusLabel = new JLabel(" ", SwingConstants.CENTER);
221 this.add(this.statusLabel, BorderLayout.SOUTH);
222 simulator.scheduleEventRel(this.aggregationTime, this, this, "addData", null);
223 new FlowSensor(lane, detectedGTUTypes);
224 }
225
226
227
228
229
230 public final void setStatusText(final String newText)
231 {
232 this.statusLabel.setText(newText);
233 }
234
235
236
237
238 public final Duration getAggregationTime()
239 {
240 return this.aggregationTime;
241 }
242
243
244
245
246
247
248
249
250
251
252
253 private JRadioButtonMenuItem addMenuItem(final JMenu subMenu, final ButtonGroup group, final Axis xAxisToSelect,
254 final Axis yAxisToSelect, final boolean selected)
255 {
256 final JRadioButtonMenuItem item =
257 new JRadioButtonMenuItem(yAxisToSelect.getShortName() + " / " + xAxisToSelect.getShortName());
258 item.setSelected(selected);
259 item.setActionCommand(yAxisToSelect.getShortName() + "/" + xAxisToSelect.getShortName());
260 item.addActionListener(this);
261 subMenu.add(item);
262 group.add(item);
263 return item;
264 }
265
266
267
268
269
270 public final void addData() throws SimRuntimeException
271 {
272
273 double n = this.lane.getGtuList().size();
274 double density = n / this.lane.getLength().si;
275 if (density > 0.0)
276 {
277 double meanSpeed = 0.0;
278 for (LaneBasedGTU gtu : this.lane.getGtuList())
279 {
280 meanSpeed += 1 / gtu.getSpeed().si;
281 }
282 meanSpeed = n / meanSpeed;
283 this.samples.add(new Sample(meanSpeed, density, this.flow / this.aggregationTime.si));
284 this.flow = 0;
285 }
286 this.simulator.scheduleEventRel(this.aggregationTime, this, this, "addData", null);
287 }
288
289
290
291
292
293
294 private static void configureAxis(final ValueAxis valueAxis, final Axis axis)
295 {
296 valueAxis.setLabel("\u2192 " + axis.getName());
297 valueAxis.setRange(axis.getMinimumValue().getInUnit(), axis.getMaximumValue().getInUnit());
298 }
299
300
301
302
303 public final void reGraph()
304 {
305 NumberAxis numberAxis = new NumberAxis();
306 configureAxis(numberAxis, this.xAxis);
307 this.chartPanel.getXYPlot().setDomainAxis(numberAxis);
308 this.chartPanel.getPlot().axisChanged(new AxisChangeEvent(numberAxis));
309 numberAxis = new NumberAxis();
310 configureAxis(numberAxis, this.yAxis);
311 this.chartPanel.getXYPlot().setRangeAxis(numberAxis);
312 this.chartPanel.getPlot().axisChanged(new AxisChangeEvent(numberAxis));
313 notifyListeners(new DatasetChangeEvent(this, null));
314 }
315
316
317
318
319
320 private void notifyListeners(final DatasetChangeEvent event)
321 {
322 for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
323 {
324 dcl.datasetChanged(event);
325 }
326 }
327
328
329 @Override
330 public final int getSeriesCount()
331 {
332 return 1;
333 }
334
335
336 @Override
337 public final Comparable<Integer> getSeriesKey(final int series)
338 {
339 return series;
340 }
341
342
343 @SuppressWarnings("rawtypes")
344 @Override
345 public final int indexOf(final Comparable seriesKey)
346 {
347 if (seriesKey instanceof Integer)
348 {
349 return (Integer) seriesKey;
350 }
351 return -1;
352 }
353
354
355 @Override
356 public final void addChangeListener(final DatasetChangeListener listener)
357 {
358 this.listenerList.add(DatasetChangeListener.class, listener);
359 }
360
361
362 @Override
363 public final void removeChangeListener(final DatasetChangeListener listener)
364 {
365 this.listenerList.remove(DatasetChangeListener.class, listener);
366 }
367
368
369 @Override
370 public final DatasetGroup getGroup()
371 {
372 return this.datasetGroup;
373 }
374
375
376 @Override
377 public final void setGroup(final DatasetGroup group)
378 {
379 this.datasetGroup = group;
380 }
381
382
383 @Override
384 public final DomainOrder getDomainOrder()
385 {
386 return DomainOrder.ASCENDING;
387 }
388
389
390 @Override
391 public final int getItemCount(final int series)
392 {
393 return this.samples.size();
394 }
395
396
397
398
399
400
401
402 private Double getSample(final int item, final Axis axis)
403 {
404 if (item >= this.samples.size())
405 {
406 return Double.NaN;
407 }
408 double result = this.samples.get(item).getValue(axis);
409 return result;
410 }
411
412
413 @Override
414 public final Number getX(final int series, final int item)
415 {
416 return getXValue(series, item);
417 }
418
419
420 @Override
421 public final double getXValue(final int series, final int item)
422 {
423 return getSample(item, this.xAxis);
424 }
425
426
427 @Override
428 public final Number getY(final int series, final int item)
429 {
430 return getYValue(series, item);
431 }
432
433
434 @Override
435 public final double getYValue(final int series, final int item)
436 {
437 return getSample(item, this.yAxis);
438 }
439
440
441 @SuppressFBWarnings("ES_COMPARING_STRINGS_WITH_EQ")
442 @Override
443 public final void actionPerformed(final ActionEvent actionEvent)
444 {
445 final String command = actionEvent.getActionCommand();
446
447 final String[] fields = command.split("[/]");
448 if (fields.length == 2)
449 {
450 for (String field : fields)
451 {
452 if (field.equalsIgnoreCase(this.densityAxis.getShortName()))
453 {
454 if (field == fields[0])
455 {
456 this.yAxis = this.densityAxis;
457 }
458 else
459 {
460 this.xAxis = this.densityAxis;
461 }
462 }
463 else if (field.equalsIgnoreCase(this.flowAxis.getShortName()))
464 {
465 if (field == fields[0])
466 {
467 this.yAxis = this.flowAxis;
468 }
469 else
470 {
471 this.xAxis = this.flowAxis;
472 }
473 }
474 else if (field.equalsIgnoreCase(this.speedAxis.getShortName()))
475 {
476 if (field == fields[0])
477 {
478 this.yAxis = this.speedAxis;
479 }
480 else
481 {
482 this.xAxis = this.speedAxis;
483 }
484 }
485 else
486 {
487 throw new Error("Cannot find axis name: " + field);
488 }
489 }
490 reGraph();
491 }
492 else
493 {
494 throw new Error("Unknown ActionEvent");
495 }
496 }
497
498
499 @Override
500 public final String toString()
501 {
502 return "FundamentalDiagramLane [caption=" + this.caption + ", aggregationTime=" + this.aggregationTime
503 + ", samples.size=" + this.samples.size() + ", lane=" + this.lane + ", flow=" + this.flow + "]";
504 }
505
506
507
508
509
510
511
512 class Sample implements Serializable
513 {
514
515 private static final long serialVersionUID = 20140000L;
516
517
518 private final double meanSpeed;
519
520
521 private final double density;
522
523
524 private final double flow;
525
526
527
528
529
530
531 Sample(final double meanSpeed, final double density, final double flow)
532 {
533 super();
534 this.meanSpeed = meanSpeed;
535 this.density = density;
536 this.flow = flow;
537 }
538
539
540
541
542
543
544 public double getValue(final Axis axis)
545 {
546 if (axis == getDensityAxis())
547 {
548 return 1000.0 * this.density;
549 }
550 else if (axis == getFlowAxis())
551 {
552 return 3600.0 * this.meanSpeed * this.density;
553 }
554 else if (axis == getSpeedAxis())
555 {
556 return 3.6 * this.meanSpeed;
557 }
558 else
559 {
560 throw new Error("Sample.getValue: Can not identify axis");
561 }
562 }
563
564
565 @Override
566 public final String toString()
567 {
568 return "Sample [meanSpeed=" + this.meanSpeed + ", density=" + this.density + ", flow=" + this.flow + "]";
569 }
570 }
571
572
573 private class FlowSensor extends AbstractSensor
574 {
575
576 private static final long serialVersionUID = 1L;
577
578
579
580
581
582
583
584 FlowSensor(final Lane lane, final Compatible detectedGTUTypes) throws NetworkException
585 {
586 super("FLOW", lane, lane.getLength().divideBy(2.0), RelativePosition.FRONT, null, detectedGTUTypes);
587 }
588
589
590 @Override
591 public void triggerResponse(final LaneBasedGTU gtu)
592 {
593 FundamentalDiagramLane.this.flow += 1;
594 }
595
596
597 @Override
598 public final String toString()
599 {
600 return "FlowSensor []";
601 }
602
603
604 @Override
605 public FlowSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator,
606 final boolean animation) throws NetworkException
607 {
608 Throw.when(!(newCSE instanceof Lane), NetworkException.class, "sensors can only be cloned for Lanes");
609 return new FlowSensor((Lane) newCSE, getDetectedGTUTypes());
610 }
611
612 }
613
614 }