I'm bringing this back from the dead. I didnt want to mess with scroll bars, so I added functionality to allow the user to pan the graph by pressing control then using the mouse. You can grab the graph like you can with google maps. You even get a cool move icon when you hold control down and move over the plot area.
I took out the pkg def for my PoC. Forgive the indention. Jbuilder sucks at dealing with indention so half are tabs and half are 4 spaces. There even may be some 2 space indents in there.
Code: Select all
/*
* Copyright 2006 CPqD - Centro de Pesquisa e Desenvolvimento em Telecomunicações.
*
* File ChartPanelShiftController.java created on 26/01/2006 10:49:42
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* ------------------------
* ChartPanelShiftController.java
* ------------------------
* Original Author: Gustavo H. Sberze Ribas;
* Contributor(s): Ben Ogle;
*
* Changes
* --------------------------
* 26-Jan-2006 : First Version (GR);
* 16-Mar-2007 : added mouse panning (BO);
*/
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
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.ContourPlot;
import org.jfree.chart.plot.FastScatterPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.Cursor;
/**
* 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)
* @version 0.1
*/
public class ChartPanelShiftController implements KeyListener, 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 */
protected ChartPanel chartPanel;
/** Does this plot supports shifting? Pie charts for example don't. */
protected boolean plotSupported = false;
/** The shift type. (default {@link #SHIFT_PIXEL} ) */
protected int shiftType = SHIFT_PIXEL;
/** Fixed shift amount for domain axis */
protected double fixedDomainShiftUnits;
/** Fixed shift amount for range axis */
protected double fixedRangeShiftUnits;
protected int oldx = -1;
protected int oldy = -1;
protected static final Cursor MOVE = new Cursor(Cursor.MOVE_CURSOR);
protected static final Cursor ARROW = new Cursor(Cursor.DEFAULT_CURSOR);
/** 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. */
protected boolean axesSwaped = 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) ||
(plot instanceof ContourPlot)
) {
plotSupported = true;
axesSwaped = isHorizontalPlot(plot);
}
}
/**
* 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) {
return domain?((XYPlot)plot).getDomainAxis():((XYPlot)plot).getRangeAxis();
}
if (plot instanceof FastScatterPlot) {
return domain?((FastScatterPlot)plot).getDomainAxis():((FastScatterPlot)plot).getRangeAxis();
}
if (plot instanceof ContourPlot) {
return domain?((ContourPlot)plot).getDomainAxis():((ContourPlot)plot).getRangeAxis();
}
return null;
}
/**
* Pan/Shifts a plot if the arrow keys are pressed.
* @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
*/
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 axis we're gonna shift
ValueAxis axis = null;
// Delta is the amount we'll shift in axis units.
double delta;
boolean domainShift = false; // used for PAN_FIXED
// Calculations for the domain axis
if ((keyCode == KeyEvent.VK_LEFT) || (keyCode == KeyEvent.VK_RIGHT)) {
axis = getPlotAxis(chartPanel.getChart(),!axesSwaped);
domainShift = true;
}
// Calculations for the range axis
else {
axis = getPlotAxis(chartPanel.getChart(),axesSwaped);
}
// Let's calculate 'delta', the amount by which we'll shift the plot
switch (shiftType) {
case SHIFT_PERCENTUAL:
delta = (axis.getUpperBound()-axis.getLowerBound())/100.0;
break;
case SHIFT_FIXED:
delta = (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 = axis.getRange().getLength() / (scaledDataArea.getWidth());
break;
}
// Shift modifier multiplies delta by 10
if (e.isShiftDown()) {
delta *= 10;
}
switch (keyCode) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_DOWN:
axis.setRange(axis.getLowerBound()-delta,axis.getUpperBound()-delta);
break;
case KeyEvent.VK_UP:
case KeyEvent.VK_RIGHT:
axis.setRange(axis.getLowerBound()+delta,axis.getUpperBound()+delta);
break;
}
}
/*
* @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
*/
public void keyTyped(KeyEvent e) {}
/*
* @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
*/
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_CONTROL)
chartPanel.setCursor(ARROW);
}
public void mouseDragged(MouseEvent mouseEvent) {
if(!mouseEvent.isControlDown())
return;
if(oldx > -1 && oldy > -1){
ValueAxis domAxis = getPlotAxis(chartPanel.getChart(),!axesSwaped);
ValueAxis rngAxis = getPlotAxis(chartPanel.getChart(),axesSwaped);
int xdif = mouseEvent.getX() - oldx;
int ydif = mouseEvent.getY() - oldy;
final Rectangle2D scaledDataArea = chartPanel.getScreenDataArea();
double xdelta = (double)xdif * domAxis.getRange().getLength() / (scaledDataArea.getWidth());
double ydelta = (double)ydif * rngAxis.getRange().getLength() / (scaledDataArea.getHeight());
domAxis.setRange(domAxis.getLowerBound()-xdelta,domAxis.getUpperBound()-xdelta);
rngAxis.setRange(rngAxis.getLowerBound()+ydelta,rngAxis.getUpperBound()+ydelta);
}
oldx = mouseEvent.getX();
oldy = mouseEvent.getY();
}
public void mouseMoved(MouseEvent mouseEvent) {
if(mouseEvent.isControlDown()){
final Rectangle2D scaledDataArea = chartPanel.getScreenDataArea();
if(mouseEvent.getX() > (int)scaledDataArea.getX() &&
mouseEvent.getY() > (int)scaledDataArea.getY() &&
mouseEvent.getX() < (int)scaledDataArea.getX()+scaledDataArea.getWidth() &&
mouseEvent.getY() < (int)scaledDataArea.getY()+scaledDataArea.getHeight())
chartPanel.setCursor(MOVE);
else
chartPanel.setCursor(ARROW);
}
}
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 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;
}
}
But the graphs still want to use the default zooming/whatever behavior when panning around. So you need to do something like this to make the panel do nothing onDrag when the control key is pressed:
Code: Select all
/*
NOTE: this will NOT work when compiling with java 1.5.
You have to do some generics magic because of the
getListeners method thats overriden in ChartPanel. I
have no idea how to circumvent this. If you figure it out
let me know.
*/
private class CtrlChartPanel extends ChartPanel{
public CtrlChartPanel(JFreeChart chart) {
super(chart);
}
public void mouseDragged(MouseEvent e) {
if(!e.isControlDown())
super.mouseDragged(e);
}
}
Then the init code becomes:
Code: Select all
ChartPanel cpanel = new CtrlChartPanel(chart);
controller = new ChartPanelShiftController(cpanel);
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventDispatcher(new KeyEventDispatcher() {
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
controller.keyPressed(e);
}
else if (e.getID() == KeyEvent.KEY_RELEASED) {
controller.keyReleased(e);
}
return false;
}
});
cpanel.addMouseListener(controller);
cpanel.addMouseMotionListener(controller);
This may produce choppy results when using large datasets. It was slightly choppy on my PoC with ~13000 data points if I moved quickly for a good amount of time (talk about objective evidence, eh?

.
If I left something out, let me know.
Also, I left out the website in the header comments cause the forum wouldnt let me post urls as I am a n00b.
Ben