package prefuse.controls; import java.awt.Cursor; import java.awt.Point; import java.awt.event.MouseEvent; import javax.swing.SwingUtilities; import prefuse.Display; import prefuse.activity.Activity; import prefuse.activity.SlowInSlowOutPacer; /** * <p>Allows users to pan over a display such that the display zooms in and * out proportionally to how fast the pan is performed.</p> * * <p>The algorithm used is that of Takeo Igarishi and Ken Hinckley in their * research paper * <a href="http://citeseer.ist.psu.edu/igarashi00speeddependent.html"> * Speed-dependent Automatic Zooming for Browsing Large Documents</a>, * UIST 2000.</p> * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class ZoomingPanControl extends ControlAdapter { private boolean repaint = true, started = false; private Point mouseDown, mouseCur, mouseUp; private int dx, dy; private double d = 0; private double v0 = 75.0, d0 = 50, d1 = 400, s0 = .1; private UpdateActivity update = new UpdateActivity(); private FinishActivity finish = new FinishActivity(); /** * Create a new ZoomingPanControl. */ public ZoomingPanControl() { this(true); } /** * Create a new ZoomingPanControl. * @param repaint true if repaint requests should be issued while * panning and zooming. false if repaint requests will come from * elsewhere (e.g., a continuously running action). */ public ZoomingPanControl(boolean repaint) { this.repaint = repaint; } /** * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ public void mousePressed(MouseEvent e) { if ( SwingUtilities.isLeftMouseButton(e) ) { Display display = (Display)e.getComponent(); display.setCursor( Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); mouseDown = e.getPoint(); } } /** * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent) */ public void mouseDragged(MouseEvent e) { if ( SwingUtilities.isLeftMouseButton(e) ) { mouseCur = e.getPoint(); dx = mouseCur.x - mouseDown.x; dy = mouseCur.y - mouseDown.y; d = Math.sqrt(dx*dx + dy*dy); if ( !started ) { Display display = (Display)e.getComponent(); update.setDisplay(display); update.run(); } } } /** * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ public void mouseReleased(MouseEvent e) { if ( SwingUtilities.isLeftMouseButton(e) ) { update.cancel(); started = false; Display display = (Display)e.getComponent(); mouseUp = e.getPoint(); finish.setDisplay(display); finish.run(); display.setCursor(Cursor.getDefaultCursor()); } } private class UpdateActivity extends Activity { private Display display; private long lastTime = 0; public UpdateActivity() { super(-1,15,0); } public void setDisplay(Display display) { this.display = display; } protected void run(long elapsedTime) { double sx = display.getTransform().getScaleX(); double s, v; if ( d <= d0 ) { s = 1.0; v = v0*(d/d0); } else { s = ( d >= d1 ? s0 : Math.pow(s0, (d-d0)/(d1-d0)) ); v = v0; } s = s/sx; double dd = (v*(elapsedTime-lastTime))/1000; lastTime = elapsedTime; double deltaX = -dd*dx/d; double deltaY = -dd*dy/d; display.pan(deltaX,deltaY); if (s != 1.0) display.zoom(mouseCur, s); if ( repaint ) display.repaint(); } } // end of class UpdateActivity private class FinishActivity extends Activity { private Display display; private double scale; public FinishActivity() { super(1500,15,0); setPacingFunction(new SlowInSlowOutPacer()); } public void setDisplay(Display display) { this.display = display; this.scale = display.getTransform().getScaleX(); double z = (scale<1.0 ? 1/scale : scale); setDuration((long)(500+500*Math.log(1+z))); } protected void run(long elapsedTime) { double f = getPace(elapsedTime); double s = display.getTransform().getScaleX(); double z = (f + (1-f)*scale)/s; display.zoom(mouseUp,z); if ( repaint ) display.repaint(); } } // end of class FinishActivity } // end of class ZoomingPanControl