package org.opentrafficsim.swing.graphs;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.XYPlot;
import org.opentrafficsim.draw.graphs.AbstractPlot;
import org.opentrafficsim.draw.graphs.JFileChooserWithSettings;
import org.opentrafficsim.draw.graphs.PointerHandler;
/**
* Swing wrapper of all plots. This schedules regular updates, creates menus and deals with listeners. There are a number of
* delegate methods for sub classes to implement.
*
* Copyright (c) 2013-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
public class SwingPlot extends JFrame
{
/** */
private static final long serialVersionUID = 20190823L;
/** The JFreeChart plot. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected final AbstractPlot plot;
/** Status label. */
private JLabel statusLabel;
/** Detach menu item. */
private JMenuItem detach;
/**
* Construct a new Swing container for an AbstractPlot.
* @param plot AbstractPlot; the plot to embed
*/
public SwingPlot(final AbstractPlot plot)
{
this.plot = plot;
// status label
this.statusLabel = new JLabel(" ", SwingConstants.CENTER);
add(this.statusLabel, BorderLayout.SOUTH);
setChart(plot.getChart());
}
/**
* Add the chart.
* @param chart JFreeChart; the chart
*/
protected void setChart(final JFreeChart chart)
{
// this.plot.setChart(chart);
// override to gain some control over the auto bounds
ChartPanel chartPanel = new ChartPanel(chart)
{
/** */
private static final long serialVersionUID = 20181006L;
/** {@inheritDoc} */
@Override
public void restoreAutoDomainBounds()
{
super.restoreAutoDomainBounds();
if (chart.getPlot() instanceof XYPlot)
{
SwingPlot.this.plot.setAutoBoundDomain(chart.getXYPlot());
}
}
/** {@inheritDoc} */
@Override
public void restoreAutoRangeBounds()
{
super.restoreAutoRangeBounds();
if (chart.getPlot() instanceof XYPlot)
{
SwingPlot.this.plot.setAutoBoundRange(chart.getXYPlot());
}
}
/** {@inheritDoc} This implementation adds control over the PNG image size and font size. */
@Override
public void doSaveAs() throws IOException
{
// the code in this method is based on the code in the super implementation
// create setting components
JLabel fontSizeLabel = new JLabel("font size");
JTextField fontSize = new JTextField("32"); // by default, give more space for labels in a png export
fontSize.setToolTipText("Font size of title (other fonts are scaled)");
fontSize.setPreferredSize(new Dimension(40, 20));
JTextField width = new JTextField("960");
width.setToolTipText("Width [pixels]");
width.setPreferredSize(new Dimension(40, 20));
JLabel x = new JLabel("x");
JTextField height = new JTextField("540");
height.setToolTipText("Height [pixels]");
height.setPreferredSize(new Dimension(40, 20));
// create file chooser with these components
JFileChooser fileChooser = new JFileChooserWithSettings(fontSizeLabel, fontSize, width, x, height);
fileChooser.setCurrentDirectory(getDefaultDirectoryForSaveAs());
FileNameExtensionFilter filter =
new FileNameExtensionFilter(localizationResources.getString("PNG_Image_Files"), "png");
fileChooser.addChoosableFileFilter(filter);
fileChooser.setFileFilter(filter);
int option = fileChooser.showSaveDialog(this);
if (option == JFileChooser.APPROVE_OPTION)
{
String filename = fileChooser.getSelectedFile().getPath();
if (isEnforceFileExtensions())
{
if (!filename.endsWith(".png"))
{
filename = filename + ".png";
}
}
// get settings from setting components
double fs; // relative scale
try
{
fs = Double.parseDouble(fontSize.getText());
}
catch (NumberFormatException exception)
{
fs = 16.0;
}
int w;
try
{
w = Integer.parseInt(width.getText());
}
catch (NumberFormatException exception)
{
w = getWidth();
}
int h;
try
{
h = Integer.parseInt(height.getText());
}
catch (NumberFormatException exception)
{
h = getHeight();
}
OutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filename)));
out.write(SwingPlot.this.plot.encodeAsPng(w, h, fs));
out.close();
}
}
};
ChartMouseListener chartListener = getChartMouseListener();
if (chartListener != null)
{
chartPanel.addChartMouseListener(chartListener);
}
// pointer handler
final PointerHandler ph = new PointerHandler()
{
/** {@inheritDoc} */
@Override
public void updateHint(final double domainValue, final double rangeValue)
{
if (Double.isNaN(domainValue))
{
setStatusLabel(" ");
}
else
{
setStatusLabel(SwingPlot.this.plot.getStatusLabel(domainValue, rangeValue));
}
}
};
chartPanel.addMouseMotionListener(ph);
chartPanel.addMouseListener(ph);
add(chartPanel, BorderLayout.CENTER);
chartPanel.setMouseWheelEnabled(true);
// pop up
JPopupMenu popupMenu = chartPanel.getPopupMenu();
popupMenu.add(new JPopupMenu.Separator());
this.detach = new JMenuItem("Show in detached window");
this.detach.addActionListener(new ActionListener()
{
@SuppressWarnings("synthetic-access")
@Override
public void actionPerformed(final ActionEvent e)
{
SwingPlot.this.detach.setEnabled(false);
JFrame window = new JFrame(getPlot().getCaption());
window.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
window.add(chartPanel, BorderLayout.CENTER);
window.add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
window.addWindowListener(new WindowAdapter()
{
/** {@inheritDoc} */
@Override
public void windowClosing(@SuppressWarnings("hiding") final WindowEvent e)
{
add(chartPanel, BorderLayout.CENTER);
add(SwingPlot.this.statusLabel, BorderLayout.SOUTH);
SwingPlot.this.detach.setEnabled(true);
SwingPlot.this.getContentPane().validate();
SwingPlot.this.getContentPane().repaint();
}
});
window.pack();
window.setVisible(true);
SwingPlot.this.getContentPane().repaint();
}
});
popupMenu.add(this.detach);
addPopUpMenuItems(popupMenu);
}
/**
* Manually set status label from sub class. Will be overwritten by a moving mouse pointer over the axes.
* @param label String; label to set
*/
protected final void setStatusLabel(final String label)
{
if (this.statusLabel != null)
{
this.statusLabel.setText(label);
}
}
/**
* Overridable method to add pop up items.
* @param popupMenu JPopupMenu; pop up menu
*/
protected void addPopUpMenuItems(final JPopupMenu popupMenu)
{
//
}
/**
* Overridable; may return a chart listener for additional functions.
* @return ChartMouseListener, {@code null} by default
*/
protected ChartMouseListener getChartMouseListener()
{
return null;
}
/**
* Retrieve the plot.
* @return AbstractPlot; the plot
*/
public AbstractPlot getPlot()
{
return this.plot;
}
}