View Javadoc
1   package org.opentrafficsim.swing.graphs;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   
6   import javax.swing.ButtonGroup;
7   import javax.swing.JCheckBoxMenuItem;
8   import javax.swing.JMenu;
9   import javax.swing.JPopupMenu;
10  import javax.swing.JRadioButtonMenuItem;
11  
12  import org.djutils.event.Event;
13  import org.djutils.event.EventListener;
14  import org.opentrafficsim.animation.graphs.AbstractContourPlot;
15  import org.opentrafficsim.animation.graphs.ContourDataSource;
16  import org.opentrafficsim.animation.graphs.ContourDataSource.Dimension;
17  import org.opentrafficsim.base.OtsRuntimeException;
18  
19  /**
20   * Embed a ContourPlot in a Swing JPanel.
21   * <p>
22   * Copyright (c) 2023-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
24   * </p>
25   * @author Alexander Verbraeck
26   * @author Peter Knoppers
27   * @author Wouter Schakel
28   */
29  public class SwingContourPlot extends SwingSpaceTimePlot implements EventListener
30  {
31      /** Serialization version UID. */
32      private static final long serialVersionUID = 20190823L;
33  
34      /** Map to set time granularity. */
35      private Map<JRadioButtonMenuItem, Double> timeGranularityButtons;
36  
37      /** Map to set space granularity. */
38      private Map<JRadioButtonMenuItem, Double> spaceGranularityButtons;
39  
40      /** Check box for smoothing. */
41      private JCheckBoxMenuItem smoothCheckBox;
42  
43      /** Check box for interpolation. */
44      private JCheckBoxMenuItem interpolateCheckBox;
45  
46      /** Whether to ignore events as this is itself the plot causing change of granularity/interpolate/smooth setting. */
47      private boolean ignoreEvent = false;
48  
49      /**
50       * Create a new SwingContourPlot with embedded plot.
51       * @param plot the plot to embed
52       */
53      public SwingContourPlot(final AbstractContourPlot<?> plot)
54      {
55          super(plot);
56          plot.addListener(this, ContourDataSource.GRANULARITY);
57          plot.addListener(this, ContourDataSource.INTERPOLATE);
58          plot.addListener(this, ContourDataSource.SMOOTH);
59      }
60  
61      @Override
62      protected void addPopUpMenuItems(final JPopupMenu popupMenu)
63      {
64          this.timeGranularityButtons = new LinkedHashMap<>();
65          this.spaceGranularityButtons = new LinkedHashMap<>();
66          super.addPopUpMenuItems(popupMenu);
67          JMenu spaceGranularityMenu = buildMenu("Distance granularity", "%.0f m", 1000, "%.0f km", "setSpaceGranularity",
68                  getPlot().getGranularities(Dimension.DISTANCE), getPlot().getGranularity(Dimension.DISTANCE),
69                  this.spaceGranularityButtons);
70          popupMenu.insert(spaceGranularityMenu, 0);
71          JMenu timeGranularityMenu = buildMenu("Time granularity", "%.0f s", 60.0, "%.0f min", "setTimeGranularity",
72                  getPlot().getGranularities(Dimension.TIME), getPlot().getGranularity(Dimension.TIME),
73                  this.timeGranularityButtons);
74          popupMenu.insert(timeGranularityMenu, 1);
75          this.smoothCheckBox = new JCheckBoxMenuItem("Adaptive smoothing method", false);
76          this.smoothCheckBox.addActionListener((e) ->
77          {
78              SwingContourPlot.this.ignoreEvent = true;
79              getPlot().setSmooth(((JCheckBoxMenuItem) e.getSource()).isSelected());
80              SwingContourPlot.this.ignoreEvent = false;
81          });
82          popupMenu.insert(this.smoothCheckBox, 2);
83          this.interpolateCheckBox = new JCheckBoxMenuItem("Bilinear interpolation", true);
84          this.interpolateCheckBox.addActionListener((e) ->
85          {
86              SwingContourPlot.this.ignoreEvent = true;
87              boolean interpolate = ((JCheckBoxMenuItem) e.getSource()).isSelected();
88              getPlot().setInterpolate(interpolate);
89              SwingContourPlot.this.ignoreEvent = false;
90          });
91          popupMenu.insert(this.interpolateCheckBox, 3);
92      }
93  
94      /**
95       * Create a JMenu to let the user set the granularity.
96       * @param menuName caption for the new JMenu
97       * @param format1 format string for the values in the items under the new JMenu, below formatValue
98       * @param formatValue format value
99       * @param format2 format string for the values in the items under the new JMenu, above and equal to formatValue
100      * @param command prefix for the actionCommand of the items under the new JMenu
101      * @param values array of values to be formatted using the format strings to yield the items under the new JMenu
102      * @param initialValue the currently selected value (used to put the bullet on the correct item)
103      * @param granularityButtons map in to which buttons should be added
104      * @return JMenu with JRadioMenuItems for the values and a bullet on the currentValue item
105      */
106     private JMenu buildMenu(final String menuName, final String format1, final double formatValue, final String format2,
107             final String command, final double[] values, final double initialValue,
108             final Map<JRadioButtonMenuItem, Double> granularityButtons)
109     {
110         JMenu result = new JMenu(menuName);
111         ButtonGroup group = new ButtonGroup();
112         for (double value : values)
113         {
114             JRadioButtonMenuItem item = new JRadioButtonMenuItem(
115                     String.format(value < formatValue ? format1 : format2, value < formatValue ? value : value / formatValue));
116             granularityButtons.put(item, value);
117             item.setSelected(value == initialValue);
118             item.setActionCommand(command);
119             item.addActionListener((actionEvent) ->
120             {
121                 SwingContourPlot.this.ignoreEvent = true;
122                 if (command.equalsIgnoreCase("setSpaceGranularity"))
123                 {
124                     double granularity = SwingContourPlot.this.spaceGranularityButtons.get(actionEvent.getSource());
125                     // offer instead of setting as data pool may be working in the background using the granularity
126                     getPlot().setGranularity(Dimension.DISTANCE, granularity);
127                 }
128                 else if (command.equalsIgnoreCase("setTimeGranularity"))
129                 {
130                     double granularity = SwingContourPlot.this.timeGranularityButtons.get(actionEvent.getSource());
131                     // offer instead of setting as data pool may be working in the background using the granularity
132                     getPlot().setGranularity(Dimension.TIME, granularity);
133                 }
134                 else
135                 {
136                     throw new OtsRuntimeException("Unknown ActionEvent");
137                 }
138                 SwingContourPlot.this.ignoreEvent = false;
139             });
140             result.add(item);
141             group.add(item);
142         }
143         return result;
144     }
145 
146     @Override
147     public AbstractContourPlot<?> getPlot()
148     {
149         return (AbstractContourPlot<?>) super.getPlot();
150     }
151 
152     @Override
153     public void notify(final Event event)
154     {
155         if (this.ignoreEvent)
156         {
157             return;
158         }
159         if (event.getType().equals(ContourDataSource.GRANULARITY))
160         {
161             Object[] payload = (Object[]) event.getContent();
162             Dimension dimension = (Dimension) payload[0];
163             double granularity = (double) payload[1];
164             Map<JRadioButtonMenuItem, Double> buttonMap =
165                     Dimension.DISTANCE.equals(dimension) ? this.spaceGranularityButtons : this.timeGranularityButtons;
166             for (JRadioButtonMenuItem button : buttonMap.keySet())
167             {
168                 button.setSelected(Math.abs(buttonMap.get(button) - granularity) < 0.001);
169             }
170         }
171         else if (event.getType().equals(ContourDataSource.INTERPOLATE))
172         {
173             this.interpolateCheckBox.setSelected((boolean) event.getContent());
174         }
175         else if (event.getType().equals(ContourDataSource.SMOOTH))
176         {
177             this.smoothCheckBox.setSelected((boolean) event.getContent());
178         }
179     }
180 
181 }