/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.plotter.charts;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.ToolTipManager;
import javax.swing.event.EventListenerList;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.ChartTransferable;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.editor.ChartEditor;
import org.jfree.chart.editor.ChartEditorManager;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartProgressEvent;
import org.jfree.chart.event.OverlayChangeEvent;
import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.Pannable;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.plot.Zoomable;
import org.jfree.data.Range;
import org.jfree.ui.ExtensionFileFilter;
import org.jfree.util.ResourceBundleWrapper;
import com.rapidminer.gui.plotter.AxisNameResolver;
import com.rapidminer.gui.plotter.CoordinateTransformation;
import com.rapidminer.gui.plotter.NullCoordinateTransformation;
import com.rapidminer.tools.container.Pair;
/**
* A Swing GUI component for displaying a {@link JFreeChart} object.
* <P>
* The panel registers with the chart to receive notification of changes to any component of the chart. The chart is
* redrawn automatically whenever this notification is received.
*
* This version of the ChartPanel provides the possibility to register a JXLayer, which is then asked to resolve
* coordinates for every direct on screen painting.
*
* @author Sebastian Land
*/
public class AbstractChartPanel extends ChartPanel {
public static class Selection {
private Collection<Pair<String, Range>> selectedRegion;
public Selection() {
this.selectedRegion = new LinkedList<Pair<String, Range>>();
}
public void addDelimiter(String dimensionName, Range range) {
selectedRegion.add(new Pair<String, Range>(dimensionName, range));
}
public Collection<Pair<String, Range>> getDelimiters() {
return selectedRegion;
}
}
public static interface SelectionListener {
/**
* This method is invoked on all listeners as a selection is made. If the selection was made by a mouse
* interaction like drawing a selection rectangle, the selectionEvent will be the causing mouse event. Otherwise
* it is null for example if the selection is performed by gui actions.
*/
public void selected(Selection selection, MouseEvent selectionEvent);
}
/** For serialization. */
private static final long serialVersionUID = 6046366297214274674L;
/** The chart that is displayed in the panel. */
private JFreeChart chart;
/**
* This is a transformation which transforms the components coordinates to screen coordinates. If is null, no
* transformation is needed.
*/
private transient CoordinateTransformation coordinateTransformation = new NullCoordinateTransformation();
/** Storage for registered (chart) mouse listeners. */
private transient EventListenerList chartMouseListeners;
/**
* The minimum width for drawing a chart (uses scaling for smaller widths).
*/
private int minimumDrawWidth;
/**
* The minimum height for drawing a chart (uses scaling for smaller heights).
*/
private int minimumDrawHeight;
/**
* The maximum width for drawing a chart (uses scaling for bigger widths).
*/
private int maximumDrawWidth;
/**
* The maximum height for drawing a chart (uses scaling for bigger heights).
*/
private int maximumDrawHeight;
/** The popup menu for the frame. */
private JPopupMenu popup;
/** The drawing info collected the last time the chart was drawn. */
private ChartRenderingInfo info;
/** The chart anchor point. */
private Point2D anchor;
/** The scale factor used to draw the chart. */
private double scaleX;
/** The scale factor used to draw the chart. */
private double scaleY;
/** The plot orientation. */
private PlotOrientation orientation = PlotOrientation.VERTICAL;
/** A flag that controls whether or not domain zooming is enabled. */
private boolean domainZoomable = false;
/** A flag that controls whether or not range zooming is enabled. */
private boolean rangeZoomable = false;
/** This resolves the names of the axes plotted. The default asks this chart for the axis name**/
private transient AxisNameResolver axisNameResolver = new AxisNameResolver() {
@Override
public Collection<String> resolveYAxis(int axisIndex) {
Plot p = chart.getPlot();
Collection<String> names = new LinkedList<String>();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis domain = plot.getRangeAxis(i);
names.add(domain.getLabel());
}
}
return names;
}
@Override
public Collection<String> resolveXAxis(int axisIndex) {
Plot p = chart.getPlot();
Collection<String> names = new LinkedList<String>();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
for (int i = 0; i < plot.getDomainAxisCount(); i++) {
ValueAxis domain = plot.getDomainAxis(i);
names.add(domain.getLabel());
}
}
return names;
}
};
/**
* The zoom rectangle starting point (selected by the user with a mouse click). This is a point on the screen, not
* the chart (which may have been scaled up or down to fit the panel).
*/
private Point2D zoomPoint = null;
/** The zoom rectangle (selected by the user with the mouse). */
private transient Rectangle2D selectionRectangle = null;
/** Controls if the zoom rectangle is drawn as an outline or filled. */
private boolean fillSelectionRectangle = true;
/** The minimum distance required to drag the mouse to trigger a zoom. */
private int zoomTriggerDistance;
/** A flag that controls whether or not horizontal tracing is enabled. */
private boolean horizontalAxisTrace = false;
/** A flag that controls whether or not vertical tracing is enabled. */
private boolean verticalAxisTrace = false;
/** A vertical trace line. */
private transient Line2D verticalTraceLine;
/** A horizontal trace line. */
private transient Line2D horizontalTraceLine;
/** Menu item for zooming in on a chart (both axes). */
private JMenuItem zoomInBothMenuItem;
/** Menu item for zooming in on a chart (domain axis). */
private JMenuItem zoomInDomainMenuItem;
/** Menu item for zooming in on a chart (range axis). */
private JMenuItem zoomInRangeMenuItem;
/** Menu item for zooming out on a chart. */
private JMenuItem zoomOutBothMenuItem;
/** Menu item for zooming out on a chart (domain axis). */
private JMenuItem zoomOutDomainMenuItem;
/** Menu item for zooming out on a chart (range axis). */
private JMenuItem zoomOutRangeMenuItem;
/** Menu item for resetting the zoom (both axes). */
private JMenuItem zoomResetBothMenuItem;
/** Menu item for resetting the zoom (domain axis only). */
private JMenuItem zoomResetDomainMenuItem;
/** Menu item for resetting the zoom (range axis only). */
private JMenuItem zoomResetRangeMenuItem;
/**
* The default directory for saving charts to file.
*
* @since 1.0.7
*/
private File defaultDirectoryForSaveAs;
/** A flag that controls whether or not file extensions are enforced. */
private boolean enforceFileExtensions;
/** A flag that indicates if original tooltip delays are changed. */
private boolean ownToolTipDelaysActive;
/** Original initial tooltip delay of ToolTipManager.sharedInstance(). */
private int originalToolTipInitialDelay;
/** Original reshow tooltip delay of ToolTipManager.sharedInstance(). */
private int originalToolTipReshowDelay;
/** Original dismiss tooltip delay of ToolTipManager.sharedInstance(). */
private int originalToolTipDismissDelay;
/** Own initial tooltip delay to be used in this chart panel. */
private int ownToolTipInitialDelay;
/** Own reshow tooltip delay to be used in this chart panel. */
private int ownToolTipReshowDelay;
/** Own dismiss tooltip delay to be used in this chart panel. */
private int ownToolTipDismissDelay;
/** The factor used to zoom in on an axis range. */
private double zoomInFactor = 0.8;
/** The factor used to zoom out on an axis range. */
private double zoomOutFactor = 1.25;
/**
* A flag that controls whether zoom operations are centred on the current anchor point, or the centre point of the
* relevant axis.
*
* @since 1.0.7
*/
private boolean zoomAroundAnchor;
/**
* The paint used to draw the zoom rectangle outline.
*
* @since 1.0.13
*/
private transient Paint selectionOutlinePaint;
/**
* The zoom fill paint (should use transparency).
*
* @since 1.0.13
*/
private transient Paint selectionFillPaint;
/** The resourceBundle for the localization. */
protected static ResourceBundle localizationResources = ResourceBundleWrapper.getBundle("org.jfree.chart.LocalizationBundle");
/**
* Temporary storage for the width and height of the chart drawing area during panning.
*/
private double panW, panH;
/** The last mouse position during panning. */
private Point panLast;
/**
* The mask for mouse events to trigger panning.
*
* @since 1.0.13
*/
private int panMask = InputEvent.CTRL_MASK;
/**
* A list of overlays for the panel.
*
* @since 1.0.13
*/
private List<Overlay> overlays;
/**
* The list of all selection listeners
*/
private Collection<SelectionListener> selectionListeners = new LinkedList<SelectionListener>();
/**
* Constructs a panel that displays the specified chart.
*
* @param chart
* the chart.
*/
public AbstractChartPanel(JFreeChart chart) {
this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED, true, // properties
true, // save
true, // print
true, // zoom
true // tooltips
);
}
/**
* Constructs a panel containing a chart. The <code>useBuffer</code> flag controls whether or not an offscreen
* <code>BufferedImage</code> is maintained for the chart. If the buffer is used, more memory is consumed, but panel
* repaints will be a lot quicker in cases where the chart itself hasn't changed (for example, when another frame is
* moved to reveal the panel). WARNING: If you set the <code>useBuffer</code> flag to false, note that the mouse
* zooming rectangle will (in that case) be drawn using XOR, and there is a SEVERE performance problem with that on
* JRE6 on Windows.
*
* @param chart
* the chart.
* @param useBuffer
* a flag controlling whether or not an off-screen buffer is used (read the warning above before setting
* this to <code>false</code>).
*/
public AbstractChartPanel(JFreeChart chart, boolean useBuffer) {
this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, useBuffer, true, // properties
true, // save
true, // print
true, // zoom
true // tooltips
);
}
public AbstractChartPanel(JFreeChart chart, int width, int height) {
this(chart, width, height, 100, 100, Integer.MAX_VALUE, Integer.MAX_VALUE, false, false, false, false, true, true);
}
/**
* Constructs a JFreeChart panel.
*
* @param chart
* the chart.
* @param properties
* a flag indicating whether or not the chart property editor should be available via the popup menu.
* @param save
* a flag indicating whether or not save options should be available via the popup menu.
* @param print
* a flag indicating whether or not the print option should be available via the popup menu.
* @param zoom
* a flag indicating whether or not zoom options should be added to the popup menu.
* @param tooltips
* a flag indicating whether or not tooltips should be enabled for the chart.
*/
public AbstractChartPanel(JFreeChart chart, boolean properties, boolean save, boolean print, boolean zoom, boolean tooltips) {
this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED, properties, save, print, zoom, tooltips);
}
/**
* Constructs a JFreeChart panel.
*
* @param chart
* the chart.
* @param width
* the preferred width of the panel.
* @param height
* the preferred height of the panel.
* @param minimumDrawWidth
* the minimum drawing width.
* @param minimumDrawHeight
* the minimum drawing height.
* @param maximumDrawWidth
* the maximum drawing width.
* @param maximumDrawHeight
* the maximum drawing height.
* @param useBuffer
* a flag that indicates whether to use the off-screen buffer to improve performance (at the expense of
* memory).
* @param properties
* a flag indicating whether or not the chart property editor should be available via the popup menu.
* @param save
* a flag indicating whether or not save options should be available via the popup menu.
* @param print
* a flag indicating whether or not the print option should be available via the popup menu.
* @param zoom
* a flag indicating whether or not zoom options should be added to the popup menu.
* @param tooltips
* a flag indicating whether or not tooltips should be enabled for the chart.
*/
public AbstractChartPanel(JFreeChart chart, int width, int height, int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, boolean properties, boolean save, boolean print, boolean zoom, boolean tooltips) {
this(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, maximumDrawHeight, useBuffer, properties, true, save, print, zoom, tooltips);
}
/**
* Constructs a JFreeChart panel.
*
* @param chart
* the chart.
* @param width
* the preferred width of the panel.
* @param height
* the preferred height of the panel.
* @param minimumDrawWidth
* the minimum drawing width.
* @param minimumDrawHeight
* the minimum drawing height.
* @param maximumDrawWidth
* the maximum drawing width.
* @param maximumDrawHeight
* the maximum drawing height.
* @param useBuffer
* a flag that indicates whether to use the off-screen buffer to improve performance (at the expense of
* memory).
* @param properties
* a flag indicating whether or not the chart property editor should be available via the popup menu.
* @param copy
* a flag indicating whether or not a copy option should be available via the popup menu.
* @param save
* a flag indicating whether or not save options should be available via the popup menu.
* @param print
* a flag indicating whether or not the print option should be available via the popup menu.
* @param zoom
* a flag indicating whether or not zoom options should be added to the popup menu.
* @param tooltips
* a flag indicating whether or not tooltips should be enabled for the chart.
*
* @since 1.0.13
*/
public AbstractChartPanel(JFreeChart chart, int width, int height, int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, boolean properties, boolean copy, boolean save, boolean print, boolean zoom, boolean tooltips) {
super(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, maximumDrawHeight, false, properties, copy, save, print, zoom, tooltips);
setChart(chart);
this.chartMouseListeners = new EventListenerList();
this.info = new ChartRenderingInfo();
setPreferredSize(new Dimension(width, height));
this.minimumDrawWidth = minimumDrawWidth;
this.minimumDrawHeight = minimumDrawHeight;
this.maximumDrawWidth = maximumDrawWidth;
this.maximumDrawHeight = maximumDrawHeight;
this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
// set up popup menu...
this.popup = null;
if (properties || copy || save || print || zoom) {
this.popup = createPopupMenu(properties, copy, save, print, zoom);
}
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
setDisplayToolTips(tooltips);
// mouse listener registered in super class
// addMouseListener(this);
// addMouseMotionListener(this);
this.defaultDirectoryForSaveAs = null;
this.enforceFileExtensions = true;
// initialize ChartPanel-specific tool tip delays with
// values the from ToolTipManager.sharedInstance()
ToolTipManager ttm = ToolTipManager.sharedInstance();
this.ownToolTipInitialDelay = ttm.getInitialDelay();
this.ownToolTipDismissDelay = ttm.getDismissDelay();
this.ownToolTipReshowDelay = ttm.getReshowDelay();
this.zoomAroundAnchor = false;
this.selectionOutlinePaint = Color.blue;
this.selectionFillPaint = new Color(0, 0, 255, 63);
this.panMask = InputEvent.CTRL_MASK;
// for MacOSX we can't use the CTRL key for mouse drags, see:
// http://developer.apple.com/qa/qa2004/qa1362.html
String osName = System.getProperty("os.name").toLowerCase();
if (osName.startsWith("mac os x")) {
this.panMask = InputEvent.ALT_MASK;
}
this.overlays = new java.util.ArrayList<Overlay>();
// adding wheel listener
addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getScrollType() != MouseWheelEvent.WHEEL_UNIT_SCROLL)
return;
if (e.getWheelRotation() < 0)
shrinkSelectionOnCenter(e.getX(), e.getY(), e);
else
enlargeSelectionOnCenter(e.getX(), e.getY(), e);
}
});
}
/**
* Returns the chart contained in the panel.
*
* @return The chart (possibly <code>null</code>).
*/
@Override
public JFreeChart getChart() {
return this.chart;
}
/**
* Sets the chart that is displayed in the panel.
*
* @param chart
* the chart (<code>null</code> permitted).
*/
@Override
public void setChart(JFreeChart chart) {
// stop listening for changes to the existing chart
if (this.chart != null) {
this.chart.removeChangeListener(this);
this.chart.removeProgressListener(this);
}
// add the new chart
this.chart = chart;
if (chart != null) {
this.chart.addChangeListener(this);
this.chart.addProgressListener(this);
Plot plot = chart.getPlot();
this.domainZoomable = false;
this.rangeZoomable = false;
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
this.domainZoomable = z.isDomainZoomable();
this.rangeZoomable = z.isRangeZoomable();
this.orientation = z.getOrientation();
}
} else {
this.domainZoomable = false;
this.rangeZoomable = false;
}
repaint();
}
/**
* Returns the minimum drawing width for charts.
* <P>
* If the width available on the panel is less than this, then the chart is drawn at the minimum width then scaled
* down to fit.
*
* @return The minimum drawing width.
*/
@Override
public int getMinimumDrawWidth() {
return this.minimumDrawWidth;
}
/**
* Sets the minimum drawing width for the chart on this panel.
* <P>
* At the time the chart is drawn on the panel, if the available width is less than this amount, the chart will be
* drawn using the minimum width then scaled down to fit the available space.
*
* @param width
* The width.
*/
@Override
public void setMinimumDrawWidth(int width) {
this.minimumDrawWidth = width;
}
/**
* Returns the maximum drawing width for charts.
* <P>
* If the width available on the panel is greater than this, then the chart is drawn at the maximum width then
* scaled up to fit.
*
* @return The maximum drawing width.
*/
@Override
public int getMaximumDrawWidth() {
return this.maximumDrawWidth;
}
/**
* Sets the maximum drawing width for the chart on this panel.
* <P>
* At the time the chart is drawn on the panel, if the available width is greater than this amount, the chart will
* be drawn using the maximum width then scaled up to fit the available space.
*
* @param width
* The width.
*/
@Override
public void setMaximumDrawWidth(int width) {
this.maximumDrawWidth = width;
}
/**
* Returns the minimum drawing height for charts.
* <P>
* If the height available on the panel is less than this, then the chart is drawn at the minimum height then scaled
* down to fit.
*
* @return The minimum drawing height.
*/
@Override
public int getMinimumDrawHeight() {
return this.minimumDrawHeight;
}
/**
* Sets the minimum drawing height for the chart on this panel.
* <P>
* At the time the chart is drawn on the panel, if the available height is less than this amount, the chart will be
* drawn using the minimum height then scaled down to fit the available space.
*
* @param height
* The height.
*/
@Override
public void setMinimumDrawHeight(int height) {
this.minimumDrawHeight = height;
}
/**
* Returns the maximum drawing height for charts.
* <P>
* If the height available on the panel is greater than this, then the chart is drawn at the maximum height then
* scaled up to fit.
*
* @return The maximum drawing height.
*/
@Override
public int getMaximumDrawHeight() {
return this.maximumDrawHeight;
}
/**
* Sets the maximum drawing height for the chart on this panel.
* <P>
* At the time the chart is drawn on the panel, if the available height is greater than this amount, the chart will
* be drawn using the maximum height then scaled up to fit the available space.
*
* @param height
* The height.
*/
@Override
public void setMaximumDrawHeight(int height) {
this.maximumDrawHeight = height;
}
/**
* Returns the X scale factor for the chart. This will be 1.0 if no scaling has been used.
*
* @return The scale factor.
*/
@Override
public double getScaleX() {
return this.scaleX;
}
/**
* Returns the Y scale factory for the chart. This will be 1.0 if no scaling has been used.
*
* @return The scale factor.
*/
@Override
public double getScaleY() {
return this.scaleY;
}
/**
* Returns the anchor point.
*
* @return The anchor point (possibly <code>null</code>).
*/
@Override
public Point2D getAnchor() {
return this.anchor;
}
/**
* Sets the anchor point. This method is provided for the use of subclasses, not end users.
*
* @param anchor
* the anchor point (<code>null</code> permitted).
*/
@Override
protected void setAnchor(Point2D anchor) {
this.anchor = anchor;
}
/**
* Returns the popup menu.
*
* @return The popup menu.
*/
@Override
public JPopupMenu getPopupMenu() {
return this.popup;
}
/**
* Sets the popup menu for the panel.
*
* @param popup
* the popup menu (<code>null</code> permitted).
*/
@Override
public void setPopupMenu(JPopupMenu popup) {
this.popup = popup;
}
/**
* Returns the chart rendering info from the most recent chart redraw.
*
* @return The chart rendering info.
*/
@Override
public ChartRenderingInfo getChartRenderingInfo() {
return this.info;
}
/**
* A convenience method that switches on mouse-based zooming.
*
* @param flag
* <code>true</code> enables zooming and rectangle fill on zoom.
*/
@Override
public void setMouseZoomable(boolean flag) {
setMouseZoomable(flag, true);
}
/**
* A convenience method that switches on mouse-based zooming.
*
* @param flag
* <code>true</code> if zooming enabled
* @param fillRectangle
* <code>true</code> if zoom rectangle is filled, false if rectangle is shown as outline only.
*/
@Override
public void setMouseZoomable(boolean flag, boolean fillRectangle) {
setDomainZoomable(flag);
setRangeZoomable(flag);
setFillZoomRectangle(fillRectangle);
}
/**
* Returns the flag that determines whether or not zooming is enabled for the domain axis.
*
* @return A boolean.
*/
@Override
public boolean isDomainZoomable() {
return this.domainZoomable;
}
/**
* Sets the flag that controls whether or not zooming is enable for the domain axis. A check is made to ensure that
* the current plot supports zooming for the domain values.
*
* @param flag
* <code>true</code> enables zooming if possible.
*/
@Override
public void setDomainZoomable(boolean flag) {
if (flag) {
Plot plot = this.chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
this.domainZoomable = flag && (z.isDomainZoomable());
}
} else {
this.domainZoomable = false;
}
}
/**
* Returns the flag that determines whether or not zooming is enabled for the range axis.
*
* @return A boolean.
*/
@Override
public boolean isRangeZoomable() {
return this.rangeZoomable;
}
/**
* A flag that controls mouse-based zooming on the vertical axis.
*
* @param flag
* <code>true</code> enables zooming.
*/
@Override
public void setRangeZoomable(boolean flag) {
if (flag) {
Plot plot = this.chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
this.rangeZoomable = flag && (z.isRangeZoomable());
}
} else {
this.rangeZoomable = false;
}
}
/**
* Returns the flag that controls whether or not the zoom rectangle is filled when drawn.
*
* @return A boolean.
*/
@Override
public boolean getFillZoomRectangle() {
return this.fillSelectionRectangle;
}
/**
* A flag that controls how the zoom rectangle is drawn.
*
* @param flag
* <code>true</code> instructs to fill the rectangle on zoom, otherwise it will be outlined.
*/
@Override
public void setFillZoomRectangle(boolean flag) {
this.fillSelectionRectangle = flag;
}
/**
* Returns the zoom trigger distance. This controls how far the mouse must move before a zoom action is triggered.
*
* @return The distance (in Java2D units).
*/
@Override
public int getZoomTriggerDistance() {
return this.zoomTriggerDistance;
}
/**
* Sets the zoom trigger distance. This controls how far the mouse must move before a zoom action is triggered.
*
* @param distance
* the distance (in Java2D units).
*/
@Override
public void setZoomTriggerDistance(int distance) {
this.zoomTriggerDistance = distance;
}
/**
* Returns the flag that controls whether or not a horizontal axis trace line is drawn over the plot area at the
* current mouse location.
*
* @return A boolean.
*/
@Override
public boolean getHorizontalAxisTrace() {
return this.horizontalAxisTrace;
}
/**
* A flag that controls trace lines on the horizontal axis.
*
* @param flag
* <code>true</code> enables trace lines for the mouse pointer on the horizontal axis.
*/
@Override
public void setHorizontalAxisTrace(boolean flag) {
this.horizontalAxisTrace = flag;
}
/**
* Returns the horizontal trace line.
*
* @return The horizontal trace line (possibly <code>null</code>).
*/
@Override
protected Line2D getHorizontalTraceLine() {
return this.horizontalTraceLine;
}
/**
* Sets the horizontal trace line.
*
* @param line
* the line (<code>null</code> permitted).
*/
@Override
protected void setHorizontalTraceLine(Line2D line) {
this.horizontalTraceLine = line;
}
/**
* Returns the flag that controls whether or not a vertical axis trace line is drawn over the plot area at the
* current mouse location.
*
* @return A boolean.
*/
@Override
public boolean getVerticalAxisTrace() {
return this.verticalAxisTrace;
}
/**
* A flag that controls trace lines on the vertical axis.
*
* @param flag
* <code>true</code> enables trace lines for the mouse pointer on the vertical axis.
*/
@Override
public void setVerticalAxisTrace(boolean flag) {
this.verticalAxisTrace = flag;
}
/**
* Returns the vertical trace line.
*
* @return The vertical trace line (possibly <code>null</code>).
*/
@Override
protected Line2D getVerticalTraceLine() {
return this.verticalTraceLine;
}
/**
* Sets the vertical trace line.
*
* @param line
* the line (<code>null</code> permitted).
*/
@Override
protected void setVerticalTraceLine(Line2D line) {
this.verticalTraceLine = line;
}
/**
* Returns the default directory for the "save as" option.
*
* @return The default directory (possibly <code>null</code>).
*
* @since 1.0.7
*/
@Override
public File getDefaultDirectoryForSaveAs() {
return this.defaultDirectoryForSaveAs;
}
/**
* Sets the default directory for the "save as" option. If you set this to <code>null</code>, the user's default
* directory will be used.
*
* @param directory
* the directory (<code>null</code> permitted).
*
* @since 1.0.7
*/
@Override
public void setDefaultDirectoryForSaveAs(File directory) {
if (directory != null) {
if (!directory.isDirectory()) {
throw new IllegalArgumentException("The 'directory' argument is not a directory.");
}
}
this.defaultDirectoryForSaveAs = directory;
}
/**
* Returns <code>true</code> if file extensions should be enforced, and <code>false</code> otherwise.
*
* @return The flag.
*
* @see #setEnforceFileExtensions(boolean)
*/
@Override
public boolean isEnforceFileExtensions() {
return this.enforceFileExtensions;
}
/**
* Sets a flag that controls whether or not file extensions are enforced.
*
* @param enforce
* the new flag value.
*
* @see #isEnforceFileExtensions()
*/
@Override
public void setEnforceFileExtensions(boolean enforce) {
this.enforceFileExtensions = enforce;
}
/**
* Returns the flag that controls whether or not zoom operations are centered around the current anchor point.
*
* @return A boolean.
*
* @since 1.0.7
*
* @see #setZoomAroundAnchor(boolean)
*/
@Override
public boolean getZoomAroundAnchor() {
return this.zoomAroundAnchor;
}
/**
* Sets the flag that controls whether or not zoom operations are centered around the current anchor point.
*
* @param zoomAroundAnchor
* the new flag value.
*
* @since 1.0.7
*
* @see #getZoomAroundAnchor()
*/
@Override
public void setZoomAroundAnchor(boolean zoomAroundAnchor) {
this.zoomAroundAnchor = zoomAroundAnchor;
}
/**
* Returns the zoom rectangle fill paint.
*
* @return The zoom rectangle fill paint (never <code>null</code>).
*
* @see #setZoomFillPaint(java.awt.Paint)
* @see #setFillZoomRectangle(boolean)
*
* @since 1.0.13
*/
@Override
public Paint getZoomFillPaint() {
return this.selectionFillPaint;
}
/**
* Sets the zoom rectangle fill paint.
*
* @param paint
* the paint (<code>null</code> not permitted).
*
* @see #getZoomFillPaint()
* @see #getFillZoomRectangle()
*
* @since 1.0.13
*/
@Override
public void setZoomFillPaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.selectionFillPaint = paint;
}
/**
* Returns the zoom rectangle outline paint.
*
* @return The zoom rectangle outline paint (never <code>null</code>).
*
* @see #setZoomOutlinePaint(java.awt.Paint)
* @see #setFillZoomRectangle(boolean)
*
* @since 1.0.13
*/
@Override
public Paint getZoomOutlinePaint() {
return this.selectionOutlinePaint;
}
/**
* Sets the zoom rectangle outline paint.
*
* @param paint
* the paint (<code>null</code> not permitted).
*
* @see #getZoomOutlinePaint()
* @see #getFillZoomRectangle()
*
* @since 1.0.13
*/
@Override
public void setZoomOutlinePaint(Paint paint) {
this.selectionOutlinePaint = paint;
}
/**
* The mouse wheel handler. This will be an instance of MouseWheelHandler but we can't reference that class directly
* because it depends on JRE 1.4 and we still want to support JRE 1.3.1.
*/
private Object mouseWheelHandler;
/**
* Returns <code>true</code> if the mouse wheel handler is enabled, and <code>false</code> otherwise.
*
* @return A boolean.
*
* @since 1.0.13
*/
@Override
public boolean isMouseWheelEnabled() {
return this.mouseWheelHandler != null;
}
/**
* Enables or disables mouse wheel support for the panel. Note that this method does nothing when running JFreeChart
* on JRE 1.3.1, because that older version of the Java runtime does not support mouse wheel events.
*
* @param flag
* a boolean.
*
* @since 1.0.13
*/
@Override
public void setMouseWheelEnabled(boolean flag) {
if (flag && this.mouseWheelHandler == null) {
// use reflection to instantiate a mouseWheelHandler because to
// continue supporting JRE 1.3.1 we cannot depend on the
// MouseWheelListener interface directly
try {
Class<?> c = Class.forName("org.jfree.chart.MouseWheelHandler");
Constructor<?> cc = c.getConstructor(new Class[] { ChartPanel.class });
Object mwh = cc.newInstance(new Object[] { this });
this.mouseWheelHandler = mwh;
} catch (ClassNotFoundException e) {
// the class isn't there, so we must have compiled JFreeChart
// with JDK 1.3.1 - thus, we can't have mouse wheel support
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else if (!flag && this.mouseWheelHandler != null) {
// use reflection to deregister the mouseWheelHandler
try {
Class<?> mwl = Class.forName("java.awt.event.MouseWheelListener");
Class<ChartPanel> c2 = ChartPanel.class;
Method m = c2.getMethod("removeMouseWheelListener", new Class[] { mwl });
m.invoke(this, new Object[] { this.mouseWheelHandler });
this.mouseWheelHandler = null;
} catch (ClassNotFoundException e) {
// must be running on JRE 1.3.1, so just ignore this
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**
* Add an overlay to the panel.
*
* @param overlay
* the overlay (<code>null</code> not permitted).
*
* @since 1.0.13
*/
@Override
public void addOverlay(Overlay overlay) {
if (overlay == null) {
throw new IllegalArgumentException("Null 'overlay' argument.");
}
this.overlays.add(overlay);
overlay.addChangeListener(this);
repaint();
}
/**
* Removes an overlay from the panel.
*
* @param overlay
* the overlay to remove (<code>null</code> not permitted).
*
* @since 1.0.13
*/
@Override
public void removeOverlay(Overlay overlay) {
if (overlay == null) {
throw new IllegalArgumentException("Null 'overlay' argument.");
}
boolean removed = this.overlays.remove(overlay);
if (removed) {
overlay.removeChangeListener(this);
repaint();
}
}
/**
* Handles a change to an overlay by repainting the panel.
*
* @param event
* the event.
*
* @since 1.0.13
*/
@Override
public void overlayChanged(OverlayChangeEvent event) {
repaint();
}
/**
* Switches the display of tooltips for the panel on or off. Note that tooltips can only be displayed if the chart
* has been configured to generate tooltip items.
*
* @param flag
* <code>true</code> to enable tooltips, <code>false</code> to disable tooltips.
*/
@Override
public void setDisplayToolTips(boolean flag) {
if (flag) {
ToolTipManager.sharedInstance().registerComponent(this);
} else {
ToolTipManager.sharedInstance().unregisterComponent(this);
}
}
/**
* Returns a string for the tooltip.
*
* @param e
* the mouse event.
*
* @return A tool tip or <code>null</code> if no tooltip is available.
*/
@Override
public String getToolTipText(MouseEvent e) {
String result = null;
if (this.info != null) {
EntityCollection entities = this.info.getEntityCollection();
if (entities != null) {
Insets insets = getInsets();
ChartEntity entity = entities.getEntity((int) ((e.getX() - insets.left) / this.scaleX), (int) ((e.getY() - insets.top) / this.scaleY));
if (entity != null) {
result = entity.getToolTipText();
}
}
}
return result;
}
/**
* Translates a Java2D point on the chart to a screen location.
*
* @param java2DPoint
* the Java2D point.
*
* @return The screen location.
*/
@Override
public Point translateJava2DToScreen(Point2D java2DPoint) {
Insets insets = getInsets();
int x = (int) (java2DPoint.getX() * this.scaleX + insets.left);
int y = (int) (java2DPoint.getY() * this.scaleY + insets.top);
return new Point(x, y);
}
/**
* Translates a panel (component) location to a Java2D point.
*
* @param screenPoint
* the screen location (<code>null</code> not permitted).
*
* @return The Java2D coordinates.
*/
@Override
public Point2D translateScreenToJava2D(Point screenPoint) {
Insets insets = getInsets();
double x = (screenPoint.getX() - insets.left) / this.scaleX;
double y = (screenPoint.getY() - insets.top) / this.scaleY;
return new Point2D.Double(x, y);
}
/**
* Applies any scaling that is in effect for the chart drawing to the given rectangle.
*
* @param rect
* the rectangle (<code>null</code> not permitted).
*
* @return A new scaled rectangle.
*/
@Override
public Rectangle2D scale(Rectangle2D rect) {
Insets insets = getInsets();
double x = rect.getX() * getScaleX() + insets.left;
double y = rect.getY() * getScaleY() + insets.top;
double w = rect.getWidth() * getScaleX();
double h = rect.getHeight() * getScaleY();
return new Rectangle2D.Double(x, y, w, h);
}
/**
* Returns the chart entity at a given point.
* <P>
* This method will return null if there is (a) no entity at the given point, or (b) no entity collection has been
* generated.
*
* @param viewX
* the x-coordinate.
* @param viewY
* the y-coordinate.
*
* @return The chart entity (possibly <code>null</code>).
*/
@Override
public ChartEntity getEntityForPoint(int viewX, int viewY) {
ChartEntity result = null;
if (this.info != null) {
Insets insets = getInsets();
double x = (viewX - insets.left) / this.scaleX;
double y = (viewY - insets.top) / this.scaleY;
EntityCollection entities = this.info.getEntityCollection();
result = entities != null ? entities.getEntity(x, y) : null;
}
return result;
}
/**
* Paints the component by drawing the chart to fill the entire component, but allowing for the insets (which will
* be non-zero if a border has been set for this component). To increase performance (at the expense of memory), an
* off-screen buffer image can be used.
*
* @param g
* the graphics device for drawing on.
*/
@Override
public void paintComponent(Graphics g) {
if (this.chart == null) {
return;
}
Graphics2D g2 = (Graphics2D) g.create();
// first determine the size of the chart rendering area...
Dimension size = getSize();
Insets insets = getInsets();
Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top, size.getWidth() - insets.left - insets.right, size.getHeight() - insets.top - insets.bottom);
// work out if scaling is required...
boolean scale = false;
double drawWidth = available.getWidth();
double drawHeight = available.getHeight();
this.scaleX = 1.0;
this.scaleY = 1.0;
if (drawWidth < this.minimumDrawWidth) {
this.scaleX = drawWidth / this.minimumDrawWidth;
drawWidth = this.minimumDrawWidth;
scale = true;
} else if (drawWidth > this.maximumDrawWidth) {
this.scaleX = drawWidth / this.maximumDrawWidth;
drawWidth = this.maximumDrawWidth;
scale = true;
}
if (drawHeight < this.minimumDrawHeight) {
this.scaleY = drawHeight / this.minimumDrawHeight;
drawHeight = this.minimumDrawHeight;
scale = true;
} else if (drawHeight > this.maximumDrawHeight) {
this.scaleY = drawHeight / this.maximumDrawHeight;
drawHeight = this.maximumDrawHeight;
scale = true;
}
Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth, drawHeight);
// redrawing the chart every time...
AffineTransform saved = g2.getTransform();
g2.translate(insets.left, insets.top);
if (scale) {
AffineTransform st = AffineTransform.getScaleInstance(this.scaleX, this.scaleY);
g2.transform(st);
}
this.chart.draw(g2, chartArea, this.anchor, this.info);
g2.setTransform(saved);
Iterator<Overlay> iterator = this.overlays.iterator();
while (iterator.hasNext()) {
Overlay overlay = iterator.next();
overlay.paintOverlay(g2, this);
}
// redraw the zoom rectangle (if present) - if useBuffer is false,
// we use XOR so we can XOR the rectangle away again without redrawing
// the chart
drawSelectionRectangle(g2);
g2.dispose();
this.anchor = null;
this.verticalTraceLine = null;
this.horizontalTraceLine = null;
}
/**
* Receives notification of changes to the chart, and redraws the chart.
*
* @param event
* details of the chart change event.
*/
@Override
public void chartChanged(ChartChangeEvent event) {
Plot plot = this.chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
this.orientation = z.getOrientation();
}
repaint();
}
/**
* Receives notification of a chart progress event.
*
* @param event
* the event.
*/
@Override
public void chartProgress(ChartProgressEvent event) {
// does nothing - override if necessary
}
/**
* Handles action events generated by the popup menu.
*
* @param event
* the event.
*/
@Override
public void actionPerformed(ActionEvent event) {
String command = event.getActionCommand();
// many of the zoom methods need a screen location - all we have is
// the zoomPoint, but it might be null. Here we grab the x and y
// coordinates, or use defaults...
double screenX = -1.0;
double screenY = -1.0;
if (this.zoomPoint != null) {
screenX = this.zoomPoint.getX();
screenY = this.zoomPoint.getY();
}
if (command.equals(PROPERTIES_COMMAND)) {
doEditChartProperties();
} else if (command.equals(COPY_COMMAND)) {
doCopy();
} else if (command.equals(SAVE_COMMAND)) {
try {
doSaveAs();
} catch (IOException e) {
e.printStackTrace();
}
} else if (command.equals(PRINT_COMMAND)) {
createChartPrintJob();
} else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
zoomInBoth(screenX, screenY);
} else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
shrinkSelectionOnCenter(screenX, screenY, null);
} else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
shrinkSelectionOnRange(screenX, screenY, null);
} else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
enlargeSelectionOnCenter(screenX, screenY, null);
} else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
enlargeSelectionOnDomain(screenX, screenY, null);
} else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
enlargeSelectionOnRange(screenX, screenY, null);
} else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
restoreAutoBounds();
} else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
selectCompleteDomainBounds();
} else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
selectCompleteRangeBounds();
}
}
/**
* Handles a 'mouse entered' event. This method changes the tooltip delays of ToolTipManager.sharedInstance() to the
* possibly different values set for this chart panel.
*
* @param e
* the mouse event.
*/
@Override
public void mouseEntered(MouseEvent e) {
if (!this.ownToolTipDelaysActive) {
ToolTipManager ttm = ToolTipManager.sharedInstance();
this.originalToolTipInitialDelay = ttm.getInitialDelay();
ttm.setInitialDelay(this.ownToolTipInitialDelay);
this.originalToolTipReshowDelay = ttm.getReshowDelay();
ttm.setReshowDelay(this.ownToolTipReshowDelay);
this.originalToolTipDismissDelay = ttm.getDismissDelay();
ttm.setDismissDelay(this.ownToolTipDismissDelay);
this.ownToolTipDelaysActive = true;
}
}
/**
* Handles a 'mouse exited' event. This method resets the tooltip delays of ToolTipManager.sharedInstance() to their
* original values in effect before mouseEntered()
*
* @param e
* the mouse event.
*/
@Override
public void mouseExited(MouseEvent e) {
if (this.ownToolTipDelaysActive) {
// restore original tooltip dealys
ToolTipManager ttm = ToolTipManager.sharedInstance();
ttm.setInitialDelay(this.originalToolTipInitialDelay);
ttm.setReshowDelay(this.originalToolTipReshowDelay);
ttm.setDismissDelay(this.originalToolTipDismissDelay);
this.ownToolTipDelaysActive = false;
}
}
/**
* Handles a 'mouse pressed' event.
* <P>
* This event is the popup trigger on Unix/Linux. For Windows, the popup trigger is the 'mouse released' event.
*
* @param e
* The mouse event.
*/
@Override
public void mousePressed(MouseEvent e) {
if (this.chart == null) {
return;
}
Plot plot = this.chart.getPlot();
int mods = e.getModifiers();
if ((mods & this.panMask) == this.panMask) {
// can we pan this plot?
if (plot instanceof Pannable) {
Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
if (screenDataArea != null && screenDataArea.contains(e.getPoint())) {
this.panW = screenDataArea.getWidth();
this.panH = screenDataArea.getHeight();
this.panLast = e.getPoint();
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
}
} else if (this.selectionRectangle == null) {
Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
if (screenDataArea != null) {
this.zoomPoint = getPointInRectangle(e.getX(), e.getY(), screenDataArea);
} else {
this.zoomPoint = null;
}
if (e.isPopupTrigger()) {
if (this.popup != null) {
displayPopupMenu(e.getX(), e.getY());
}
}
}
}
/**
* Returns a point based on (x, y) but constrained to be within the bounds of the given rectangle. This method could
* be moved to JCommon.
*
* @param x
* the x-coordinate.
* @param y
* the y-coordinate.
* @param area
* the rectangle (<code>null</code> not permitted).
*
* @return A point within the rectangle.
*/
private Point2D getPointInRectangle(int x, int y, Rectangle2D area) {
double xx = Math.max(area.getMinX(), Math.min(x, area.getMaxX()));
double yy = Math.max(area.getMinY(), Math.min(y, area.getMaxY()));
return new Point2D.Double(xx, yy);
}
/**
* Handles a 'mouse dragged' event.
*
* @param e
* the mouse event.
*/
@Override
public void mouseDragged(MouseEvent e) {
// if the popup menu has already been triggered, then ignore dragging...
if (this.popup != null && this.popup.isShowing()) {
return;
}
// handle panning if we have a start point
if (this.panLast != null) {
double dx = e.getX() - this.panLast.getX();
double dy = e.getY() - this.panLast.getY();
if (dx == 0.0 && dy == 0.0) {
return;
}
double wPercent = -dx / this.panW;
double hPercent = dy / this.panH;
boolean old = this.chart.getPlot().isNotify();
this.chart.getPlot().setNotify(false);
Pannable p = (Pannable) this.chart.getPlot();
if (p.getOrientation() == PlotOrientation.VERTICAL) {
panAxes(wPercent, hPercent, e);
} else {
panAxes(hPercent, wPercent, e);
}
this.panLast = e.getPoint();
this.chart.getPlot().setNotify(old);
return;
}
// if no initial zoom point was set, ignore dragging...
if (this.zoomPoint == null) {
return;
}
boolean hZoom = false;
boolean vZoom = false;
if (this.orientation == PlotOrientation.HORIZONTAL) {
hZoom = this.rangeZoomable;
vZoom = this.domainZoomable;
} else {
hZoom = this.domainZoomable;
vZoom = this.rangeZoomable;
}
Rectangle2D scaledDataArea = getScreenDataArea((int) this.zoomPoint.getX(), (int) this.zoomPoint.getY());
if (hZoom && vZoom) {
// selected rectangle shouldn't extend outside the data area...
double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
this.selectionRectangle = new Rectangle2D.Double(this.zoomPoint.getX(), this.zoomPoint.getY(), xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY());
} else if (hZoom) {
double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
this.selectionRectangle = new Rectangle2D.Double(this.zoomPoint.getX(), scaledDataArea.getMinY(), xmax - this.zoomPoint.getX(), scaledDataArea.getHeight());
} else if (vZoom) {
double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
this.selectionRectangle = new Rectangle2D.Double(scaledDataArea.getMinX(), this.zoomPoint.getY(), scaledDataArea.getWidth(), ymax - this.zoomPoint.getY());
}
// Draw the new zoom rectangle...
repaint();
}
private void panAxes(double wPercent, double hPercent, MouseEvent selectionEvent) {
Plot p = this.chart.getPlot();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
Selection selectionObject = new Selection();
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis axis = plot.getRangeAxis(i);
double lowerBound = axis.getLowerBound();
double upperBound = axis.getUpperBound();
double shift = (upperBound - lowerBound) * hPercent;
lowerBound += shift;
upperBound += shift;
Range axisRange = new Range(lowerBound, upperBound);
for (String axisName: axisNameResolver.resolveYAxis(i))
selectionObject.addDelimiter(axisName, axisRange);
}
for (int i = 0; i < plot.getDomainAxisCount(); i++) {
ValueAxis axis = plot.getDomainAxis(i);
double lowerBound = axis.getLowerBound();
double upperBound = axis.getUpperBound();
double shift = (upperBound - lowerBound) * wPercent;
lowerBound += shift;
upperBound += shift;
Range axisRange = new Range(lowerBound, upperBound);
for (String axisName: axisNameResolver.resolveXAxis(i))
selectionObject.addDelimiter(axisName, axisRange);
}
informSelectionListener(selectionObject, selectionEvent);
}
}
/**
* Handles a 'mouse released' event. On Windows, we need to check if this is a popup trigger, but only if we haven't
* already been tracking a zoom rectangle.
*
* @param e
* information about the event.
*/
@Override
public void mouseReleased(MouseEvent e) {
// if we've been panning, we need to reset now that the mouse is
// released...
if (this.panLast != null) {
this.panLast = null;
setCursor(Cursor.getDefaultCursor());
} else if (this.selectionRectangle != null) {
boolean hZoom = false;
boolean vZoom = false;
if (this.orientation == PlotOrientation.HORIZONTAL) {
hZoom = this.rangeZoomable;
vZoom = this.domainZoomable;
} else {
hZoom = this.domainZoomable;
vZoom = this.rangeZoomable;
}
boolean zoomTrigger1 = hZoom && Math.abs(e.getX() - this.zoomPoint.getX()) >= this.zoomTriggerDistance;
boolean zoomTrigger2 = vZoom && Math.abs(e.getY() - this.zoomPoint.getY()) >= this.zoomTriggerDistance;
if (zoomTrigger1 || zoomTrigger2) {
if ((hZoom && (e.getX() < this.zoomPoint.getX())) || (vZoom && (e.getY() < this.zoomPoint.getY()))) {
restoreAutoBounds();
} else {
double x, y, w, h;
Rectangle2D screenDataArea = getScreenDataArea((int) this.zoomPoint.getX(), (int) this.zoomPoint.getY());
double maxX = screenDataArea.getMaxX();
double maxY = screenDataArea.getMaxY();
// for mouseReleased event, (horizontalZoom || verticalZoom)
// will be true, so we can just test for either being false;
// otherwise both are true
if (!vZoom) {
x = this.zoomPoint.getX();
y = screenDataArea.getMinY();
w = Math.min(this.selectionRectangle.getWidth(), maxX - this.zoomPoint.getX());
h = screenDataArea.getHeight();
} else if (!hZoom) {
x = screenDataArea.getMinX();
y = this.zoomPoint.getY();
w = screenDataArea.getWidth();
h = Math.min(this.selectionRectangle.getHeight(), maxY - this.zoomPoint.getY());
} else {
x = this.zoomPoint.getX();
y = this.zoomPoint.getY();
w = Math.min(this.selectionRectangle.getWidth(), maxX - this.zoomPoint.getX());
h = Math.min(this.selectionRectangle.getHeight(), maxY - this.zoomPoint.getY());
}
Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
selectRectangle(zoomArea, e);
}
this.zoomPoint = null;
this.selectionRectangle = null;
} else {
this.zoomPoint = null;
this.selectionRectangle = null;
}
}
else if (e.isPopupTrigger()) {
if (this.popup != null) {
displayPopupMenu(e.getX(), e.getY());
}
}
}
/**
* Receives notification of mouse clicks on the panel. These are translated and passed on to any registered
* {@link ChartMouseListener}s.
*
* @param event
* Information about the mouse event.
*/
@Override
public void mouseClicked(MouseEvent event) {
Insets insets = getInsets();
int x = (int) ((event.getX() - insets.left) / this.scaleX);
int y = (int) ((event.getY() - insets.top) / this.scaleY);
this.anchor = new Point2D.Double(x, y);
if (this.chart == null) {
return;
}
this.chart.setNotify(true); // force a redraw
// new entity code...
Object[] listeners = this.chartMouseListeners.getListeners(ChartMouseListener.class);
if (listeners.length == 0) {
return;
}
ChartEntity entity = null;
if (this.info != null) {
EntityCollection entities = this.info.getEntityCollection();
if (entities != null) {
entity = entities.getEntity(x, y);
}
}
ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event, entity);
for (int i = listeners.length - 1; i >= 0; i -= 1) {
((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
}
}
/**
* Implementation of the MouseMotionListener's method.
*
* @param e
* the event.
*/
@Override
public void mouseMoved(MouseEvent e) {
Graphics2D g2 = (Graphics2D) getGraphics();
if (this.horizontalAxisTrace) {
drawHorizontalAxisTrace(g2, e.getX());
}
if (this.verticalAxisTrace) {
drawVerticalAxisTrace(g2, e.getY());
}
g2.dispose();
Object[] listeners = this.chartMouseListeners.getListeners(ChartMouseListener.class);
if (listeners.length == 0) {
return;
}
Insets insets = getInsets();
int x = (int) ((e.getX() - insets.left) / this.scaleX);
int y = (int) ((e.getY() - insets.top) / this.scaleY);
ChartEntity entity = null;
if (this.info != null) {
EntityCollection entities = this.info.getEntityCollection();
if (entities != null) {
entity = entities.getEntity(x, y);
}
}
// we can only generate events if the panel's chart is not null
// (see bug report 1556951)
if (this.chart != null) {
ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity);
for (int i = listeners.length - 1; i >= 0; i -= 1) {
((ChartMouseListener) listeners[i]).chartMouseMoved(event);
}
}
}
/**
* Zooms in on an anchor point (specified in screen coordinate space).
*
* @param x
* the x value (in screen coordinates).
* @param y
* the y value (in screen coordinates).
*/
public void shrinkSelectionOnCenter(double x, double y, MouseEvent selectionEvent) {
Plot plot = this.chart.getPlot();
if (plot == null) {
return;
}
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = plot.isNotify();
plot.setNotify(false);
shrinkSelectionOnDomain(x, y, selectionEvent);
shrinkSelectionOnRange(x, y, selectionEvent);
plot.setNotify(savedNotify);
}
/**
* Zooms out on an anchor point (specified in screen coordinate space).
*
* @param x
* the x value (in screen coordinates).
* @param y
* the y value (in screen coordinates).
*/
public void enlargeSelectionOnCenter(double x, double y, MouseEvent selectionEvent) {
Plot plot = this.chart.getPlot();
if (plot == null) {
return;
}
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = plot.isNotify();
plot.setNotify(false);
enlargeSelectionOnDomain(x, y, selectionEvent);
enlargeSelectionOnRange(x, y, selectionEvent);
plot.setNotify(savedNotify);
}
/**
* Decreases the length of the domain axis, centered about the given coordinate on the screen. The length of the
* domain axis is reduced by the value of {@link #getZoomInFactor()}.
*
* @param x
* the x coordinate (in screen coordinates).
* @param y
* the y-coordinate (in screen coordinates).
*/
public void shrinkSelectionOnDomain(double x, double y, MouseEvent selectionEvent) {
Plot p = this.chart.getPlot();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
Selection selectionObject = new Selection();
for (int i = 0; i < plot.getDomainAxisCount(); i++) {
ValueAxis domain = plot.getDomainAxis(i);
double zoomFactor = getZoomInFactor();
shrinkSelectionXAxis(x, y, selectionObject, domain, i, zoomFactor);
}
informSelectionListener(selectionObject, selectionEvent);
}
}
/**
* Increases the length of the domain axis, centered about the given coordinate on the screen. The length of the
* domain axis is increased by the value of {@link #getZoomOutFactor()}.
*
* @param x
* the x coordinate (in screen coordinates).
* @param y
* the y-coordinate (in screen coordinates).
*/
public void enlargeSelectionOnDomain(double x, double y, MouseEvent selectionEvent) {
Plot p = this.chart.getPlot();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
Selection selectionObject = new Selection();
for (int i = 0; i < plot.getDomainAxisCount(); i++) {
ValueAxis domain = plot.getDomainAxis(i);
double zoomFactor = getZoomOutFactor();
shrinkSelectionXAxis(x, y, selectionObject, domain, i, zoomFactor);
}
informSelectionListener(selectionObject, selectionEvent);
}
}
private void shrinkSelectionYAxis(double x, double y, Selection selectionObject, ValueAxis axis, int axisIndex, double zoomFactor) {
Rectangle2D scaledDataArea = getScreenDataArea((int) x, (int) y);
double minY = scaledDataArea.getMinY();
double maxY = scaledDataArea.getMaxY();
double partToTop = (y - minY) / (maxY - minY);
double lowerDomain = axis.getLowerBound();
double upperDomain = axis.getUpperBound();
double middlePointTop = lowerDomain + (upperDomain - lowerDomain) * (1d - partToTop);
double width = (upperDomain - lowerDomain) * zoomFactor;
Range axisRange = new Range(middlePointTop - width / 2d, middlePointTop + width / 2d);
for (String axisName: axisNameResolver.resolveYAxis(axisIndex))
selectionObject.addDelimiter(axisName, axisRange);
}
/**
* Decreases the length of the range axis, centered about the given coordinate on the screen. The length of the
* range axis is reduced by the value of {@link #getZoomInFactor()}.
*
* @param x
* the x-coordinate (in screen coordinates).
* @param y
* the y coordinate (in screen coordinates).
*/
public void shrinkSelectionOnRange(double x, double y, MouseEvent selectionEvent) {
Plot p = this.chart.getPlot();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
Selection selectionObject = new Selection();
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis domain = plot.getRangeAxis(i);
double zoomFactor = getZoomInFactor();
shrinkSelectionYAxis(x, y, selectionObject, domain, i, zoomFactor);
}
informSelectionListener(selectionObject, selectionEvent);
}
}
/**
* Increases the length the range axis, centered about the given coordinate on the screen. The length of the range
* axis is increased by the value of {@link #getZoomOutFactor()}.
*
* @param x
* the x coordinate (in screen coordinates).
* @param y
* the y-coordinate (in screen coordinates).
*/
public void enlargeSelectionOnRange(double x, double y, MouseEvent selectionEvent) {
Plot p = this.chart.getPlot();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
Selection selectionObject = new Selection();
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis domain = plot.getRangeAxis(i);
double zoomFactor = getZoomOutFactor();
shrinkSelectionYAxis(x, y, selectionObject, domain, i, zoomFactor);
}
informSelectionListener(selectionObject, selectionEvent);
}
}
private void shrinkSelectionXAxis(double x, double y, Selection selectionObject, ValueAxis axis, int axisIndex, double zoomFactor) {
Rectangle2D scaledDataArea = getScreenDataArea((int) x, (int) y);
double minX = scaledDataArea.getMinX();
double maxX = scaledDataArea.getMaxX();
double partToLeft = (x - minX) / (maxX - minX);
double lowerDomain = axis.getLowerBound();
double upperDomain = axis.getUpperBound();
double middlePointLeft = lowerDomain + (upperDomain - lowerDomain) * partToLeft;
double width = (upperDomain - lowerDomain) * zoomFactor;
Range domainRange = new Range(middlePointLeft - width / 2d, middlePointLeft + width / 2d);
for (String axisName: axisNameResolver.resolveXAxis(axisIndex))
selectionObject.addDelimiter(axisName, domainRange);
}
public void selectRectangle(Rectangle2D selection, MouseEvent selectionEvent) {
Rectangle2D scaledDataArea = getScreenDataArea((int) selection.getCenterX(), (int) selection.getCenterY());
if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {
double hLower = (selection.getMinX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth();
double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth();
double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) / scaledDataArea.getHeight();
double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) / scaledDataArea.getHeight();
Plot p = this.chart.getPlot();
if (p instanceof XYPlot) {
XYPlot plot = (XYPlot) p;
Selection selectionObject = new Selection();
for (int i = 0; i < plot.getDomainAxisCount(); i++) {
ValueAxis domain = plot.getDomainAxis(i);
double lowerDomain = domain.getLowerBound();
double upperDomain = domain.getUpperBound();
Range axisRange = new Range(lowerDomain + (upperDomain - lowerDomain) * hLower, lowerDomain + (upperDomain - lowerDomain) * hUpper);
for (String axisName: axisNameResolver.resolveXAxis(i))
selectionObject.addDelimiter(axisName, axisRange);
}
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis range = plot.getRangeAxis(i);
double lowerRange = range.getLowerBound();
double upperRange = range.getUpperBound();
Range axisRange = new Range(lowerRange + (upperRange - lowerRange) * vLower, lowerRange + (upperRange - lowerRange) * vUpper);
for (String axisName: axisNameResolver.resolveYAxis(i))
selectionObject.addDelimiter(axisName, axisRange);
}
informSelectionListener(selectionObject, selectionEvent);
}
}
}
private void informSelectionListener(Selection selectionObject, MouseEvent selectionEvent) {
for (SelectionListener listener : selectionListeners) {
listener.selected(selectionObject, selectionEvent);
}
if (selectionListeners.isEmpty())
repaint();
}
/**
* Restores the auto-range calculation on both axes.
*/
@Override
public void restoreAutoBounds() {
Plot plot = this.chart.getPlot();
if (plot == null) {
return;
}
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = plot.isNotify();
plot.setNotify(false);
selectCompleteDomainBounds();
selectCompleteRangeBounds();
plot.setNotify(savedNotify);
}
/**
* Restores the auto-range calculation on the domain axis.
*/
public void selectCompleteDomainBounds() {
Plot plot = this.chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = plot.isNotify();
plot.setNotify(false);
// we need to guard against this.zoomPoint being null
Point2D zp = (this.zoomPoint != null ? this.zoomPoint : new Point());
z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp);
plot.setNotify(savedNotify);
if (plot instanceof XYPlot) {
XYPlot xyPlot = (XYPlot) plot;
Selection selectionObject = new Selection();
for (int i = 0; i < xyPlot.getDomainAxisCount(); i++) {
ValueAxis domain = xyPlot.getDomainAxis(i);
Range axisRange = new Range(domain.getLowerBound(), domain.getUpperBound());
for (String axisName: axisNameResolver.resolveXAxis(i))
selectionObject.addDelimiter(axisName, axisRange);
}
informSelectionListener(selectionObject, null);
}
}
}
/**
* Restores the auto-range calculation on the range axis.
*/
public void selectCompleteRangeBounds() {
Plot plot = this.chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = plot.isNotify();
plot.setNotify(false);
// we need to guard against this.zoomPoint being null
Point2D zp = (this.zoomPoint != null ? this.zoomPoint : new Point());
z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp);
plot.setNotify(savedNotify);
if (plot instanceof XYPlot) {
XYPlot xyPlot = (XYPlot) plot;
Selection selectionObject = new Selection();
for (int i = 0; i < xyPlot.getRangeAxisCount(); i++) {
ValueAxis range = xyPlot.getRangeAxis(i);
Range axisRange = new Range(range.getLowerBound(), range.getUpperBound());
for (String axisName: axisNameResolver.resolveYAxis(i))
selectionObject.addDelimiter(axisName, axisRange);
}
informSelectionListener(selectionObject, null);
}
}
}
/**
* Returns the data area for the chart (the area inside the axes) with the current scaling applied (that is, the
* area as it appears on screen).
*
* @return The scaled data area.
*/
@Override
public Rectangle2D getScreenDataArea() {
Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
Insets insets = getInsets();
double x = dataArea.getX() * this.scaleX + insets.left;
double y = dataArea.getY() * this.scaleY + insets.top;
double w = dataArea.getWidth() * this.scaleX;
double h = dataArea.getHeight() * this.scaleY;
return new Rectangle2D.Double(x, y, w, h);
}
/**
* Returns the data area (the area inside the axes) for the plot or subplot, with the current scaling applied.
*
* @param x
* the x-coordinate (for subplot selection).
* @param y
* the y-coordinate (for subplot selection).
*
* @return The scaled data area.
*/
@Override
public Rectangle2D getScreenDataArea(int x, int y) {
PlotRenderingInfo plotInfo = this.info.getPlotInfo();
Rectangle2D result;
if (plotInfo.getSubplotCount() == 0) {
result = getScreenDataArea();
} else {
// get the origin of the zoom selection in the Java2D space used for
// drawing the chart (that is, before any scaling to fit the panel)
Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
if (subplotIndex == -1) {
return null;
}
result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
}
return result;
}
/**
* Returns the initial tooltip delay value used inside this chart panel.
*
* @return An integer representing the initial delay value, in milliseconds.
*
* @see javax.swing.ToolTipManager#getInitialDelay()
*/
@Override
public int getInitialDelay() {
return this.ownToolTipInitialDelay;
}
/**
* Returns the reshow tooltip delay value used inside this chart panel.
*
* @return An integer representing the reshow delay value, in milliseconds.
*
* @see javax.swing.ToolTipManager#getReshowDelay()
*/
@Override
public int getReshowDelay() {
return this.ownToolTipReshowDelay;
}
/**
* Returns the dismissal tooltip delay value used inside this chart panel.
*
* @return An integer representing the dismissal delay value, in milliseconds.
*
* @see javax.swing.ToolTipManager#getDismissDelay()
*/
@Override
public int getDismissDelay() {
return this.ownToolTipDismissDelay;
}
/**
* Specifies the initial delay value for this chart panel.
*
* @param delay
* the number of milliseconds to delay (after the cursor has paused) before displaying.
*
* @see javax.swing.ToolTipManager#setInitialDelay(int)
*/
@Override
public void setInitialDelay(int delay) {
this.ownToolTipInitialDelay = delay;
}
/**
* Specifies the amount of time before the user has to wait initialDelay milliseconds before a tooltip will be
* shown.
*
* @param delay
* time in milliseconds
*
* @see javax.swing.ToolTipManager#setReshowDelay(int)
*/
@Override
public void setReshowDelay(int delay) {
this.ownToolTipReshowDelay = delay;
}
/**
* Specifies the dismissal delay value for this chart panel.
*
* @param delay
* the number of milliseconds to delay before taking away the tooltip
*
* @see javax.swing.ToolTipManager#setDismissDelay(int)
*/
@Override
public void setDismissDelay(int delay) {
this.ownToolTipDismissDelay = delay;
}
/**
* Returns the zoom in factor.
*
* @return The zoom in factor.
*
* @see #setZoomInFactor(double)
*/
@Override
public double getZoomInFactor() {
return this.zoomInFactor;
}
/**
* Sets the zoom in factor.
*
* @param factor
* the factor.
*
* @see #getZoomInFactor()
*/
@Override
public void setZoomInFactor(double factor) {
this.zoomInFactor = factor;
}
/**
* Returns the zoom out factor.
*
* @return The zoom out factor.
*
* @see #setZoomOutFactor(double)
*/
@Override
public double getZoomOutFactor() {
return this.zoomOutFactor;
}
/**
* Sets the zoom out factor.
*
* @param factor
* the factor.
*
* @see #getZoomOutFactor()
*/
@Override
public void setZoomOutFactor(double factor) {
this.zoomOutFactor = factor;
}
/**
* Draws zoom rectangle (if present). The drawing is performed in XOR mode, therefore when this method is called
* twice in a row, the second call will completely restore the state of the canvas.
*
* @param g2
* the graphics device.
* @param xor
* use XOR for drawing?
*/
private void drawSelectionRectangle(Graphics2D g2) {
if (this.selectionRectangle != null) {
if (this.fillSelectionRectangle) {
g2.setPaint(this.selectionFillPaint);
g2.fill(selectionRectangle);
} else {
g2.setPaint(this.selectionOutlinePaint);
g2.draw(selectionRectangle);
}
}
}
/**
* Draws a vertical line used to trace the mouse position to the horizontal axis.
*
* @param g2
* the graphics device.
* @param x
* the x-coordinate of the trace line.
*/
private void drawHorizontalAxisTrace(Graphics2D g2, int x) {
Rectangle2D dataArea = getScreenDataArea();
g2.setXORMode(Color.orange);
if (((int) dataArea.getMinX() < x) && (x < (int) dataArea.getMaxX())) {
if (this.verticalTraceLine != null) {
g2.draw(this.verticalTraceLine);
this.verticalTraceLine.setLine(x, (int) dataArea.getMinY(), x, (int) dataArea.getMaxY());
} else {
this.verticalTraceLine = new Line2D.Float(x, (int) dataArea.getMinY(), x, (int) dataArea.getMaxY());
}
g2.draw(this.verticalTraceLine);
}
// Reset to the default 'overwrite' mode
g2.setPaintMode();
}
/**
* Draws a horizontal line used to trace the mouse position to the vertical axis.
*
* @param g2
* the graphics device.
* @param y
* the y-coordinate of the trace line.
*/
private void drawVerticalAxisTrace(Graphics2D g2, int y) {
Rectangle2D dataArea = getScreenDataArea();
g2.setXORMode(Color.orange);
if (((int) dataArea.getMinY() < y) && (y < (int) dataArea.getMaxY())) {
if (this.horizontalTraceLine != null) {
g2.draw(this.horizontalTraceLine);
this.horizontalTraceLine.setLine((int) dataArea.getMinX(), y, (int) dataArea.getMaxX(), y);
} else {
this.horizontalTraceLine = new Line2D.Float((int) dataArea.getMinX(), y, (int) dataArea.getMaxX(), y);
}
g2.draw(this.horizontalTraceLine);
}
// Reset to the default 'overwrite' mode
g2.setPaintMode();
}
/**
* Displays a dialog that allows the user to edit the properties for the current chart.
*
* @since 1.0.3
*/
@Override
public void doEditChartProperties() {
ChartEditor editor = ChartEditorManager.getChartEditor(this.chart);
int result = JOptionPane.showConfirmDialog(this, editor, localizationResources.getString("Chart_Properties"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
editor.updateChart(this.chart);
}
}
/**
* Copies the current chart to the system clipboard.
*
* @since 1.0.13
*/
@Override
public void doCopy() {
Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Insets insets = getInsets();
int w = getWidth() - insets.left - insets.right;
int h = getHeight() - insets.top - insets.bottom;
ChartTransferable selection = new ChartTransferable(this.chart, w, h);
systemClipboard.setContents(selection, null);
}
/**
* Opens a file chooser and gives the user an opportunity to save the chart in PNG format.
*
* @throws IOException
* if there is an I/O error.
*/
@Override
public void doSaveAs() throws IOException {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
ExtensionFileFilter filter = new ExtensionFileFilter(localizationResources.getString("PNG_Image_Files"), ".png");
fileChooser.addChoosableFileFilter(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";
}
}
ChartUtilities.saveChartAsPNG(new File(filename), this.chart, getWidth(), getHeight());
}
}
/**
* Creates a print job for the chart.
*/
@Override
public void createChartPrintJob() {
PrinterJob job = PrinterJob.getPrinterJob();
PageFormat pf = job.defaultPage();
PageFormat pf2 = job.pageDialog(pf);
if (pf2 != pf) {
job.setPrintable(this, pf2);
if (job.printDialog()) {
try {
job.print();
} catch (PrinterException e) {
JOptionPane.showMessageDialog(this, e);
}
}
}
}
/**
* Prints the chart on a single page.
*
* @param g
* the graphics context.
* @param pf
* the page format to use.
* @param pageIndex
* the index of the page. If not <code>0</code>, nothing gets print.
*
* @return The result of printing.
*/
@Override
public int print(Graphics g, PageFormat pf, int pageIndex) {
if (pageIndex != 0) {
return NO_SUCH_PAGE;
}
Graphics2D g2 = (Graphics2D) g;
double x = pf.getImageableX();
double y = pf.getImageableY();
double w = pf.getImageableWidth();
double h = pf.getImageableHeight();
this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor, null);
return PAGE_EXISTS;
}
/**
* Adds a listener to the list of objects listening for chart mouse events.
*
* @param listener
* the listener (<code>null</code> not permitted).
*/
@Override
public void addChartMouseListener(ChartMouseListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Null 'listener' argument.");
}
this.chartMouseListeners.add(ChartMouseListener.class, listener);
}
/**
* Removes a listener from the list of objects listening for chart mouse events.
*
* @param listener
* the listener.
*/
@Override
public void removeChartMouseListener(ChartMouseListener listener) {
this.chartMouseListeners.remove(ChartMouseListener.class, listener);
}
/**
* The idea is to modify the zooming options depending on the type of chart being displayed by the panel.
*
* @param x
* horizontal position of the popup.
* @param y
* vertical position of the popup.
*/
@Override
protected void displayPopupMenu(int x, int y) {
if (this.popup == null) {
return;
}
// go through each zoom menu item and decide whether or not to
// enable it...
boolean isDomainZoomable = false;
boolean isRangeZoomable = false;
Plot plot = (this.chart != null ? this.chart.getPlot() : null);
if (plot instanceof Zoomable) {
Zoomable z = (Zoomable) plot;
isDomainZoomable = z.isDomainZoomable();
isRangeZoomable = z.isRangeZoomable();
}
if (this.zoomInDomainMenuItem != null) {
this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
}
if (this.zoomOutDomainMenuItem != null) {
this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
}
if (this.zoomResetDomainMenuItem != null) {
this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
}
if (this.zoomInRangeMenuItem != null) {
this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
}
if (this.zoomOutRangeMenuItem != null) {
this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
}
if (this.zoomResetRangeMenuItem != null) {
this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
}
if (this.zoomInBothMenuItem != null) {
this.zoomInBothMenuItem.setEnabled(isDomainZoomable && isRangeZoomable);
}
if (this.zoomOutBothMenuItem != null) {
this.zoomOutBothMenuItem.setEnabled(isDomainZoomable && isRangeZoomable);
}
if (this.zoomResetBothMenuItem != null) {
this.zoomResetBothMenuItem.setEnabled(isDomainZoomable && isRangeZoomable);
}
coordinateTransformation.showPopupMenu(new Point(x, y), this, popup);
}
/**
* This method sets the coordinate transformation for this component.
*/
public void setCoordinateTransformation(CoordinateTransformation transformation) {
this.coordinateTransformation = transformation;
}
/**
* This method will add the given selection listener to the list of objects which will be notified as soon as a
* selection is made.
*/
public void registerSelectionListener(SelectionListener listener) {
this.selectionListeners.add(listener);
}
/**
* This one clears the complete list of registered selection listeners. This might be useful if a default listener
* should be replaced.
*/
public void clearSelectionListener() {
this.selectionListeners.clear();
}
public void registerAxisNameResolver(AxisNameResolver axisNameResolver) {
this.axisNameResolver = axisNameResolver;
}
}