/*
* 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.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.FastScatterPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
/**
* This class provides ways to shift (aka pan/scroll) a plot. The shift is done
* through the arrow keys and its step can be configured to be a fixed amount,
* a percentual of the current axis or a range in pixels.
* <p>
* This class only supports plots of type {@link org.jfree.chart.plot.XYPlot XYPlot},
* {@link org.jfree.chart.plot.ContourPlot ContourPlot} and
* {@link org.jfree.chart.plot.FastScatterPlot FastScatterPlot}.
* <p>
* Use ← and → to shift the plot left and right; <br>
* Use ↑ and ↓ to shift the plot up and down; <br>
* Press the SHIFT key to increase the shift by a factor of 10.
*
* @author Gustavo H. Sberze Ribas (CPqD), Ingo Mierswa
*/
public class ChartPanelShiftController implements MouseMotionListener, MouseListener {
/** PAN plot by a fixed percentual of the range (eg. 1%) */
public static final int SHIFT_PERCENTUAL = 1;
/** PAN plot by a fixed number of pixels (eg. 1px) */
public static final int SHIFT_PIXEL = 2;
/** PAN plot by a fixed amout (eg. 5 range units) */
public static final int SHIFT_FIXED = 3;
/** The chart panel we're using */
private ChartPanel chartPanel;
/** Does this plot supports shifting? Pie charts for example don't. */
private boolean plotSupported = false;
/** The shift type. (default {@link #SHIFT_PIXEL} ) */
private int shiftType = SHIFT_PIXEL;
/** Fixed shift amount for domain axis */
private double fixedDomainShiftUnits;
/** Fixed shift amount for range axis */
private double fixedRangeShiftUnits;
private int oldx = -1;
private int oldy = -1;
/** By default we assume that the range axis is the vertical one (ie,
* PlotOrientation.VERTICAL (axesSwaped=false). If the range axis is the
* horizontal one (ie, PlotOrientation.HORIZONTAL) this variable should
* be set to true. */
private boolean axesSwaped = false;
private boolean onlyXShift = false;
/**
* Creates a new controller to handle plot shifts.
* @param chartPanel The panel displaying the plot.
*/
public ChartPanelShiftController(ChartPanel chartPanel) {
super();
this.chartPanel = chartPanel;
// Check to see if plot is shiftable
Plot plot = chartPanel.getChart().getPlot();
if ((plot instanceof XYPlot) || (plot instanceof FastScatterPlot)) {
plotSupported = true;
axesSwaped = isHorizontalPlot(plot);
}
}
public void setOnlyXShift(boolean onlyXShift) {
this.onlyXShift = onlyXShift;
}
/**
* Returns the plot orientation.
* @return True = {@link org.jfree.chart.plot.PlotOrientation#VERTICAL VERTICAL};
* False = {@link org.jfree.chart.plot.PlotOrientation#HORIZONTAL HORIZONTAL}
*/
protected boolean isHorizontalPlot(Plot plot) {
if (plot instanceof XYPlot) {
return ((XYPlot) plot).getOrientation() == PlotOrientation.HORIZONTAL;
}
if (plot instanceof FastScatterPlot) {
return ((FastScatterPlot) plot).getOrientation() == PlotOrientation.HORIZONTAL;
}
return false;
}
/**
* Returns the ValueAxis for the plot or <code>null</code> if the plot
* doesn't have one.
* @param chart The chart
* @param domain True = get Domain axis. False = get Range axis.
* @return The selected ValueAxis or <code>null</code> if the plot doesn't
* have one.
*/
protected ValueAxis[] getPlotAxis(JFreeChart chart, boolean domain) {
// Where's the Shiftable interface when we need it ?? ;)
Plot plot = chart.getPlot();
if (plot instanceof XYPlot) {
XYPlot xyPlot = (XYPlot)plot;
//return domain ? ((XYPlot) plot).getDomainAxis() : ((XYPlot) plot).getRangeAxis();
if (domain) {
ValueAxis[] rangeAxes = new ValueAxis[xyPlot.getDomainAxisCount()];
for (int i = 0; i < rangeAxes.length; i++) {
rangeAxes[i] = xyPlot.getDomainAxis(i);
}
return rangeAxes;
} else {
ValueAxis[] rangeAxes = new ValueAxis[xyPlot.getRangeAxisCount()];
for (int i = 0; i < rangeAxes.length; i++) {
rangeAxes[i] = xyPlot.getRangeAxis(i);
}
return rangeAxes;
}
}
if (plot instanceof FastScatterPlot) {
return domain ?
new ValueAxis[] { ((FastScatterPlot) plot).getDomainAxis() } :
new ValueAxis[] { ((FastScatterPlot) plot).getRangeAxis() };
}
return null;
}
/**
* Pan / Shifts a plot if the arrow keys are pressed.
*/
public void keyPressed(KeyEvent e) {
if (!plotSupported)
return;
int keyCode = e.getKeyCode();
// we're only interested in arrows (code 37,38,39,40)
if ((keyCode < 37) || (keyCode > 40))
return;
// The axes we're gonna shift
ValueAxis[] axes = null;
boolean domainShift = false; // used for PAN_FIXED
// Calculations for the domain axis
if ((keyCode == KeyEvent.VK_LEFT) || (keyCode == KeyEvent.VK_RIGHT)) {
axes = getPlotAxis(chartPanel.getChart(), !axesSwaped);
domainShift = true;
}
// Calculations for the range axis
else {
axes = getPlotAxis(chartPanel.getChart(), axesSwaped);
}
// Delta is the amount we'll shift in axes units.
double[] delta = new double[axes.length];
// Let's calculate 'delta', the amount by which we'll shift the plot
for (int i = 0; i < axes.length; i++) {
switch (shiftType) {
case SHIFT_PERCENTUAL:
delta[i] = (axes[i].getUpperBound() - axes[i].getLowerBound()) / 100.0;
break;
case SHIFT_FIXED:
delta[i] = (domainShift ? fixedDomainShiftUnits : fixedRangeShiftUnits);
break;
case SHIFT_PIXEL: // also the default
default:
// Let's find out what's the range for 1 pixel.
final Rectangle2D scaledDataArea = chartPanel.getScreenDataArea();
delta[i] = axes[i].getRange().getLength() / (scaledDataArea.getWidth());
break;
}
}
// Shift modifier multiplies delta by 10
if (e.isShiftDown()) {
for (int i = 0; i < delta.length; i++)
delta[i] *= 10;
}
for (int i = 0; i < axes.length; i++) {
switch (keyCode) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_DOWN:
axes[i].setRange(axes[i].getLowerBound() - delta[i], axes[i].getUpperBound() - delta[i]);
break;
case KeyEvent.VK_UP:
case KeyEvent.VK_RIGHT:
axes[i].setRange(axes[i].getLowerBound() + delta[i], axes[i].getUpperBound() + delta[i]);
break;
}
}
}
public void mouseDragged(MouseEvent mouseEvent) {
if (!mouseEvent.isControlDown())
return;
if (oldx > -1 && oldy > -1) {
int xdif = mouseEvent.getX() - oldx;
int ydif = mouseEvent.getY() - oldy;
final Rectangle2D scaledDataArea = chartPanel.getScreenDataArea();
ValueAxis[] domAxes = getPlotAxis(chartPanel.getChart(), !axesSwaped);
if (domAxes != null) {
double[] xDelta = new double[domAxes.length];
for (int i = 0; i < domAxes.length; i++) {
xDelta[i] = xdif * domAxes[i].getRange().getLength() / (scaledDataArea.getWidth());
}
for (int i = 0; i < domAxes.length; i++) {
domAxes[i].setRange(domAxes[i].getLowerBound() - xDelta[i], domAxes[i].getUpperBound() - xDelta[i]);
}
}
ValueAxis[] rngAxes = getPlotAxis(chartPanel.getChart(), axesSwaped);
if (rngAxes != null) {
double[] yDelta = new double[rngAxes.length];
for (int i = 0; i < rngAxes.length; i++) {
yDelta[i] = ydif * rngAxes[i].getRange().getLength() / (scaledDataArea.getHeight());
}
if (!onlyXShift) {
for (int i = 0; i < rngAxes.length; i++) {
rngAxes[i].setRange(rngAxes[i].getLowerBound() + yDelta[i], rngAxes[i].getUpperBound() + yDelta[i]);
}
}
}
}
oldx = mouseEvent.getX();
oldy = mouseEvent.getY();
}
public void mouseMoved(MouseEvent mouseEvent) {}
public void mouseReleased(MouseEvent mouseEvent) {
oldx = -1;
oldy = -1;
}
public void mouseClicked(MouseEvent mouseEvent) {
}
public void mousePressed(MouseEvent mouseEvent) {
}
public void mouseEntered(MouseEvent mouseEvent) {
}
public void mouseExited(MouseEvent mouseEvent) {
}
/**
* Returns the fixed shift step for the domain axis.
* @return the fixed shift step for the domain axis.
*/
public double getFixedDomainShiftUnits() {
return fixedDomainShiftUnits;
}
/**
* Sets the fixed shift step for the domain axis.
* @param fixedDomainShiftUnits the fixed shift step for the domain axis.
*/
public void setFixedDomainShiftUnits(double fixedDomainShiftUnits) {
this.fixedDomainShiftUnits = fixedDomainShiftUnits;
}
/**
* Returns the fixed shift step for the range axis.
* @return the fixed shift step for the range axis.
*/
public double getFixedRangeShiftUnits() {
return fixedRangeShiftUnits;
}
/**
* Sets the fixed shift step for the range axis.
* @param fixedRangeShiftUnits the fixed shift step for the range axis.
*/
public void setFixedRangeShiftUnits(double fixedRangeShiftUnits) {
this.fixedRangeShiftUnits = fixedRangeShiftUnits;
}
/**
* Returns the current shift type.
* @return the current shift type.
* @see #SHIFT_FIXED
* @see #SHIFT_PERCENTUAL
* @see #SHIFT_PIXEL
*/
public int getShiftType() {
return shiftType;
}
/**
* Sets the shift type.
* @param shiftType the new shift type.
* @see #SHIFT_FIXED
* @see #SHIFT_PERCENTUAL
* @see #SHIFT_PIXEL
*/
public void setShiftType(int shiftType) {
this.shiftType = shiftType;
}
/**
* Returns whether or not the plot supports shifting.
* @return True if plot can be shifted.
*/
public boolean isPlotSupported() {
return plotSupported;
}
}