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().getSource().getNumberOfSeries() < 2 ? null
59                  : GraphUtil.getToggleSeriesByLegendListener(getPlot().getLegend(), getPlot().getLaneVisible());
60          return new ChartMouseListener()
61          {
62              /** {@inheritDoc} */
63              @SuppressWarnings({"unchecked", "synthetic-access"})
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             /** {@inheritDoc} */
114             @SuppressWarnings({"synthetic-access", "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                 // 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                     int item = itemEntity.getItem();
129                     double t = item * getPlot().getSource().getUpdateInterval().si;
130                     getPlot().setTimeInfo(String.format(", %.0fs", t));
131                     double x = getPlot().getXValue(series, item);
132                     double y = getPlot().getYValue(series, item);
133                     Range domain = getPlot().getChart().getXYPlot().getDomainAxis().getRange();
134                     Range range = getPlot().getChart().getXYPlot().getRangeAxis().getRange();
135                     TextAnchor anchor;
136                     if (range.getUpperBound() - y < y - range.getLowerBound())
137                     {
138                         // upper half
139                         if (domain.getUpperBound() - x < x - domain.getLowerBound())
140                         {
141                             // upper right quadrant
142                             anchor = TextAnchor.TOP_RIGHT;
143                         }
144                         else
145                         {
146                             // upper left quadrant, can't use TOP_LEFT as text will be under mouse pointer
147                             if ((range.getUpperBound() - y)
148                                     / (range.getUpperBound() - range.getLowerBound()) < (x - domain.getLowerBound())
149                                             / (domain.getUpperBound() - domain.getLowerBound()))
150                             {
151                                 // closer to top (at least relatively) so move text down
152                                 anchor = TextAnchor.TOP_RIGHT;
153                             }
154                             else
155                             {
156                                 // closer to left (at least relatively) so move text right
157                                 anchor = TextAnchor.BOTTOM_LEFT;
158                             }
159                         }
160                     }
161                     else if (domain.getUpperBound() - x < x - domain.getLowerBound())
162                     {
163                         // lower right quadrant
164                         anchor = TextAnchor.BOTTOM_RIGHT;
165                     }
166                     else
167                     {
168                         // lower left quadrant
169                         anchor = TextAnchor.BOTTOM_LEFT;
170                     }
171                     XYTextAnnotation textAnnotation = new XYTextAnnotation(String.format("%.0fs", t), x, y);
172                     textAnnotation.setTextAnchor(anchor);
173                     textAnnotation.setFont(textAnnotation.getFont().deriveFont(14.0f).deriveFont(Font.BOLD));
174                     getPlot().getChart().getXYPlot().addAnnotation(textAnnotation);
175                 }
176                 // remove texts when mouse is elsewhere
177                 else
178                 {
179                     for (XYAnnotation annotation : ((List<XYAnnotation>) getPlot().getChart().getXYPlot().getAnnotations()))
180                     {
181                         if (annotation instanceof XYTextAnnotation)
182                         {
183                             getPlot().getChart().getXYPlot().removeAnnotation(annotation);
184                         }
185                     }
186                     getPlot().setTimeInfo("");
187                 }
188             }
189         };
190     }
191 
192     /** {@inheritDoc} */
193     @Override
194     protected void addPopUpMenuItems(final JPopupMenu popupMenu)
195     {
196         super.addPopUpMenuItems(popupMenu);
197         popupMenu.insert(new JPopupMenu.Separator(), 0);
198 
199         JMenu updMenu = new JMenu("Update frequency");
200         ButtonGroup updGroup = new ButtonGroup();
201         for (int f : getPlot().getSource().getPossibleUpdateFrequencies())
202         {
203             String format = "%dx";
204             JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, f));
205             item.setSelected(f == 1);
206             item.addActionListener(new ActionListener()
207             {
208                 /** {@inheritDoc} */
209                 @Override
210                 public void actionPerformed(final ActionEvent e)
211                 {
212 
213                     if ((int) (.5 + getPlot().getSource().getAggregationPeriod().si
214                             / getPlot().getSource().getUpdateInterval().si) != f)
215                     {
216                         Duration interval = Duration.instantiateSI(getPlot().getSource().getAggregationPeriod().si / f);
217                         getPlot().setUpdateInterval(interval);
218                         // the above setUpdateInterval also recalculates the virtual last update time
219                         // add half an interval to avoid any rounding issues
220                         getPlot().getSource().setUpdateInterval(interval,
221                                 getPlot().getUpdateTime().plus(interval.times(0.5)), getPlot());
222                         getPlot().getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
223                         getPlot().getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
224                         getPlot().notifyPlotChange();
225                     }
226                 }
227             });
228             updGroup.add(item);
229             updMenu.add(item);
230         }
231         popupMenu.insert(updMenu, 0);
232 
233         JMenu aggMenu = new JMenu("Aggregation period");
234         ButtonGroup aggGroup = new ButtonGroup();
235         for (double t : getPlot().getSource().getPossibleAggregationPeriods())
236         {
237             double t2 = t;
238             String format = "%.0f s";
239             if (t >= 60.0)
240             {
241                 t2 = t / 60.0;
242                 format = "%.0f min";
243             }
244             JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, t2));
245             item.setSelected(t == getPlot().getSource().getAggregationPeriod().si);
246             item.addActionListener(new ActionListener()
247             {
248 
249                 /** {@inheritDoc} */
250                 @SuppressWarnings("synthetic-access")
251                 @Override
252                 public void actionPerformed(final ActionEvent e)
253                 {
254                     if (getPlot().getSource().getAggregationPeriod().si != t)
255                     {
256                         int n = (int) (0.5 + getPlot().getSource().getAggregationPeriod().si
257                                 / getPlot().getSource().getUpdateInterval().si);
258                         Duration period = Duration.instantiateSI(t);
259                         getPlot().setUpdateInterval(period.divide(n));
260                         // add half an interval to avoid any rounding issues
261                         getPlot().getSource().setAggregationPeriod(period);
262                         getPlot().getSource().setUpdateInterval(period.divide(n),
263                                 getPlot().getUpdateTime().plus(period.divide(n).times(0.5)),
264                                 getPlot());
265                         getPlot().getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
266                         getPlot().getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
267                         getPlot().notifyPlotChange();
268                     }
269                 }
270 
271             });
272             aggGroup.add(item);
273             aggMenu.add(item);
274         }
275         popupMenu.insert(aggMenu, 0);
276     }
277 
278     /**
279      * Retrieve the plot.
280      * @return AbstractPlot; the plot
281      */
282     @Override
283     public FundamentalDiagram getPlot()
284     {
285         return (FundamentalDiagram) super.getPlot();
286     }
287 
288 }