A brief charting example
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.