December 20, 2008

A brief charting example

Filed under: JavaFX — Ben Jones @ 10:21 pm

This is the technical writeup I provided to Jim Weaver for his updated blog with some editorial changes.

One of the features of JavaFX that makes it attractive to real-time application developers is its ability to tie graphical elements to models through the binding mechanism. This mechanism makes it possible for the current state of the model to be reflected in the interface without the need to create any type of polling system.

In our Sapphire application, we collect real-time data from classroom observations. The observer records classroom events by pressing various buttons. These events are displayed in several dashboard type displays which are actively tied to supporting models. One of these displays in a time lime of events created by a series of colored rectangles. The size, placement and color of the rectangles represent different relationships in the event data. Every time an event is pressed, the data changes and the bound display automatically updates.

To achieve this, you only need three things: a data model, a display element, and an event driver of some type. Let’s take a look at one of the visual building blocks, the TimeOnTaskBlock class:

public class TimeOnTaskBlock extends CustomNode {

public var aboveColor:Color = Color.GREEN;
public var belowColor:Color = Color.RED;

public var seconds:Integer;
public var startingMark:Integer;
public var zoneWidth:Integer = 600;
public var zoneSeconds:Integer = 3600;
public var height:Integer = 100;
public var maxSegments:Number = 5;
public var segment:Number = 0;
public var x:Integer;
public var scale:Number = 0.95;

  public override function create(): Node {
    return Group {

      var maxDisplayHeight:Number = height/2;

      content: [
        Rectangle {
          x: bind zoneWidth * startingMark / zoneSeconds;
          y: bind height/2 - scale * ((maxDisplayHeight) * segment/maxSegments)
          width: bind zoneWidth * seconds / zoneSeconds;
          height: bind scale * (maxDisplayHeight * segment/maxSegments)
          fill: bind aboveColor
          strokeWidth:0
        },
        Rectangle {
          x: bind zoneWidth * startingMark / zoneSeconds;
          y: bind height / 2
          width: bind zoneWidth * seconds / zoneSeconds;
          height: bind scale * (maxDisplayHeight - (maxDisplayHeight * segment/maxSegments))
          fill: bind belowColor
          strokeWidth:0
        }
      ]
      effect: Lighting {
        light: DistantLight { azimuth: 225 elevation: 60 }
        surfaceScale: 2
      }
    };
  }
}

This custom node represents what will be displayed. It is a pair of scaled rectangles stacked on top of each other. The various attributes define shape, size, and placement. Notice the use of the the DistantLight effect to give the rectangles in the graph some depth. The next building block up from this is another custom node that handles the visual orchestration of the time blocks nodes. The DisplayTimeChart node is effectively the dashboard element being displayed. It has a data member holding a sequence of TimeOnTaskBlock nodes. Let’s take a peek at this node:

public class TimeOnTaskBlock extends CustomNode {

public var aboveColor:Color = Color.GREEN;
public var belowColor:Color = Color.RED;

public var seconds:Integer;
public var startingMark:Integer;
public var zoneWidth:Integer = 600;
public var zoneSeconds:Integer = 3600;
public var height:Integer = 100;
public var maxSegments:Number = 5;
public var segment:Number = 0;
public var x:Integer;
public var scale:Number = 0.95;

  public override function create(): Node {
    return Group {

      var maxDisplayHeight:Number = height/2;

      content: [
        Rectangle {
          x: bind zoneWidth * startingMark / zoneSeconds;
          y: bind height/2 - scale * ((maxDisplayHeight) * segment/maxSegments)
          width: bind zoneWidth * seconds / zoneSeconds;
          height: bind scale * (maxDisplayHeight * segment/maxSegments)
          fill: bind aboveColor
          strokeWidth:0
        },
        Rectangle {
          x: bind zoneWidth * startingMark / zoneSeconds;
          y: bind height / 2
          width: bind zoneWidth * seconds / zoneSeconds;
          height: bind scale * (maxDisplayHeight - (maxDisplayHeight * segment/maxSegments))
          fill: bind belowColor
          strokeWidth:0
        }
      ]
      effect: Lighting {
        light: DistantLight { azimuth: 225 elevation: 60 }
        surfaceScale: 2
      }
    };
  }
}

The TimeDisplayChart serves a couple of purposes. First it builds the overall dashboard element. This defines the usual display space, necessary labeling and element placements. Please note two aspects of this class: the group where its content is bound to the displayBlocks sequence and the second group composed of a solo TimeOnTaskBlock. By binding the content of a group to a data source, any changes in the data source will be made available to the group automatically. This effectively produces a self-updating display since we bound a displayable node. The second group which is a single instance of the TimeOnTaskBlock serves another purpose. It covers the span of time since the last data change and the current time. Since the data model stores only past events, we need a means of showing data yet to be stored. It’s real-time after all.

To place all of this into an application, an instance of TimeDisplayChart is placed into a group as follows:

Group {
  translateY:310
  translateX:10
  content:[
    TimeDisplayChart{
      width:600
      height:100
      zoneSeconds:3600
      zoneWidth:600
      background:Color.#969696
      timer: bind timer
      displayTicker: bind displayTicker
      currentSegment: bind appState.currentSegment
      timeMark: bind appState.currentTimeMark
      onColor: bind appState.onColor
      offColor:bind appState.offColor
      displayBlocks: bind displayblocks
    }
  ]
}

Here we can see the various attributes of the custom node being bound to different data sources and conditions that exit within the larger context of the application. The bindings create an active communication channel between application state data and the graphical elements. It’s convenient, responsive, and easy to manage during the development process.

You can find a wealth of information about JavaFX and what people are doing with this RIA technology over at Jim Weaver’s JavaFX blog and of course at Sun’s JavaFX.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress