The Code
The scaling is an extension of the generic ZoomUI by Piet Blok, which is built on JXLayer. Specifically, I extend two classes. My WheelZoomableUI class extends ZoomUI, and WheelZoomablePort extends ZoomPort. The WheelZoomableUI class contains only one method that overrides transformMouseWheelEvent(...). That method calls the appropriate scaling algorithms in the corresponding WheelZoomablePort class.
The scaling is performed by applying appropriate scaling and translation transformations to the AffineTransform field in the ZoomPort class. When zooming in, the operation is quite simple. First, the AffineTransform.scale(...) transformation applies the scaling. Then, the cursor position in the view is converted to the unscaled coordinate system. This position is then used to translate the view so that the zoomed-in point is centered at the original mouse cursor position. This simply means that the content under the cursor remains in the same position while scaling.
Zooming out requires one additional step. After the above scaling and translation have been applied, it is possible that part of the chart has fallen off the edge of the view. Another way of describing this is that the user zoomed-out so far that the chart has slid over toward the cursor. This results in a blank area in the applet window. In order to avoid this blank area, the chart needs to be slid back to cover the blank area. So a corrective translation is applied to ensure that no part of the view is empty.
The minimum scaling allowed is 1.0, meaning that one cannot make the chart smaller than the applet panel size. So no blank areas are possible in the applet. If you observe my magnifier applet, it is possible to zoom out to a scaling factor less than 1.0. This creates a blue area within the magnifying glass that shows the area outside the chart. This blue area corresponds to the empty area described above that necessitates the corrective translation when zooming out.
Constructing the applet is quite simple, as seen here:
public JXLayerScaleDemo() { super(); XYDataset dataset = createDataset(); JFreeChart chart = createChart(dataset); chartPanel = new ChartPanel(chart); final WheelZoomablePort zoomPort = new WheelZoomablePort(); final WheelZoomableUI zoomUI = new WheelZoomableUI(); zoomPort.setView(chartPanel); zoomPort.setOpaque(true); final JXLayer<ZoomPort> layer = new JXLayer<ZoomPort>(zoomPort, zoomUI); chartPanel.setPreferredSize(new java.awt.Dimension(480, 260)); setContentPane(layer); }Instances of the WheelZoomablePort and WheelZoomableUI are created from the default constructors. The view of the port is set to the chart panel. Then the port and UI are sent to the JXLayer constructor. The JXLayer is then set as the main content.
Future Work
As mentioned above, the wheel zoomer behaves similarly to the Mac OS X Universal Access Zoom, but with one important omission. When zoomed-in, the user cannot pan and explore the hidden parts of the panel. Luckily, JXLayer already has a nice UI to handle panning and scrolling called MouseScrollableUI. I hope to add this functionality in the future.
From a coding perspective, it is not really necessary to subclass ZoomUI and ZoomPort. It may be better to simply add a MouseWheelListener to the ZoomPort and override the mouseWheelMoved method. In fact, the test classes in the ZoomUI and the new TransformUI packages do just this to achieve similar scaling behavior without subclassing.
Resources
To build the applet, you will need these:
- My code
- JXLayer library (I use version 3.0)
- JFreeChart library (I use version 1.0.12)
- JFree JCommon library (I use version 1.0.15)