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://tudelft.nl/staff/p.knoppers-1">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      /**
59       * Construct a new Swing container for an AbstractPlot.
60       * @param plot AbstractPlot; the plot to embed
61       */
62      public SwingPlot(final AbstractPlot plot)
63      {
64          this.plot = plot;
65          // status label
66          this.statusLabel = new JLabel(" ", SwingConstants.CENTER);
67          add(this.statusLabel, BorderLayout.SOUTH);
68          setChart(plot.getChart());
69      }
70  
71      /**
72       * Add the chart.
73       * @param chart JFreeChart; the chart
74       */
75      protected void setChart(final JFreeChart chart)
76      {
77          // this.plot.setChart(chart);
78          // override to gain some control over the auto bounds
79          ChartPanel chartPanel = new ChartPanel(chart)
80          {
81              /** */
82              private static final long serialVersionUID = 20181006L;
83  
84              /** {@inheritDoc} */
85              @Override
86              public void restoreAutoDomainBounds()
87              {
88                  super.restoreAutoDomainBounds();
89                  if (chart.getPlot() instanceof XYPlot)
90                  {
91                      SwingPlot.this.plot.setAutoBoundDomain(chart.getXYPlot());
92                  }
93              }
94  
95              /** {@inheritDoc} */
96              @Override
97              public void restoreAutoRangeBounds()
98              {
99                  super.restoreAutoRangeBounds();
100                 if (chart.getPlot() instanceof XYPlot)
101                 {
102                     SwingPlot.this.plot.setAutoBoundRange(chart.getXYPlot());
103                 }
104             }
105 
106             /** {@inheritDoc} This implementation adds control over the PNG image size and font size. */
107             @Override
108             public void doSaveAs() throws IOException
109             {
110                 // the code in this method is based on the code in the super implementation
111 
112                 // create setting components
113                 JLabel fontSizeLabel = new JLabel("font size");
114                 JTextField fontSize = new JTextField("32"); // by default, give more space for labels in a png export
115                 fontSize.setToolTipText("Font size of title (other fonts are scaled)");
116                 fontSize.setPreferredSize(new Dimension(40, 20));
117                 JTextField width = new JTextField("960");
118                 width.setToolTipText("Width [pixels]");
119                 width.setPreferredSize(new Dimension(40, 20));
120                 JLabel x = new JLabel("x");
121                 JTextField height = new JTextField("540");
122                 height.setToolTipText("Height [pixels]");
123                 height.setPreferredSize(new Dimension(40, 20));
124 
125                 // create file chooser with these components
126                 JFileChooser fileChooser = new JFileChooserWithSettings(fontSizeLabel, fontSize, width, x, height);
127                 fileChooser.setCurrentDirectory(getDefaultDirectoryForSaveAs());
128                 FileNameExtensionFilter filter =
129                         new FileNameExtensionFilter(localizationResources.getString("PNG_Image_Files"), "png");
130                 fileChooser.addChoosableFileFilter(filter);
131                 fileChooser.setFileFilter(filter);
132 
133                 int option = fileChooser.showSaveDialog(this);
134                 if (option == JFileChooser.APPROVE_OPTION)
135                 {
136                     String filename = fileChooser.getSelectedFile().getPath();
137                     if (isEnforceFileExtensions())
138                     {
139                         if (!filename.endsWith(".png"))
140                         {
141                             filename = filename + ".png";
142                         }
143                     }
144 
145                     // get settings from setting components
146                     double fs; // relative scale
147                     try
148                     {
149                         fs = Double.parseDouble(fontSize.getText());
150                     }
151                     catch (NumberFormatException exception)
152                     {
153                         fs = 16.0;
154                     }
155                     int w;
156                     try
157                     {
158                         w = Integer.parseInt(width.getText());
159                     }
160                     catch (NumberFormatException exception)
161                     {
162                         w = getWidth();
163                     }
164                     int h;
165                     try
166                     {
167                         h = Integer.parseInt(height.getText());
168                     }
169                     catch (NumberFormatException exception)
170                     {
171                         h = getHeight();
172                     }
173                     OutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filename)));
174                     out.write(SwingPlot.this.plot.encodeAsPng(w, h, fs));
175                     out.close();
176                 }
177             }
178         };
179         ChartMouseListener chartListener = getChartMouseListener();
180         if (chartListener != null)
181         {
182             chartPanel.addChartMouseListener(chartListener);
183         }
184 
185         // pointer handler
186         final PointerHandler ph = new PointerHandler()
187         {
188             /** {@inheritDoc} */
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         chartPanel.addMouseMotionListener(ph);
203         chartPanel.addMouseListener(ph);
204         add(chartPanel, BorderLayout.CENTER);
205         chartPanel.setMouseWheelEnabled(true);
206 
207         // pop up
208         JPopupMenu popupMenu = 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(chartPanel, BorderLayout.CENTER);
221                 window.add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
222                 window.addWindowListener(new WindowAdapter()
223                 {
224                     /** {@inheritDoc} */
225                     @Override
226                     public void windowClosing(@SuppressWarnings("hiding") final WindowEvent e)
227                     {
228                         add(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 String; 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 JPopupMenu; 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, {@code null} by default
268      */
269     protected ChartMouseListener getChartMouseListener()
270     {
271         return null;
272     }
273 
274     /**
275      * Retrieve the plot.
276      * @return AbstractPlot; the plot
277      */
278     public AbstractPlot getPlot()
279     {
280         return this.plot;
281     }
282 
283 }