1 package org.opentrafficsim.swing.graphs;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Point;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.geom.Point2D;
9 import java.awt.geom.Rectangle2D;
10 import java.util.Optional;
11
12 import javax.swing.ButtonGroup;
13 import javax.swing.JMenu;
14 import javax.swing.JPopupMenu;
15 import javax.swing.JRadioButtonMenuItem;
16
17 import org.djutils.draw.point.Point2d;
18 import org.djutils.exceptions.Throw;
19 import org.jfree.chart.ChartMouseEvent;
20 import org.jfree.chart.ChartMouseListener;
21 import org.jfree.chart.annotations.XYLineAnnotation;
22 import org.jfree.chart.annotations.XYTextAnnotation;
23 import org.jfree.chart.entity.PlotEntity;
24 import org.jfree.chart.plot.XYPlot;
25 import org.opentrafficsim.draw.colorer.trajectory.AccelerationTrajectoryColorer;
26 import org.opentrafficsim.draw.colorer.trajectory.FixedTrajectoryColorer;
27 import org.opentrafficsim.draw.colorer.trajectory.IdTrajectoryColorer;
28 import org.opentrafficsim.draw.colorer.trajectory.SpeedTrajectoryColorer;
29 import org.opentrafficsim.draw.colorer.trajectory.TrajectoryColorer;
30 import org.opentrafficsim.draw.graphs.GraphUtil;
31 import org.opentrafficsim.draw.graphs.TrajectoryPlot;
32
33
34
35
36
37
38
39
40
41
42
43 public class SwingTrajectoryPlot extends SwingSpaceTimePlot
44 {
45
46 private static final long serialVersionUID = 20190823L;
47
48
49 private boolean density;
50
51
52 private boolean flow;
53
54
55 private Point2D.Double from;
56
57
58 private Point2D.Double to;
59
60
61 private XYLineAnnotation lineAnnotation;
62
63
64 private XYTextAnnotation textAnnotation;
65
66
67 private JMenu colorMenu;
68
69
70 private ButtonGroup colorButtonGroup = new ButtonGroup();
71
72
73
74
75
76
77 public SwingTrajectoryPlot(final TrajectoryPlot plot)
78 {
79 this(plot, true);
80 }
81
82
83
84
85
86
87 public SwingTrajectoryPlot(final TrajectoryPlot plot, final boolean defaultColorers)
88 {
89 super(plot);
90 if (plot.getLaneCount() == 1)
91 {
92 if (defaultColorers)
93 {
94 addColorer(new FixedTrajectoryColorer(Color.BLUE, "Blue"), true);
95 addColorer(new IdTrajectoryColorer(), false);
96 addColorer(new SpeedTrajectoryColorer(), false);
97 addColorer(new AccelerationTrajectoryColorer(), false);
98 }
99 else
100 {
101
102 plot.setColorer(new FixedTrajectoryColorer(Color.BLUE, "Blue"));
103 }
104 }
105 }
106
107
108
109
110
111
112
113 public void addColorer(final TrajectoryColorer colorer, final boolean selected)
114 {
115 Throw.when(getPlot().getLaneCount() > 1, IllegalStateException.class,
116 "Trajectory plots of multiple lanes do not support colorers.");
117 if (this.colorMenu == null)
118 {
119
120 return;
121 }
122 JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(colorer.getName());
123 menuItem.addActionListener(new ActionListener()
124 {
125 @Override
126 public void actionPerformed(final ActionEvent e)
127 {
128 SwingTrajectoryPlot.this.getPlot().setColorer(colorer);
129 SwingTrajectoryPlot.this.getPlot().update();
130 }
131 });
132 this.colorButtonGroup.add(menuItem);
133 if (selected || this.colorButtonGroup.getButtonCount() == 1)
134 {
135 menuItem.setSelected(true);
136 SwingTrajectoryPlot.this.getPlot().setColorer(colorer);
137 }
138 menuItem.setFont(this.colorMenu.getFont());
139 this.colorMenu.add(menuItem);
140 this.colorMenu.setVisible(true);
141 }
142
143 @Override
144 protected void addPopUpMenuItems(final JPopupMenu popupMenu)
145 {
146 super.addPopUpMenuItems(popupMenu);
147 if (getPlot().getLaneCount() == 1)
148 {
149 this.colorMenu = new JMenu("Color");
150 this.colorMenu.setVisible(false);
151 popupMenu.insert(this.colorMenu, 0);
152 }
153 }
154
155
156
157
158
159 @Override
160 protected Optional<ChartMouseListener> getChartMouseListener()
161 {
162
163 ChartMouseListener toggle = getPlot().getPath().getNumberOfSeries() < 2 ? null
164 : GraphUtil.getToggleSeriesByLegendListener(getPlot().getLegend(), getPlot().getLaneVisible());
165 return Optional.of(new ChartMouseListener()
166 {
167 @Override
168 public void chartMouseClicked(final ChartMouseEvent event)
169 {
170 if (toggle != null)
171 {
172 toggle.chartMouseClicked(event);
173 }
174 if (event.getEntity() instanceof PlotEntity)
175 {
176 removeAnnotations();
177 if (SwingTrajectoryPlot.this.from == null)
178 {
179 if (event.getTrigger().isControlDown())
180 {
181 SwingTrajectoryPlot.this.density = false;
182 SwingTrajectoryPlot.this.flow = false;
183 }
184 else if (event.getTrigger().isShiftDown())
185 {
186 SwingTrajectoryPlot.this.density = true;
187 SwingTrajectoryPlot.this.flow = false;
188 }
189 else if (event.getTrigger().isAltDown())
190 {
191 SwingTrajectoryPlot.this.density = false;
192 SwingTrajectoryPlot.this.flow = true;
193 }
194 else
195 {
196 SwingTrajectoryPlot.this.from = null;
197 SwingTrajectoryPlot.this.to = null;
198 return;
199 }
200 SwingTrajectoryPlot.this.from = getValuePoint(event);
201 SwingTrajectoryPlot.this.to = null;
202 }
203 else
204 {
205 SwingTrajectoryPlot.this.to = getValuePoint(event);
206 removeAnnotations();
207 snap(SwingTrajectoryPlot.this.to);
208 drawLine(SwingTrajectoryPlot.this.to);
209 drawStatistics();
210 SwingTrajectoryPlot.this.from = null;
211 SwingTrajectoryPlot.this.to = null;
212 }
213 }
214 }
215
216 @Override
217 public void chartMouseMoved(final ChartMouseEvent event)
218 {
219 if (toggle != null)
220 {
221 toggle.chartMouseMoved(event);
222 }
223 if (event.getEntity() instanceof PlotEntity && SwingTrajectoryPlot.this.from != null
224 && SwingTrajectoryPlot.this.to == null)
225 {
226 removeAnnotations();
227 Point2D.Double toPoint = getValuePoint(event);
228 snap(toPoint);
229 drawLine(toPoint);
230 }
231 }
232
233 });
234 }
235
236
237
238
239
240
241 private Point2D.Double getValuePoint(final ChartMouseEvent event)
242 {
243 Point2D p = getChartPanel().translateScreenToJava2D(new Point(event.getTrigger().getX(), event.getTrigger().getY()));
244 XYPlot plot = getChartPanel().getChart().getXYPlot();
245 Rectangle2D dataArea = getChartPanel().getChartRenderingInfo().getPlotInfo().getDataArea();
246 double x = plot.getDomainAxis().java2DToValue(p.getX(), dataArea, plot.getDomainAxisEdge());
247 double y = plot.getRangeAxis().java2DToValue(p.getY(), dataArea, plot.getRangeAxisEdge());
248 return new Point2D.Double(x, y);
249 }
250
251
252
253
254
255 private void drawLine(final Point2D.Double toPoint)
256 {
257 this.lineAnnotation =
258 new XYLineAnnotation(this.from.x, this.from.y, toPoint.x, toPoint.y, new BasicStroke(2.0f), Color.WHITE);
259 getPlot().getChart().getXYPlot().addAnnotation(this.lineAnnotation);
260 }
261
262
263
264
265 private void drawStatistics()
266 {
267 double dx = this.to.x - this.from.x;
268 double dy = this.to.y - this.from.y;
269 double v = 3.6 * dy / dx;
270
271 String label;
272 if (this.density || this.flow)
273 {
274 int n = 0;
275 for (int i = 0; i < getPlot().getSeriesCount(); i++)
276 {
277
278 int k = getPlot().getItemCount(i) - 1;
279 double x1 = Math.min(this.from.x, this.to.x);
280 double y1 = Math.min(this.from.y, this.to.y);
281 double x2 = Math.max(this.from.x, this.to.x);
282 double y2 = Math.max(this.from.y, this.to.y);
283 double x3 = Math.min(getPlot().getXValue(i, 0), getPlot().getXValue(i, k));
284 double y3 = Math.min(getPlot().getYValue(i, 0), getPlot().getYValue(i, k));
285 double x4 = Math.max(getPlot().getXValue(i, 0), getPlot().getXValue(i, k));
286 double y4 = Math.max(getPlot().getYValue(i, 0), getPlot().getYValue(i, k));
287 if (x3 <= x2 && y3 <= y2 && x1 <= x4 && y1 <= y4)
288 {
289 for (int j = 0; j < k; j++)
290 {
291 if (Point2d.intersectionOfLineSegments(this.from.x, this.from.y, this.to.x, this.to.y,
292 getPlot().getXValue(i, j), getPlot().getYValue(i, j), getPlot().getXValue(i, j + 1),
293 getPlot().getYValue(i, j + 1)) != null)
294 {
295 n++;
296 break;
297 }
298 }
299 }
300 }
301 if (this.density)
302 {
303 label = String.format("%.1f veh/km", Math.abs(1000.0 * n / dy));
304 }
305 else
306 {
307 label = String.format("%.1f veh/h", Math.abs(3600.0 * n / dx));
308 }
309 }
310 else
311 {
312 label = String.format("%.1f km/h", v);
313 }
314
315 this.textAnnotation = new XYTextAnnotation(label, this.from.x, this.from.y);
316 getPlot().getChart().getXYPlot().addAnnotation(this.textAnnotation);
317
318 }
319
320
321
322
323 private void removeAnnotations()
324 {
325 if (SwingTrajectoryPlot.this.lineAnnotation != null)
326 {
327 getPlot().getChart().getXYPlot().removeAnnotation(SwingTrajectoryPlot.this.lineAnnotation);
328 }
329 if (SwingTrajectoryPlot.this.textAnnotation != null)
330 {
331 getPlot().getChart().getXYPlot().removeAnnotation(SwingTrajectoryPlot.this.textAnnotation);
332 }
333 }
334
335
336
337
338
339 private void snap(final Point2D.Double toPoint)
340 {
341 if (this.density)
342 {
343 toPoint.x = this.from.x;
344 }
345 if (this.flow)
346 {
347 toPoint.y = this.from.y;
348 }
349 }
350
351
352
353
354
355 @Override
356 public TrajectoryPlot getPlot()
357 {
358 return (TrajectoryPlot) super.getPlot();
359 }
360
361 }