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.base.OtsRuntimeException;
15  import org.opentrafficsim.draw.graphs.AbstractContourPlot;
16  import org.opentrafficsim.draw.graphs.ContourDataSource;
17  import org.opentrafficsim.draw.graphs.ContourDataSource.Dimension;
18  
19  /**
20   * Embed a ContourPlot in a Swing JPanel.
21   * <p>
22   * Copyright (c) 2023-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
27   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
28   */
29  public class SwingContourPlot extends SwingSpaceTimePlot implements EventListener
30  {
31      /** */
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.getDataPool().addListener(this, ContourDataSource.GRANULARITY);
57          plot.getDataPool().addListener(this, ContourDataSource.INTERPOLATE);
58          plot.getDataPool().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().getDataPool().getGranularities(Dimension.DISTANCE),
69                  getPlot().getDataPool().getGranularity(Dimension.DISTANCE), this.spaceGranularityButtons);
70          popupMenu.insert(spaceGranularityMenu, 0);
71          JMenu timeGranularityMenu = buildMenu("Time granularity", "%.0f s", 60.0, "%.0f min", "setTimeGranularity",
72                  getPlot().getDataPool().getGranularities(Dimension.TIME),
73                  getPlot().getDataPool().getGranularity(Dimension.TIME), 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().getDataPool().setSmooth(((JCheckBoxMenuItem) e.getSource()).isSelected());
80              getPlot().notifyPlotChange();
81              SwingContourPlot.this.ignoreEvent = false;
82          });
83          popupMenu.insert(this.smoothCheckBox, 2);
84          this.interpolateCheckBox = new JCheckBoxMenuItem("Bilinear interpolation", true);
85          this.interpolateCheckBox.addActionListener((e) ->
86          {
87              SwingContourPlot.this.ignoreEvent = true;
88              boolean interpolate = ((JCheckBoxMenuItem) e.getSource()).isSelected();
89              getPlot().getBlockRenderer().setInterpolate(interpolate);
90              getPlot().getDataPool().setInterpolate(interpolate);
91              getPlot().notifyPlotChange();
92              SwingContourPlot.this.ignoreEvent = false;
93          });
94          popupMenu.insert(this.interpolateCheckBox, 3);
95      }
96  
97      /**
98       * Create a JMenu to let the user set the granularity.
99       * @param menuName caption for the new JMenu
100      * @param format1 format string for the values in the items under the new JMenu, below formatValue
101      * @param formatValue format value
102      * @param format2 format string for the values in the items under the new JMenu, above and equal to formatValue
103      * @param command prefix for the actionCommand of the items under the new JMenu
104      * @param values array of values to be formatted using the format strings to yield the items under the new JMenu
105      * @param initialValue the currently selected value (used to put the bullet on the correct item)
106      * @param granularityButtons map in to which buttons should be added
107      * @return JMenu with JRadioMenuItems for the values and a bullet on the currentValue item
108      */
109     private JMenu buildMenu(final String menuName, final String format1, final double formatValue, final String format2,
110             final String command, final double[] values, final double initialValue,
111             final Map<JRadioButtonMenuItem, Double> granularityButtons)
112     {
113         JMenu result = new JMenu(menuName);
114         ButtonGroup group = new ButtonGroup();
115         for (double value : values)
116         {
117             JRadioButtonMenuItem item = new JRadioButtonMenuItem(
118                     String.format(value < formatValue ? format1 : format2, value < formatValue ? value : value / formatValue));
119             granularityButtons.put(item, value);
120             item.setSelected(value == initialValue);
121             item.setActionCommand(command);
122             item.addActionListener((actionEvent) ->
123             {
124                 SwingContourPlot.this.ignoreEvent = true;
125                 if (command.equalsIgnoreCase("setSpaceGranularity"))
126                 {
127                     double granularity = SwingContourPlot.this.spaceGranularityButtons.get(actionEvent.getSource());
128                     // offer instead of setting as data pool may be working in the background using the granularity
129                     getPlot().getDataPool().offerGranularity(Dimension.DISTANCE, granularity);
130                 }
131                 else if (command.equalsIgnoreCase("setTimeGranularity"))
132                 {
133                     double granularity = SwingContourPlot.this.timeGranularityButtons.get(actionEvent.getSource());
134                     // offer instead of setting as data pool may be working in the background using the granularity
135                     getPlot().getDataPool().offerGranularity(Dimension.TIME, granularity);
136                 }
137                 else
138                 {
139                     throw new OtsRuntimeException("Unknown ActionEvent");
140                 }
141                 SwingContourPlot.this.ignoreEvent = false;
142             });
143             result.add(item);
144             group.add(item);
145         }
146         return result;
147     }
148 
149     @Override
150     public AbstractContourPlot<?> getPlot()
151     {
152         return (AbstractContourPlot<?>) super.getPlot();
153     }
154 
155     @Override
156     public void notify(final Event event)
157     {
158         if (this.ignoreEvent)
159         {
160             return;
161         }
162         if (event.getType().equals(ContourDataSource.GRANULARITY))
163         {
164             Object[] payload = (Object[]) event.getContent();
165             Dimension dimension = (Dimension) payload[0];
166             double granularity = (double) payload[1];
167             Map<JRadioButtonMenuItem, Double> buttonMap =
168                     Dimension.DISTANCE.equals(dimension) ? this.spaceGranularityButtons : this.timeGranularityButtons;
169             for (JRadioButtonMenuItem button : buttonMap.keySet())
170             {
171                 button.setSelected(Math.abs(buttonMap.get(button) - granularity) < 0.001);
172             }
173         }
174         else if (event.getType().equals(ContourDataSource.INTERPOLATE))
175         {
176             this.interpolateCheckBox.setSelected((boolean) event.getContent());
177         }
178         else if (event.getType().equals(ContourDataSource.SMOOTH))
179         {
180             this.smoothCheckBox.setSelected((boolean) event.getContent());
181         }
182     }
183 
184 }