With JavaFX 1.0 having been released just a few days ago, I'm pleased that we're observing more JavaFX applications "in the wild". Here's one that I became aware of recently, named Sapphire, developed by The Standards Company.
According to the website in which you can launch this JavaFX application:
"Sapphire is an advanced classroom observation tool. It provides an easy-to-use interface, multiple platform deployments, and interchangeable observation forms which can be customized to fit most any situation. The Community Edition [which is the version that you can try out from the link above] is a demonstration version requested by the Oklahoma State Department of Education and configured to complement the "Time on Task" observation form released in October 2008."
Some JavaFX Technical Notes from Ben Jones of The Standards Company: Real-time Charting in JavaFX
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 TimeDisplayChart extends CustomNode {
public var width:Integer;
public var height:Integer;
public var zoneSeconds:Integer;
public var zoneWidth:Integer;
public var background:Color;
public var timer:SapphireClock;
public var timeMark:Integer = 0;
public var currentSegment:Number = 0;
public var displayTicker:Integer;
public var displayBlocks:TimeOnTaskBlock[];
public var onColor:Color = Color.#96ff96;
public var offColor:Color = Color.#ff9696;
public override function create(): Node {
return Group {
content: [
Group {
content:[
Rectangle {
width: width
height: height
fill: background
arcWidth:10
arcHeight:10
stroke:Color.WHITE
},
Group {
translateX:2
content:[
Group {
content: bind displayBlocks
}
]
},
Group {
translateX:0
content:[
TimeOnTaskBlock {
aboveColor:bind onColor
belowColor:bind offColor
seconds: bind timer.ticker - timeMark
startingMark: bind timeMark
segment:bind currentSegment
}
]
},
Line {
startX:0
startY: height / 2
endX: zoneWidth
endY: height / 2
strokeWidth: 1
stroke: Color.BLACK
},
Group {
content: bind
for (i in [1..zoneSeconds - 2] where (i mod 300 == 0)) {
Line {
startX: bind zoneWidth * i / zoneSeconds
startY:0
endX: bind zoneWidth * i / zoneSeconds
endY: bind height
strokeWidth: 1
stroke: Color.BLACK
}
}
}
]
clip: Rectangle {
x:-1,
y:-1
width: bind width + 4
height: bind height + 4
}
},
Group {
translateX: 0
translateY: bind height + 5
content: bind
for (i in [1..zoneSeconds] where (i mod 300 == 0)) {
Text {
textAlignment: TextAlignment.RIGHT
textOrigin: TextOrigin.TOP
fill:Color.WHITE
font: Font {
size: 12
}
x: (zoneWidth * i / zoneSeconds) - 7
content: "{i / 60}"
}
}
}
]
};
}
}
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.
JavaFX in the Enterprise
The technical notes that Ben Jones supplied above demonstrate that graphing real-time data is very achievable in JavaFX. This is one of the capabilities required by many enterprise applications, so I'd like to see an open source initiative that provides a library of JavaFX graphs (that have a contemporary RIA look and feel, of course).
Anyway, play with Sapphire, and please give The Standards Company your feedback on this very specialized JavaFX application. Also, if you have any JavaFX applications in the wild, please let me know at jim.weaver [at] javafxpert.com. At the moment I'm particularly interested in showcasing JavaFX apps that communicate with a server, such as enterprise applications and mashups.
Regards,
Jim Weaver
JavaFXpert.com
I agree with the author and I think the same.
Of course there are some things that cannot be explained but there should always be room for improvement.
Posted by: Hair Loss Products For Men | June 21, 2010 at 10:02 AM
I needed this code some days ago but then I couldn't find it. Damn, and now I just searching through internet found it here.
Anyway, hope to use it later.
Posted by: Acne Scar Cream | June 21, 2010 at 10:00 AM
really nice application. But i could't see the layout behaviour in this application. if you give the option to resize the window i could able see how you effectively used java layouts. Onething i am getting confused is that, i could't see any javax application that uses layout :)
Posted by: Prince | February 11, 2009 at 02:11 AM
Lame interface... see above comments
Posted by: Javagator | January 05, 2009 at 02:53 PM