View Javadoc
1   /**
2    * 
3    */
4   package org.opentrafficsim.swing.graphs;
5   
6   import java.awt.BasicStroke;
7   import java.awt.Color;
8   import java.awt.Font;
9   import java.awt.event.ActionEvent;
10  import java.awt.event.ActionListener;
11  import java.util.List;
12  
13  import javax.swing.ButtonGroup;
14  import javax.swing.JMenu;
15  import javax.swing.JPopupMenu;
16  import javax.swing.JRadioButtonMenuItem;
17  
18  import org.djunits.value.vdouble.scalar.Duration;
19  import org.jfree.chart.ChartMouseEvent;
20  import org.jfree.chart.ChartMouseListener;
21  import org.jfree.chart.annotations.XYAnnotation;
22  import org.jfree.chart.annotations.XYLineAnnotation;
23  import org.jfree.chart.annotations.XYTextAnnotation;
24  import org.jfree.chart.entity.AxisEntity;
25  import org.jfree.chart.entity.XYItemEntity;
26  import org.jfree.chart.ui.TextAnchor;
27  import org.jfree.data.Range;
28  import org.opentrafficsim.draw.graphs.FundamentalDiagram;
29  import org.opentrafficsim.draw.graphs.FundamentalDiagram.Quantity;
30  import org.opentrafficsim.draw.graphs.GraphUtil;
31  
32  /**
33   * Embed a FundamentalDiagram in a Swing JPanel.
34   * <p>
35   * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
36   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
37   * </p>
38   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
39   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
40   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
41   */
42  public class SwingFundamentalDiagram extends SwingPlot
43  {
44      /** */
45      private static final long serialVersionUID = 20190823L;
46  
47      /**
48       * Construct a new Swing container for FundamentalDiagram plot.
49       * @param plot the plot to embed
50       */
51      public SwingFundamentalDiagram(final FundamentalDiagram plot)
52      {
53          super(plot);
54      }
55  
56      @Override
57      protected ChartMouseListener getChartMouseListener()
58      {
59          ChartMouseListener toggle = !getPlot().hasLineFD() && getPlot().getSource().getNumberOfSeries() < 2 ? null
60                  : GraphUtil.getToggleSeriesByLegendListener(getPlot().getLegend(), getPlot().getLaneVisible());
61          return new ChartMouseListener()
62          {
63              @SuppressWarnings("unchecked")
64              @Override
65              public void chartMouseClicked(final ChartMouseEvent event)
66              {
67                  if (toggle != null)
68                  {
69                      toggle.chartMouseClicked(event); // forward as we use two listeners
70                  }
71                  // remove any line annotations
72                  for (XYAnnotation annotation : ((List<XYAnnotation>) getPlot().getChart().getXYPlot().getAnnotations()))
73                  {
74                      if (annotation instanceof XYLineAnnotation)
75                      {
76                          getPlot().getChart().getXYPlot().removeAnnotation(annotation);
77                      }
78                  }
79                  // add line annotation for each item in series if the user clicked in an item
80                  if (event.getEntity() instanceof XYItemEntity)
81                  {
82                      XYItemEntity itemEntity = (XYItemEntity) event.getEntity();
83                      int series = itemEntity.getSeriesIndex();
84                      for (int i = 0; i < getPlot().getItemCount(series) - 1; i++)
85                      {
86                          XYLineAnnotation annotation = new XYLineAnnotation(getPlot().getXValue(series, i),
87                                  getPlot().getYValue(series, i), getPlot().getXValue(series, i + 1),
88                                  getPlot().getYValue(series, i + 1), new BasicStroke(1.0f), Color.WHITE);
89                          getPlot().getChart().getXYPlot().addAnnotation(annotation);
90                      }
91                  }
92                  else if (event.getEntity() instanceof AxisEntity)
93                  {
94                      if (((AxisEntity) event.getEntity()).getAxis().equals(getPlot().getChart().getXYPlot().getDomainAxis()))
95                      {
96                          Quantity old = getPlot().getDomainQuantity();
97                          getPlot().setDomainQuantity(getPlot().getOtherQuantity());
98                          getPlot().setOtherQuantity(old);
99                          getPlot().getChart().getXYPlot().getDomainAxis().setLabel(getPlot().getDomainQuantity().label());
100                         getPlot().getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
101                     }
102                     else
103                     {
104                         Quantity old = getPlot().getRangeQuantity();
105                         getPlot().setRangeQuantity(getPlot().getOtherQuantity());
106                         getPlot().setOtherQuantity(old);
107                         getPlot().getChart().getXYPlot().getRangeAxis().setLabel(getPlot().getRangeQuantity().label());
108                         getPlot().getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
109                     }
110                 }
111             }
112 
113             @SuppressWarnings("unchecked")
114             @Override
115             public void chartMouseMoved(final ChartMouseEvent event)
116             {
117                 if (toggle != null)
118                 {
119                     toggle.chartMouseMoved(event); // forward as we use two listeners
120                 }
121                 boolean clearText = true;
122                 // set text annotation and status text to time of item
123                 if (event.getEntity() instanceof XYItemEntity)
124                 {
125                     // create time info for status label
126                     XYItemEntity itemEntity = (XYItemEntity) event.getEntity();
127                     int series = itemEntity.getSeriesIndex();
128                     if (!getPlot().hasLineFD() || series != getPlot().getSeriesCount() - 1)
129                     {
130                         clearText = false;
131                         int item = itemEntity.getItem();
132                         double t = item * getPlot().getSource().getUpdateInterval().si;
133                         getPlot().setTimeInfo(String.format(", %.0fs", t));
134                         double x = getPlot().getXValue(series, item);
135                         double y = getPlot().getYValue(series, item);
136                         Range domain = getPlot().getChart().getXYPlot().getDomainAxis().getRange();
137                         Range range = getPlot().getChart().getXYPlot().getRangeAxis().getRange();
138                         TextAnchor anchor;
139                         if (range.getUpperBound() - y < y - range.getLowerBound())
140                         {
141                             // upper half
142                             if (domain.getUpperBound() - x < x - domain.getLowerBound())
143                             {
144                                 // upper right quadrant
145                                 anchor = TextAnchor.TOP_RIGHT;
146                             }
147                             else
148                             {
149                                 // upper left quadrant, can't use TOP_LEFT as text will be under mouse pointer
150                                 if ((range.getUpperBound() - y)
151                                         / (range.getUpperBound() - range.getLowerBound()) < (x - domain.getLowerBound())
152                                                 / (domain.getUpperBound() - domain.getLowerBound()))
153                                 {
154                                     // closer to top (at least relatively) so move text down
155                                     anchor = TextAnchor.TOP_RIGHT;
156                                 }
157                                 else
158                                 {
159                                     // closer to left (at least relatively) so move text right
160                                     anchor = TextAnchor.BOTTOM_LEFT;
161                                 }
162                             }
163                         }
164                         else if (domain.getUpperBound() - x < x - domain.getLowerBound())
165                         {
166                             // lower right quadrant
167                             anchor = TextAnchor.BOTTOM_RIGHT;
168                         }
169                         else
170                         {
171                             // lower left quadrant
172                             anchor = TextAnchor.BOTTOM_LEFT;
173                         }
174                         XYTextAnnotation textAnnotation = new XYTextAnnotation(String.format("%.0fs", t), x, y);
175                         textAnnotation.setTextAnchor(anchor);
176                         textAnnotation.setFont(textAnnotation.getFont().deriveFont(14.0f).deriveFont(Font.BOLD));
177                         getPlot().getChart().getXYPlot().addAnnotation(textAnnotation);
178                     }
179                 }
180                 // remove texts when mouse is elsewhere, or on FD line
181                 if (clearText)
182                 {
183                     for (XYAnnotation annotation : ((List<XYAnnotation>) getPlot().getChart().getXYPlot().getAnnotations()))
184                     {
185                         if (annotation instanceof XYTextAnnotation)
186                         {
187                             getPlot().getChart().getXYPlot().removeAnnotation(annotation);
188                         }
189                     }
190                     getPlot().setTimeInfo("");
191                 }
192             }
193         };
194     }
195 
196     @Override
197     protected void addPopUpMenuItems(final JPopupMenu popupMenu)
198     {
199         super.addPopUpMenuItems(popupMenu);
200         popupMenu.insert(new JPopupMenu.Separator(), 0);
201 
202         JMenu updMenu = new JMenu("Update frequency");
203         ButtonGroup updGroup = new ButtonGroup();
204         for (int f : getPlot().getSource().getPossibleUpdateFrequencies())
205         {
206             String format = "%dx";
207             JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, f));
208             item.setSelected(f == 1);
209             item.addActionListener(new ActionListener()
210             {
211                 @Override
212                 public void actionPerformed(final ActionEvent e)
213                 {
214 
215                     if ((int) (.5 + getPlot().getSource().getAggregationPeriod().si
216                             / getPlot().getSource().getUpdateInterval().si) != f)
217                     {
218                         Duration interval = Duration.instantiateSI(getPlot().getSource().getAggregationPeriod().si / f);
219                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
220                         {
221                             diagram.setUpdateInterval(interval);
222                         }
223                         // the above setUpdateInterval also recalculates the virtual last update time
224                         // add half an interval to avoid any rounding issues
225                         getPlot().getSource().setUpdateInterval(interval, getPlot().getUpdateTime().plus(interval.times(0.5)));
226 
227                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
228                         {
229                             diagram.getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
230                             diagram.getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
231                             diagram.notifyPlotChange();
232                         }
233                     }
234                 }
235             });
236             updGroup.add(item);
237             updMenu.add(item);
238         }
239         popupMenu.insert(updMenu, 0);
240 
241         JMenu aggMenu = new JMenu("Aggregation period");
242         ButtonGroup aggGroup = new ButtonGroup();
243         for (double t : getPlot().getSource().getPossibleAggregationPeriods())
244         {
245             double t2 = t;
246             String format = "%.0f s";
247             if (t >= 60.0)
248             {
249                 t2 = t / 60.0;
250                 format = "%.0f min";
251             }
252             JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, t2));
253             item.setSelected(t == getPlot().getSource().getAggregationPeriod().si);
254             item.addActionListener(new ActionListener()
255             {
256 
257                 @Override
258                 public void actionPerformed(final ActionEvent e)
259                 {
260                     if (getPlot().getSource().getAggregationPeriod().si != t)
261                     {
262                         int n = (int) (0.5 + getPlot().getSource().getAggregationPeriod().si
263                                 / getPlot().getSource().getUpdateInterval().si);
264                         Duration period = Duration.instantiateSI(t);
265                         Duration interval = period.divide(n);
266                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
267                         {
268                             diagram.setUpdateInterval(interval);
269                         }
270                         // add half an interval to avoid any rounding issues
271                         getPlot().getSource().setAggregationPeriod(period);
272                         getPlot().getSource().setUpdateInterval(period.divide(n),
273                                 getPlot().getUpdateTime().plus(period.divide(n).times(0.5)));
274                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
275                         {
276                             diagram.getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
277                             diagram.getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
278                             diagram.notifyPlotChange();
279                         }
280                     }
281                 }
282 
283             });
284             aggGroup.add(item);
285             aggMenu.add(item);
286         }
287         popupMenu.insert(aggMenu, 0);
288     }
289 
290     /**
291      * Retrieve the plot.
292      * @return the plot
293      */
294     @Override
295     public FundamentalDiagram getPlot()
296     {
297         return (FundamentalDiagram) super.getPlot();
298     }
299 
300 }