View Javadoc
1   package org.opentrafficsim.swing.graphs;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Dimension;
5   import java.awt.event.ActionEvent;
6   import java.awt.event.ActionListener;
7   import java.awt.event.WindowAdapter;
8   import java.awt.event.WindowEvent;
9   import java.io.BufferedOutputStream;
10  import java.io.File;
11  import java.io.FileOutputStream;
12  import java.io.IOException;
13  import java.io.OutputStream;
14  import java.util.Optional;
15  
16  import javax.swing.JFileChooser;
17  import javax.swing.JFrame;
18  import javax.swing.JLabel;
19  import javax.swing.JMenuItem;
20  import javax.swing.JPopupMenu;
21  import javax.swing.JTextField;
22  import javax.swing.SwingConstants;
23  import javax.swing.filechooser.FileNameExtensionFilter;
24  
25  import org.jfree.chart.ChartMouseListener;
26  import org.jfree.chart.ChartPanel;
27  import org.jfree.chart.JFreeChart;
28  import org.jfree.chart.plot.XYPlot;
29  import org.opentrafficsim.animation.graphs.AbstractPlot;
30  import org.opentrafficsim.animation.graphs.JFileChooserWithSettings;
31  import org.opentrafficsim.animation.graphs.PointerHandler;
32  
33  /**
34   * Swing wrapper of all plots. This schedules regular updates, creates menus and deals with listeners. There are a number of
35   * delegate methods for sub classes to implement.
36   * <p>
37   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
38   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
39   * </p>
40   * @author Alexander Verbraeck
41   * @author Peter Knoppers
42   * @author Wouter Schakel
43   */
44  public class SwingPlot extends JFrame
45  {
46      /**  */
47      private static final long serialVersionUID = 20190823L;
48  
49      /** The JFreeChart plot. */
50      @SuppressWarnings("checkstyle:visibilitymodifier")
51      protected final AbstractPlot<?> plot;
52  
53      /** Status label. */
54      private JLabel statusLabel;
55  
56      /** Detach menu item. */
57      private JMenuItem detach;
58  
59      /** Chart panel. */
60      private ChartPanel chartPanel;
61  
62      /**
63       * Construct a new Swing container for an AbstractPlot.
64       * @param plot the plot to embed
65       */
66      public SwingPlot(final AbstractPlot<?> plot)
67      {
68          this.plot = plot;
69          // status label
70          this.statusLabel = new JLabel(" ", SwingConstants.CENTER);
71          add(this.statusLabel, BorderLayout.SOUTH);
72          setChart(plot.getChart());
73      }
74  
75      /**
76       * Add the chart.
77       * @param chart the chart
78       */
79      protected void setChart(final JFreeChart chart)
80      {
81          // this.plot.setChart(chart);
82          // override to gain some control over the auto bounds
83          this.chartPanel = new ChartPanel(chart)
84          {
85              /** Serialization version UID. */
86              private static final long serialVersionUID = 20181006L;
87  
88              @Override
89              public void restoreAutoDomainBounds()
90              {
91                  super.restoreAutoDomainBounds();
92                  if (chart.getPlot() instanceof XYPlot)
93                  {
94                      SwingPlot.this.plot.setAutoBoundDomain(chart.getXYPlot());
95                  }
96              }
97  
98              @Override
99              public void restoreAutoRangeBounds()
100             {
101                 super.restoreAutoRangeBounds();
102                 if (chart.getPlot() instanceof XYPlot)
103                 {
104                     SwingPlot.this.plot.setAutoBoundRange(chart.getXYPlot());
105                 }
106             }
107 
108             /** {@inheritDoc} This implementation adds control over the PNG image size and font size. */
109             @Override
110             public void doSaveAs() throws IOException
111             {
112                 // the code in this method is based on the code in the super implementation
113 
114                 // create setting components
115                 JLabel fontSizeLabel = new JLabel("font size");
116                 JTextField fontSize = new JTextField("32"); // by default, give more space for labels in a png export
117                 fontSize.setToolTipText("Font size of title (other fonts are scaled)");
118                 fontSize.setPreferredSize(new Dimension(40, 20));
119                 JTextField width = new JTextField("960");
120                 width.setToolTipText("Width [pixels]");
121                 width.setPreferredSize(new Dimension(40, 20));
122                 JLabel x = new JLabel("x");
123                 JTextField height = new JTextField("540");
124                 height.setToolTipText("Height [pixels]");
125                 height.setPreferredSize(new Dimension(40, 20));
126 
127                 // create file chooser with these components
128                 JFileChooser fileChooser = new JFileChooserWithSettings(fontSizeLabel, fontSize, width, x, height);
129                 fileChooser.setCurrentDirectory(getDefaultDirectoryForSaveAs());
130                 FileNameExtensionFilter filter =
131                         new FileNameExtensionFilter(localizationResources.getString("PNG_Image_Files"), "png");
132                 fileChooser.addChoosableFileFilter(filter);
133                 fileChooser.setFileFilter(filter);
134 
135                 int option = fileChooser.showSaveDialog(this);
136                 if (option == JFileChooser.APPROVE_OPTION)
137                 {
138                     String filename = fileChooser.getSelectedFile().getPath();
139                     if (isEnforceFileExtensions())
140                     {
141                         if (!filename.endsWith(".png"))
142                         {
143                             filename = filename + ".png";
144                         }
145                     }
146 
147                     // get settings from setting components
148                     double fs; // relative scale
149                     try
150                     {
151                         fs = Double.parseDouble(fontSize.getText());
152                     }
153                     catch (NumberFormatException exception)
154                     {
155                         fs = 16.0;
156                     }
157                     int w;
158                     try
159                     {
160                         w = Integer.parseInt(width.getText());
161                     }
162                     catch (NumberFormatException exception)
163                     {
164                         w = getWidth();
165                     }
166                     int h;
167                     try
168                     {
169                         h = Integer.parseInt(height.getText());
170                     }
171                     catch (NumberFormatException exception)
172                     {
173                         h = getHeight();
174                     }
175                     OutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filename)));
176                     out.write(SwingPlot.this.plot.encodeAsPng(w, h, fs));
177                     out.close();
178                 }
179             }
180         };
181         Optional<ChartMouseListener> chartListener = getChartMouseListener();
182         if (chartListener.isPresent())
183         {
184             this.chartPanel.addChartMouseListener(chartListener.get());
185         }
186 
187         // pointer handler
188         final PointerHandler ph = new PointerHandler()
189         {
190             @Override
191             public void updateHint(final double domainValue, final double rangeValue)
192             {
193                 if (Double.isNaN(domainValue))
194                 {
195                     setStatusLabel(" ");
196                 }
197                 else
198                 {
199                     setStatusLabel(SwingPlot.this.plot.getStatusLabel(domainValue, rangeValue));
200                 }
201             }
202         };
203         this.chartPanel.addMouseMotionListener(ph);
204         this.chartPanel.addMouseListener(ph);
205         add(this.chartPanel, BorderLayout.CENTER);
206         this.chartPanel.setMouseWheelEnabled(true);
207 
208         // pop up
209         JPopupMenu popupMenu = this.chartPanel.getPopupMenu();
210         popupMenu.add(new JPopupMenu.Separator());
211         this.detach = new JMenuItem("Show in detached window");
212         this.detach.addActionListener(new ActionListener()
213         {
214             @Override
215             public void actionPerformed(final ActionEvent e)
216             {
217                 SwingPlot.this.detach.setEnabled(false);
218                 JFrame window = new JFrame(getPlot().getCaption());
219                 window.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
220                 window.add(SwingPlot.this.chartPanel, BorderLayout.CENTER);
221                 window.add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
222                 window.addWindowListener(new WindowAdapter()
223                 {
224                     @Override
225                     public void windowClosing(final WindowEvent e)
226                     {
227                         add(SwingPlot.this.chartPanel, BorderLayout.CENTER);
228                         add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
229                         SwingPlot.this.detach.setEnabled(true);
230                         SwingPlot.this.getContentPane().validate();
231                         SwingPlot.this.getContentPane().repaint();
232                     }
233                 });
234                 window.pack();
235                 window.setVisible(true);
236                 SwingPlot.this.getContentPane().repaint();
237             }
238         });
239         popupMenu.add(this.detach);
240         addPopUpMenuItems(popupMenu);
241     }
242 
243     /**
244      * Manually set status label from sub class. Will be overwritten by a moving mouse pointer over the axes.
245      * @param label label to set
246      */
247     protected void setStatusLabel(final String label)
248     {
249         if (this.statusLabel != null)
250         {
251             this.statusLabel.setText(label);
252         }
253     }
254 
255     /**
256      * Overridable method to add pop up items.
257      * @param popupMenu pop up menu
258      */
259     protected void addPopUpMenuItems(final JPopupMenu popupMenu)
260     {
261         //
262     }
263 
264     /**
265      * Overridable; may return a chart listener for additional functions.
266      * @return ChartMouseListener, empty by default
267      */
268     protected Optional<ChartMouseListener> getChartMouseListener()
269     {
270         return Optional.empty();
271     }
272 
273     /**
274      * Retrieve the plot.
275      * @return the plot
276      */
277     public AbstractPlot<?> getPlot()
278     {
279         return this.plot;
280     }
281 
282     /**
283      * Returns the chart panel.
284      * @return chart panel.
285      */
286     public ChartPanel getChartPanel()
287     {
288         return this.chartPanel;
289     }
290 
291 }