Wednesday, March 4, 2009

Scalable JFreeChart JavaFX Applet

A previous post demonstrated an applet written in Swing that showed how to scale a JFreeChart using the mouse wheel. That applet lacked the ability to scroll and explore the chart when zoomed-in. This post presents a similar applet, but with the added scrolling ability. Rather than using Swing exclusively, the applet below harnesses the power of JavaFX. The mouse wheel adjusts the zoom level. When zoomed in, the chart moves continuously with the pointer, allowing the user to scroll to any part of the chart simply by moving the mouse. Holding down the Control and Shift keys while moving the scroll wheel down returns the chart to unity scale. Mac users may have to click the “Trust” or “Allow” button in order to use the applet. Less-paranoid operating systems like Linux and Windows do not require any security confirmations. Scaling may not work in Safari on a Mac.

Note: Windows users may need to right-click on the chart to enable the mouse wheel adjustments. For some reason, the popup menu that appears after a right-click causes the applet to start listening to mouse wheel events. This is most noticeable when dragging the applet out of the browser and then closing the dragged-out applet and returning to the browser window. This problem does not occur on Mac or Linux.

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:
  1. My code
  2. JFreeChart library (I use version 1.0.12)
  3. JFree JCommon library (I use version 1.0.15)



Friday, February 27, 2009

SyntaxHighlighter in Blogger

If you want to display well-formatted and easy-to-read source code in your Blogger blog, you may want to used Alex Gorbatchev's SyntaxHighlighter. Here is a sample of formatted Java:
public @interface Politics {}

@Override
public final void liveInAmerica(boolean insane) {
  Party<Fool>    republican = new Party<Fool>(false);
  // Party like it's 1999!
  Party<Dude> nonRepublican = new Party<Dude>(true);
  while(isBush()) {
    suffer(13);
  }
  if(insane) {
    elect(republican);
  }
  else {
    System.out.println("Free at last!");
    elect(nonRepublican);
    beHappy(7);
  }
}
In order to display my Java code, I perform the following three steps:
  1. Add CSS styles and a JavaScript to the Blogger template.
  2. Make source code more HTML friendly.
  3. Wrap code in <pre> tag when posting.


Step 1: Modify Template
In Blogger's Layout tab, select the “Edit HTML” sub-tab. Locate </head> in the text area. Right BEFORE this tag (before the <), paste the following code.
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/> <link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/> <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'/> <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'/> <script type='text/javascript'> SyntaxHighlighter.config.clipboardSwf = &#39;http://alexgorbatchev.com/pub/sh/current/scripts/clipboard.swf&#39;; SyntaxHighlighter.config.bloggerMode = true; SyntaxHighlighter.all(); </script>
Click the SAVE TEMPLATE button. If no errors occurred, you may navigate away from the Layout tab. This step only needs to be performed once. For each subsequent blog post, you only need to do the remaining steps to get highlighted source code.


Step 2: Clean the Code
Java code that uses generics contains lots of less-than (<) and greater-than (>) symbols. These symbols confuse HTML renderers. So the generified Java must be converted to a more friendly format. That is, each less-than and greater-than symbol needs to be replaced with “&lt;” and “&gt;” (without the quotation marks,) respectively. I use an online HTML encoder to quickly format the code. There are many other tools that achieve the same result.


Step 3: Wrap Code in <pre> Tag
Precede each block of Java code with the following:
<pre class="brush: java">
Finish the code block with:
</pre>


Blank Lines in IE
In order for Blogger to display blank lines within highlighted source code on Internet Explorer, one additional setting must be adjusted. Under the Settings tab, in the Formatting sub-tab, the option “Convert line breaks” must be changed to “No.” With this setting, hard-returns typed in the WYSIWYG post editor will not be converted to HTML line break tags.

WARNING: changing this option from yes to no will reformat all posts. If you don't want to spend forever re-entering all line breaks in your existing posts, then you may want to live without blank lines in your highlighted source code. But if you only have a few posts, then it is worth the reformatting exercise. Blank lines can make long source code listings much more readable. Keep in mind that the WYSIWYG “Compose” editor will be basically useless after changing this option to “No.” All line breaks will have to be entered as <br /> tags in the “Edit Html” editor, or a third-party blog editor.


Other Languages
The brush option in the <pre> tag tells SyntaxHighlighter which language to use. If you want to post source code in other languages, you would need to change the brush type in the <pre> tag and add the appropriate language script to your Blogger template. I only use Java, so I only added the Java script. It is a good idea to add as few JavaScripts as possible to your template in order to minimize page load times. You may choose from a complete list of supported languages and add the appropriate script line to your template for each desired language. For example, to add C++ support to your blog, add the following line to your template:
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js' type='text/javascript'/>


SyntaxHighlighter Usage
In order for the “copy to clipboard” feature to work, the blog viewer must have the Flash plugin installed. Without the appropriate Flash plugin, a blank area will be displayed where the clipboard button is usually located.

If you wish to host the SyntaxHighlighter scripts on your own site and not rely on the developer's server, you can find more information here. It is preferred that you host SyntaxHighlighter on your own server in order to conserve the developer's hosting bandwidth.