What follows is the highlighted source code with comments. You can also download this file directly: TransformManager.java.
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package graphics; import java.awt.Component; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; /** * * @author pat */ public class TransformManager { private class Listener implements ComponentListener { public Listener() { } @Override public void componentHidden(ComponentEvent ce) { } @Override public void componentMoved(ComponentEvent ce) { } @Override public void componentResized(ComponentEvent ce) { updateTransform(); } @Override public void componentShown(ComponentEvent ce) { } } //// FUNCTIONS RELATED TO THE BOUNDING BOX AND COORDINATE CHANGES // We guarantee that this box will be displayed. private Rectangle2D display_box; // This affine transformation converts from math coordinates to screen coordinates. private AffineTransform current_transform = new AffineTransform(); private Component c; private TransformChangedListener listener = null; public TransformManager(Component c, Rectangle2D initial_display_box) { this.c = c; display_box = initial_display_box; c.addComponentListener(new Listener()); updateTransform(); } public void setListener(TransformChangedListener tcl) { listener = tcl; } public Rectangle2D getDisplayBox() { return display_box; } public void setDisplayBox(Rectangle2D r) { display_box = r; updateTransform(); } private void updateTransform() { /* * The component has pixels whose x-coordinates are numbered 0 to * getWidth()-1 as we move rightward. The y-coordinates of these pixels * increases from 0 to getHeight()-1 as we move downward. * * We will return the transformation which takes the bounding box for * our curve into the rectangle representing coordinates for our * component on the screen. To move this box into the screen we use the * following steps: 1. We translate our box, moving the center of our * box to the origin. 2. We scale the box by a constant so that the * image of our box has width and height less than or equal to the width * and height of this component. 3. We negate the y-coordinate, because * in mathematics the y-coordinate increases as we move upward. 4. We * translate the origin so that it is moved to the center of the * component. * * These steps are carried out below. */ // Construct a transformation which translates the plane moving the center of // our box to the origin. AffineTransform transform = AffineTransform.getTranslateInstance(-display_box.getCenterX(), -display_box.getCenterY()); // The number scale is the minimal ratio of screen dimensions to bounding box dimensions. double scale; if ((display_box.getWidth() != 0) && (c.getWidth() != 0)) { if ((display_box.getHeight() != 0) && (c.getHeight() != 0)) { scale = Math.min(c.getWidth() / display_box.getWidth(), c.getHeight() / display_box.getHeight()); } else { scale = c.getWidth() / display_box.getWidth(); } } else { if ((display_box.getHeight() != 0) && (c.getHeight() != 0)) { scale = c.getHeight() / display_box.getHeight(); } else { scale = 1; } } // The following line post-composes by scaling the plane by the number "scale". // Because we use the same constant in each coordinate, we preserve the aspect ratio. transform.preConcatenate(AffineTransform.getScaleInstance(scale, scale)); // This has the effect of negating the y-coordinate: transform.preConcatenate(AffineTransform.getScaleInstance(1, -1)); // Now translate the origin until it is centered in the component. transform.preConcatenate( AffineTransform.getTranslateInstance(c.getWidth() / 2, c.getHeight() / 2)); current_transform = transform; if (listener != null) { listener.transformChanged(); } } /** * Convert the point from math coordinates into screen coordinates. */ public Point2D toScreenCoordinates(Point2D p) { // Convert z to a Point2D, apply the affine transform and return the result. return current_transform.transform(p, null); } /** * Convert the point from screen coordinates into math coordinates. */ public Point2D toMathCoordinates(Point2D p) { try { return current_transform.inverseTransform(p, null); } catch (NoninvertibleTransformException e) { // We would enter here if the current_transform represents a matrix // which is not invertible. This should never happen, because // we constructed our transformation as a composition of // invertible transformations. System.err.println("TransformManager failed to invert the " + "current_transform in the toMathCoordinates() method."); return new Point2D.Double(0, 0); } } /** * Return the current affine transformation which converts from math * coordinates to screen coordinates. */ public AffineTransform getTransform() { return current_transform; } /** * Rescale the box we are guaranteed to display by a multiplicative constant * via a dilation fixing the provided complex number. * * All calculations are done in math coordinates. * * @param scaling_constant A positive constant. * @param fixed_point Point fixed by the dilation. */ public void scale(double scaling_constant, Point2D fixed_point) { if (scaling_constant > 0) { setDisplayBox( new Rectangle2D.Double( scaling_constant*(display_box.getMinX()-fixed_point.getX())+fixed_point.getX(), scaling_constant*(display_box.getMinY()-fixed_point.getY())+fixed_point.getY(), scaling_constant*display_box.getWidth(), scaling_constant*display_box.getHeight() ) ); } } }