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