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