The Code
Unlike my previous Swing applets, the code for this applet consumes only one file. JavaFX gets a lot done in only a few lines of code. I'll run through the highlights of the code. The complete source file is available to download.
First, constants and variables are declared:
def SCALING_INCREMENT: Number = 0.1; def MINIMUM_SCALE: Number = 1.0; var mouseX: Number; var mouseY: Number; var scale: Number = 1.0; var chartPanel = new ChartPanel(createChart()); chartPanel.setPreferredSize(new Dimension(466, 288)); var chartComponent = SwingComponent.wrap(chartPanel);The ChartPanel is declared just as in Swing, but with type var. The key to getting the Swing ChartPanel component into JavaFX is wrapping it in the SwingComponent.wrap() method. This wrapped component will be the only content in a Group, which is the only content of the Scene as shown here:
Stage { title: "JFreeChart in JavaFX" scene: Scene { content: [ Group { content: { chartComponent } transforms: Scale { // Bind pivot point to mouse position so // zooming is always centered at the pointer pivotX: bind mouseX pivotY: bind mouseY x: bind scale y: bind scale } onMouseMoved: function( me: MouseEvent ) { updateMousePosition(me); } onMouseWheelMoved: function( we: MouseEvent ) { updateMousePosition(we); updateScale(we); } onMouseEntered: function( me: MouseEvent ) { windowsHack(); } } ] } }The power of JavaFX is revealed in the bindings within the transforms variable of the Group. These four bindings update the scale of the chart whenever the variable scale changes. Scaling is centered about a pivot point which is the current pointer position. So zooming is always centered at the mouse pointer.
There are two mouse handlers that update the bindings. The onMouseMoved and onMouseWheelMoved handlers call appropriate functions to maintain the mouse position and scaling factor variables. These functions are:
function updateMousePosition(me : MouseEvent) { mouseX = me.x; mouseY = me.y; } function updateScale(we: MouseEvent) : Void { var rotation = we.wheelRotation; // Control-Shift zoom-out scales back to MINIMUM_SCALE if (we.controlDown and we.shiftDown and rotation > 0) { scale = MINIMUM_SCALE; return; } var newScale = scale - (rotation * SCALING_INCREMENT); // Don't allow scaling below MINIMUM_SCALE if (newScale < MINIMUM_SCALE) { scale = MINIMUM_SCALE; return; } scale = newScale; }The mouse coordinates mouseX and mouseY are kept up-to-date in updateMousePosition(). The updateScale() function adjusts the scaling factor according to the sign and number of mouse wheel rotation units. If the Control and Shift key modifiers are enabled on the mouse event, scale is reset to its minimum value. A check ensures that less-than-unity scaling is not allowed.
Unfortunately, JavaFX applets are not immune to the annoying Windows behavior that plagued my previous applets. When the applet is first displayed on a page, it won't listen to mouse wheel events. But if a tooltip or popup menu is displayed and then hidden, mouse wheel events become active. Here is the hack to get this working:
function windowsHack() { // simulate right-mouse click to bring-up popup menu chartPanel.mousePressed(new java.awt.event.MouseEvent(chartPanel, 0, 0, java.awt.event.InputEvent.BUTTON3_DOWN_MASK, 0, 0, 1, true)); // immediately hide the popup menu chartPanel.getPopupMenu().setVisible(false); }The onMouseEntered function calls the above method the first time the mouse hovers over the applet. A right-click is simulated and a popup menu is shown and immediately hidden. This hack only fixes the situation when the applet is first displayed in the browser. If the applet is dragged out of the browser and then returned to the browser, the user will have to manually right-click and dismiss the popup menu in order to restore correct mouse wheel behavior.
JavaFX
Unlike my previous applets, this applet does not require Java SE 6. I do not use JXLayer or any other nifty Swing libraries; therefore, it will run on J2SE 5.0. This is the latest version of Java that will run applets within a web browser on my 32-bit Intel Mac, so testing is much easier than before. Performance does suffer on Java versions prior to SE 6 update 10, but not significantly due to the simplicity of this applet.
As a first impression, I'm quite pleased with the ability to get so much out of so few lines of code. It is nice not having to write listeners and just letting the bindings take care of everything. I look forward to working with JavaFX on future projects.
Resources
To build the applet, you will need these: