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   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
36   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
37   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
38   */
39  public class SwingFundamentalDiagram extends SwingPlot
40  {
41  
42      /**  */
43      private static final long serialVersionUID = 20190823L;
44  
45      /**
46       * Construct a new Swing container for FundamentalDiagram plot.
47       * @param plot FundamentalDiagram; the plot to embed
48       */
49      public SwingFundamentalDiagram(final FundamentalDiagram plot)
50      {
51          super(plot);
52      }
53  
54      /** {@inheritDoc} */
55      @Override
56      protected ChartMouseListener getChartMouseListener()
57      {
58          ChartMouseListener toggle = !getPlot().hasLineFD() && getPlot().getSource().getNumberOfSeries() < 2 ? null : GraphUtil
59              .getToggleSeriesByLegendListener(getPlot().getLegend(), getPlot().getLaneVisible());
60          return new ChartMouseListener()
61          {
62              /** {@inheritDoc} */
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), getPlot().getYValue(
87                              series, i), getPlot().getXValue(series, i + 1), getPlot().getYValue(series, i + 1), new BasicStroke(
88                                  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             /** {@inheritDoc} */
114             @SuppressWarnings("unchecked")
115             @Override
116             public void chartMouseMoved(final ChartMouseEvent event)
117             {
118                 if (toggle != null)
119                 {
120                     toggle.chartMouseMoved(event); // forward as we use two listeners
121                 }
122                 boolean clearText = true;
123                 // set text annotation and status text to time of item
124                 if (event.getEntity() instanceof XYItemEntity)
125                 {
126                     // create time info for status label
127                     XYItemEntity itemEntity = (XYItemEntity) event.getEntity();
128                     int series = itemEntity.getSeriesIndex();
129                     if (!getPlot().hasLineFD() || series != getPlot().getSeriesCount() - 1)
130                     {
131                         clearText = false;
132                         int item = itemEntity.getItem();
133                         double t = item * getPlot().getSource().getUpdateInterval().si;
134                         getPlot().setTimeInfo(String.format(", %.0fs", t));
135                         double x = getPlot().getXValue(series, item);
136                         double y = getPlot().getYValue(series, item);
137                         Range domain = getPlot().getChart().getXYPlot().getDomainAxis().getRange();
138                         Range range = getPlot().getChart().getXYPlot().getRangeAxis().getRange();
139                         TextAnchor anchor;
140                         if (range.getUpperBound() - y < y - range.getLowerBound())
141                         {
142                             // upper half
143                             if (domain.getUpperBound() - x < x - domain.getLowerBound())
144                             {
145                                 // upper right quadrant
146                                 anchor = TextAnchor.TOP_RIGHT;
147                             }
148                             else
149                             {
150                                 // upper left quadrant, can't use TOP_LEFT as text will be under mouse pointer
151                                 if ((range.getUpperBound() - y) / (range.getUpperBound() - range.getLowerBound()) < (x - domain
152                                     .getLowerBound()) / (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     /** {@inheritDoc} */
197     @Override
198     protected void addPopUpMenuItems(final JPopupMenu popupMenu)
199     {
200         super.addPopUpMenuItems(popupMenu);
201         popupMenu.insert(new JPopupMenu.Separator(), 0);
202 
203         JMenu updMenu = new JMenu("Update frequency");
204         ButtonGroup updGroup = new ButtonGroup();
205         for (int f : getPlot().getSource().getPossibleUpdateFrequencies())
206         {
207             String format = "%dx";
208             JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, f));
209             item.setSelected(f == 1);
210             item.addActionListener(new ActionListener()
211             {
212                 /** {@inheritDoc} */
213                 @Override
214                 public void actionPerformed(final ActionEvent e)
215                 {
216 
217                     if ((int) (.5 + getPlot().getSource().getAggregationPeriod().si / getPlot().getSource()
218                         .getUpdateInterval().si) != f)
219                     {
220                         Duration interval = Duration.instantiateSI(getPlot().getSource().getAggregationPeriod().si / f);
221                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
222                         {
223                             diagram.setUpdateInterval(interval);
224                         }
225                         // the above setUpdateInterval also recalculates the virtual last update time
226                         // add half an interval to avoid any rounding issues
227                         getPlot().getSource().setUpdateInterval(interval, getPlot().getUpdateTime().plus(interval.times(0.5)));
228 
229                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
230                         {
231                             diagram.getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
232                             diagram.getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
233                             diagram.notifyPlotChange();
234                         }
235                     }
236                 }
237             });
238             updGroup.add(item);
239             updMenu.add(item);
240         }
241         popupMenu.insert(updMenu, 0);
242 
243         JMenu aggMenu = new JMenu("Aggregation period");
244         ButtonGroup aggGroup = new ButtonGroup();
245         for (double t : getPlot().getSource().getPossibleAggregationPeriods())
246         {
247             double t2 = t;
248             String format = "%.0f s";
249             if (t >= 60.0)
250             {
251                 t2 = t / 60.0;
252                 format = "%.0f min";
253             }
254             JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, t2));
255             item.setSelected(t == getPlot().getSource().getAggregationPeriod().si);
256             item.addActionListener(new ActionListener()
257             {
258 
259                 /** {@inheritDoc} */
260                 @Override
261                 public void actionPerformed(final ActionEvent e)
262                 {
263                     if (getPlot().getSource().getAggregationPeriod().si != t)
264                     {
265                         int n = (int) (0.5 + getPlot().getSource().getAggregationPeriod().si / getPlot().getSource()
266                             .getUpdateInterval().si);
267                         Duration period = Duration.instantiateSI(t);
268                         Duration interval = period.divide(n);
269                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
270                         {
271                             diagram.setUpdateInterval(interval);
272                         }
273                         // add half an interval to avoid any rounding issues
274                         getPlot().getSource().setAggregationPeriod(period);
275                         getPlot().getSource().setUpdateInterval(period.divide(n), getPlot().getUpdateTime().plus(period.divide(
276                             n).times(0.5)));
277                         for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
278                         {
279                             diagram.getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
280                             diagram.getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
281                             diagram.notifyPlotChange();
282                         }
283                     }
284                 }
285 
286             });
287             aggGroup.add(item);
288             aggMenu.add(item);
289         }
290         popupMenu.insert(aggMenu, 0);
291     }
292 
293     /**
294      * Retrieve the plot.
295      * @return AbstractPlot; the plot
296      */
297     @Override
298     public FundamentalDiagram getPlot()
299     {
300         return (FundamentalDiagram) super.getPlot();
301     }
302 
303 }