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.draw.graphs.AbstractPlot;
30  import org.opentrafficsim.draw.graphs.JFileChooserWithSettings;
31  import org.opentrafficsim.draw.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-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
41   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
42   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
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              /** */
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             @SuppressWarnings("synthetic-access")
215             @Override
216             public void actionPerformed(final ActionEvent e)
217             {
218                 SwingPlot.this.detach.setEnabled(false);
219                 JFrame window = new JFrame(getPlot().getCaption());
220                 window.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
221                 window.add(SwingPlot.this.chartPanel, BorderLayout.CENTER);
222                 window.add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
223                 window.addWindowListener(new WindowAdapter()
224                 {
225                     @Override
226                     public void windowClosing(@SuppressWarnings("hiding") final WindowEvent e)
227                     {
228                         add(SwingPlot.this.chartPanel, BorderLayout.CENTER);
229                         add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
230                         SwingPlot.this.detach.setEnabled(true);
231                         SwingPlot.this.getContentPane().validate();
232                         SwingPlot.this.getContentPane().repaint();
233                     }
234                 });
235                 window.pack();
236                 window.setVisible(true);
237                 SwingPlot.this.getContentPane().repaint();
238             }
239         });
240         popupMenu.add(this.detach);
241         addPopUpMenuItems(popupMenu);
242     }
243 
244     /**
245      * Manually set status label from sub class. Will be overwritten by a moving mouse pointer over the axes.
246      * @param label label to set
247      */
248     protected final void setStatusLabel(final String label)
249     {
250         if (this.statusLabel != null)
251         {
252             this.statusLabel.setText(label);
253         }
254     }
255 
256     /**
257      * Overridable method to add pop up items.
258      * @param popupMenu pop up menu
259      */
260     protected void addPopUpMenuItems(final JPopupMenu popupMenu)
261     {
262         //
263     }
264 
265     /**
266      * Overridable; may return a chart listener for additional functions.
267      * @return ChartMouseListener, empty by default
268      */
269     protected Optional<ChartMouseListener> getChartMouseListener()
270     {
271         return Optional.empty();
272     }
273 
274     /**
275      * Retrieve the plot.
276      * @return the plot
277      */
278     public AbstractPlot getPlot()
279     {
280         return this.plot;
281     }
282 
283     /**
284      * Returns the chart panel.
285      * @return chart panel.
286      */
287     public ChartPanel getChartPanel()
288     {
289         return this.chartPanel;
290     }
291 
292 }