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  
15  import javax.swing.JFileChooser;
16  import javax.swing.JFrame;
17  import javax.swing.JLabel;
18  import javax.swing.JMenuItem;
19  import javax.swing.JPopupMenu;
20  import javax.swing.JTextField;
21  import javax.swing.SwingConstants;
22  import javax.swing.filechooser.FileNameExtensionFilter;
23  
24  import org.jfree.chart.ChartMouseListener;
25  import org.jfree.chart.ChartPanel;
26  import org.jfree.chart.JFreeChart;
27  import org.jfree.chart.plot.XYPlot;
28  import org.opentrafficsim.draw.graphs.AbstractPlot;
29  import org.opentrafficsim.draw.graphs.JFileChooserWithSettings;
30  import org.opentrafficsim.draw.graphs.PointerHandler;
31  
32  /**
33   * Swing wrapper of all plots. This schedules regular updates, creates menus and deals with listeners. There are a number of
34   * delegate methods for sub classes to implement.
35   * <p>
36   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
37   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
38   * </p>
39   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
40   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
41   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
42   */
43  public class SwingPlot extends JFrame
44  {
45      /**  */
46      private static final long serialVersionUID = 20190823L;
47  
48      /** The JFreeChart plot. */
49      @SuppressWarnings("checkstyle:visibilitymodifier")
50      protected final AbstractPlot plot;
51  
52      /** Status label. */
53      private JLabel statusLabel;
54  
55      /** Detach menu item. */
56      private JMenuItem detach;
57  
58      /** Chart panel. */
59      private ChartPanel chartPanel;
60  
61      /**
62       * Construct a new Swing container for an AbstractPlot.
63       * @param plot the plot to embed
64       */
65      public SwingPlot(final AbstractPlot plot)
66      {
67          this.plot = plot;
68          // status label
69          this.statusLabel = new JLabel(" ", SwingConstants.CENTER);
70          add(this.statusLabel, BorderLayout.SOUTH);
71          setChart(plot.getChart());
72      }
73  
74      /**
75       * Add the chart.
76       * @param chart the chart
77       */
78      protected void setChart(final JFreeChart chart)
79      {
80          // this.plot.setChart(chart);
81          // override to gain some control over the auto bounds
82          this.chartPanel = new ChartPanel(chart)
83          {
84              /** */
85              private static final long serialVersionUID = 20181006L;
86  
87              @Override
88              public void restoreAutoDomainBounds()
89              {
90                  super.restoreAutoDomainBounds();
91                  if (chart.getPlot() instanceof XYPlot)
92                  {
93                      SwingPlot.this.plot.setAutoBoundDomain(chart.getXYPlot());
94                  }
95              }
96  
97              @Override
98              public void restoreAutoRangeBounds()
99              {
100                 super.restoreAutoRangeBounds();
101                 if (chart.getPlot() instanceof XYPlot)
102                 {
103                     SwingPlot.this.plot.setAutoBoundRange(chart.getXYPlot());
104                 }
105             }
106 
107             /** {@inheritDoc} This implementation adds control over the PNG image size and font size. */
108             @Override
109             public void doSaveAs() throws IOException
110             {
111                 // the code in this method is based on the code in the super implementation
112 
113                 // create setting components
114                 JLabel fontSizeLabel = new JLabel("font size");
115                 JTextField fontSize = new JTextField("32"); // by default, give more space for labels in a png export
116                 fontSize.setToolTipText("Font size of title (other fonts are scaled)");
117                 fontSize.setPreferredSize(new Dimension(40, 20));
118                 JTextField width = new JTextField("960");
119                 width.setToolTipText("Width [pixels]");
120                 width.setPreferredSize(new Dimension(40, 20));
121                 JLabel x = new JLabel("x");
122                 JTextField height = new JTextField("540");
123                 height.setToolTipText("Height [pixels]");
124                 height.setPreferredSize(new Dimension(40, 20));
125 
126                 // create file chooser with these components
127                 JFileChooser fileChooser = new JFileChooserWithSettings(fontSizeLabel, fontSize, width, x, height);
128                 fileChooser.setCurrentDirectory(getDefaultDirectoryForSaveAs());
129                 FileNameExtensionFilter filter =
130                         new FileNameExtensionFilter(localizationResources.getString("PNG_Image_Files"), "png");
131                 fileChooser.addChoosableFileFilter(filter);
132                 fileChooser.setFileFilter(filter);
133 
134                 int option = fileChooser.showSaveDialog(this);
135                 if (option == JFileChooser.APPROVE_OPTION)
136                 {
137                     String filename = fileChooser.getSelectedFile().getPath();
138                     if (isEnforceFileExtensions())
139                     {
140                         if (!filename.endsWith(".png"))
141                         {
142                             filename = filename + ".png";
143                         }
144                     }
145 
146                     // get settings from setting components
147                     double fs; // relative scale
148                     try
149                     {
150                         fs = Double.parseDouble(fontSize.getText());
151                     }
152                     catch (NumberFormatException exception)
153                     {
154                         fs = 16.0;
155                     }
156                     int w;
157                     try
158                     {
159                         w = Integer.parseInt(width.getText());
160                     }
161                     catch (NumberFormatException exception)
162                     {
163                         w = getWidth();
164                     }
165                     int h;
166                     try
167                     {
168                         h = Integer.parseInt(height.getText());
169                     }
170                     catch (NumberFormatException exception)
171                     {
172                         h = getHeight();
173                     }
174                     OutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filename)));
175                     out.write(SwingPlot.this.plot.encodeAsPng(w, h, fs));
176                     out.close();
177                 }
178             }
179         };
180         ChartMouseListener chartListener = getChartMouseListener();
181         if (chartListener != null)
182         {
183             this.chartPanel.addChartMouseListener(chartListener);
184         }
185 
186         // pointer handler
187         final PointerHandler ph = new PointerHandler()
188         {
189             @Override
190             public void updateHint(final double domainValue, final double rangeValue)
191             {
192                 if (Double.isNaN(domainValue))
193                 {
194                     setStatusLabel(" ");
195                 }
196                 else
197                 {
198                     setStatusLabel(SwingPlot.this.plot.getStatusLabel(domainValue, rangeValue));
199                 }
200             }
201         };
202         this.chartPanel.addMouseMotionListener(ph);
203         this.chartPanel.addMouseListener(ph);
204         add(this.chartPanel, BorderLayout.CENTER);
205         this.chartPanel.setMouseWheelEnabled(true);
206 
207         // pop up
208         JPopupMenu popupMenu = this.chartPanel.getPopupMenu();
209         popupMenu.add(new JPopupMenu.Separator());
210         this.detach = new JMenuItem("Show in detached window");
211         this.detach.addActionListener(new ActionListener()
212         {
213             @SuppressWarnings("synthetic-access")
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(@SuppressWarnings("hiding") 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 final 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, {@code null} by default
267      */
268     protected ChartMouseListener getChartMouseListener()
269     {
270         return null;
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 }