JMSL Chart Programmer’s Guide
Zoom
JMSL provides the functionality to implement a variety of zoom policies instead of implementing a single specific zoom policy. The following example shows how to build a zoom policy that draws a rubber-band box in a scatter plot and zooms both the x-axis and y-axis to correspond to the region selected. Zoom out is also implemented.
This code can be used as the basis of an implementation of other zoom policies. For example, with a function plot one might want to only change the x-axis in response to a zoom and leave the y-axis alone. In other situations it may be desirable to zoom in or out a fixed amount around the location of a mouse click.
Example
In the constructor:
*A scatter plot is created,
*Zoom items are added to the main menu,
*A MouseListener is added to the panel. A JFrameChart contains a number of components, including a JPanel in which the chart is drawn. It is important that the MouseListener be added to the component in which the chart is drawn, so that the MouseEvent coordinates are the same as the device coordinates of the chart.
A mouse press event starts the zoom. In response to the event a MouseMotionListener is added to the panel to listen for mouse drag events. The location of this initial event is also recorded.
A mouse drag event resizes the area of the zoom. In response, the old rubber-band box is erased and a new box is drawn. The new box has the initial mouse press location as one corner and this drag event location as the opposite corner. The location of this event is stored in (xLast,yLast).
A mouse release event completes the zoom. In response,
*The MouseMotionListener is removed from the panel,
*The existing Window attributes for the x-axis and y-axis are saved on a stack. They may be used later to zoom out.
*The new Window is computed. The Window is the range, in user coordinates, along an axis. The method Axis.mapDeviceToUser is used to map the device (pixel) coordinates to the user coordinate space. The new windows are constrained to be inside of the existing window.
*Autoscaling is turned off, so that the new Window values will be used without further modification.
*The chart is redrawn with the new windows.
The menu items, added by the constructor to the main menu, can be used to zoom out. The added menu items added "this" as an ActionListener, so the actionPerformed method is called when these menu items are selected. The actionPerformed method implements "Out" by popping previous Window attribute values off a stack. It implements "Restore" by re-enabling autoscaling.
View code file
 
import com.imsl.chart.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Stack;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
 
public class SampleZoom extends JFrameChart
implements MouseListener, MouseMotionListener, ActionListener {
 
private javax.swing.JPanel panel;
private AxisXY axis;
private int xFirst, yFirst;
private int xLast, yLast;
private Stack stack;
 
public SampleZoom() {
// create a scatter plot with some random points
Chart chart = getChart();
axis = new AxisXY(chart);
int n = 1000;
double x[] = new double[n];
double y[] = new double[n];
for (int k = 0; k < n; k++) {
x[k] = 10. * Math.random();
y[k] = 10. * Math.random();
}
Data data = new Data(axis, x, y);
data.setDataType(Data.DATA_TYPE_MARKER);
 
// add menu "Zoom" menu
JMenu jMenuZoom = new JMenu("Zoom");
jMenuZoom.setMnemonic('Z');
addMenuItem(jMenuZoom, "Out");
addMenuItem(jMenuZoom, "Restore");
getJMenuBar().add(jMenuZoom);
 
// save x-axis and y-axis Window attributes on this stack.
// they are used to zoom out one level.
stack = new Stack();
 
// listen for mouse press events
panel = getPanel();
panel.addMouseListener(this);
}
 
/**
* Add a menu item
*/
private void addMenuItem(JMenu jMenuZoom, String title) {
JMenuItem jMenuItem = new JMenuItem(title);
jMenuItem.setActionCommand(title);
jMenuItem.setMnemonic(title.charAt(0));
jMenuZoom.add(jMenuItem);
jMenuItem.addActionListener(this);
}
 
/**
* A mouse press starts the zoom
* Record location of this point and listen for drag (motion) events.
*/
public void mousePressed(MouseEvent event) {
xFirst = xLast = event.getX();
yFirst = yLast = event.getY();
panel.addMouseMotionListener(this);
}
 
/**
* A mouse release ends the zoom.
* Stop listening for drag events and update the
* Window attribute in the x and y axes.
*/
public void mouseReleased(MouseEvent event) {
panel.removeMouseMotionListener(this);
 
// ignore degenerate zooms
if (xFirst == xLast || yFirst == yLast) {
repaint();
return;
}
 
// get window and convert to user coordinates
double windowX[] = (double[]) axis.getAxisX().getWindow();
double windowY[] = (double[]) axis.getAxisY().getWindow();
 
// save the windows on the stack (for zoom out option)
stack.add(windowX);
stack.add(windowY);
 
// get user coordinate of left-upper corner of the box
// limit it to stay inside current window
double boxLU[] = new double[2];
int x = Math.min(xFirst, xLast);
int y = Math.min(yFirst, yLast);
axis.mapDeviceToUser(x, y, boxLU);
 
// get user coordinate of right-lower corner of the box
// limit it to stay inside current window
double boxRL[] = new double[2];
x = Math.max(xFirst, xLast);
y = Math.max(yFirst, yLast);
axis.mapDeviceToUser(x, y, boxRL);
 
// set axis window to range of rubber-band box
// and disable autoscale to force use of window settings
axis.setAutoscaleInput(ChartNode.AUTOSCALE_OFF);
double xa = Math.max(windowX[0], boxLU[0]);
double xb = Math.min(windowX[1], boxRL[0]);
double ya = Math.max(windowY[0], boxRL[1]);
double yb = Math.min(windowY[1], boxLU[1]);
axis.getAxisX().setWindow(xa, xb);
axis.getAxisY().setWindow(ya, yb);
 
// force redraw of the chart
repaint();
}
 
/**
* A drag event continues the zoom.
* Erase the old rubber-band box and draw a new one.
* Also keep track of the location of this event.
*/
public void mouseDragged(MouseEvent event) {
// erase previous rectangle
Graphics g = panel.getGraphics();
g.setXORMode(Color.cyan); // complement of drawing color red
drawBox(g);
 
// draw new rectangle
xLast = event.getX();
yLast = event.getY();
g.setPaintMode();
g.setColor(Color.red);
drawBox(g);
}
 
public void mouseMoved(MouseEvent event) {
}
 
public void mouseEntered(MouseEvent event) {
}
 
public void mouseClicked(MouseEvent event) {
}
 
public void mouseExited(MouseEvent event) {
}
 
/**
* Draw a box with (xFirst,yFirst) and (xLast,yLast) as its corners.
*/
private void drawBox(Graphics g) {
int x = Math.min(xFirst, xLast);
int y = Math.min(yFirst, yLast);
int w = Math.abs(xLast - xFirst);
int h = Math.abs(yLast - yFirst);
g.drawRect(x, y, w, h);
}
 
/**
* Handle menu item events.
* Command "Out" zooms out one level.
* Command "Restore" undoes all zooms.
*/
public void actionPerformed(ActionEvent actionEvent) {
String command = actionEvent.getActionCommand();
if (command.equals("Out")) {
try {
// zoom out by restoring window settings from the stack
axis.getAxisY().setWindow((double[]) stack.pop());
axis.getAxisX().setWindow((double[]) stack.pop());
} catch (java.util.EmptyStackException e) {
// no more levels to zoom out
// restore original setting by turning on autoscale
axis.setAutoscaleInput(ChartNode.AUTOSCALE_DATA);
}
} else if (command.equals("Restore")) {
// restore original setting by turning on autoscale
// an empty stack
axis.setAutoscaleInput(ChartNode.AUTOSCALE_DATA);
stack.empty();
}
// force redraw of the chart
repaint();
}
 
public static void main(String argv[]) {
new SampleZoom().setVisible(true);
}
}