Recently I had been given several GB of raw data from our two most recent scientific drilling expeditions in Antarctica. This data needs a fair amount of quality control processing to turn it into a usable datasets for the scientists. To do this, I needed to write a tool for the drillers to interactively plot and explore the data to determine regions of interest. Given the recent buzz about Griffon, I thought I'd give it a try.
I started by downloading and installing Griffon. Once I had everything setup, I created an app:
griffon create-app DrillingAnalytics
If you've done any Grails development, this will be a familiar idiom to you. The results of this command is a straightforward directory structure, focused around the MVC pattern. You'll recognize directories for
models
, views
, and controllers
.My next step was to flesh out my model. When you create an app, Griffon automatically creates a model class called
${app.name}Model
(DrillingAnalyticsModel for me) in the griffon-app/models
directory. The main purpose of my app is to plot time series data so I defined two fields, startDate
and endDate
in my model:
import groovy.beans.Bindable
class DrillingAnalyticsModel {
@Bindable String startDate = "2006-11-07 00:00"
@Bindable String endDate = "2006-11-08 00:00"
}
You'll notice the @Bindable
annotations on these fields. These fields will tie to these components in the UI, and the @Bindable
annotation will automatically take care of keeping the UI in sync with the model via PropertyChangeEvent
s.
The model class is also where you can put other fields to maintain applications state:
def plot = new CombinedDomainXYPlot(new DateAxis())
def subplots = []
def chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false)
With the model sorted out, I moved on to developing the view. As with model, Griffon creates a ${app.name}View
class for you in the griffon-app/views
directory. Griffon puts the full power of SwingBuilder, SwingXBuilder, and GraphicsBuilder (with more on the way) at your fingertips for developing the UI.
I spent the majority of my time on the UI. It was a seemingly endless cycle of tweaking the code and testing with griffon run-app
to get it to look the way I wanted. This is no knock on Griffon; writing Java UIs, especially by hand, just plain sucks.
After far too long trying to get the standard Java layout managers to do what I want, I did myself a favor and downloaded MigLayout. Despite not being built into SwingBuilder, MigLayout integrates nicely with SwingBuilder:
application(title:'Drilling Analytics', pack:true, locationByPlatform:true) {
panel(layout: new MigLayout('fill')) {
// chart panel
widget(chartPanel, constraints:'span, grow')
// our runs and time
panel(layout: new MigLayout('fill'), border: titledBorder('Time'), constraints: 'grow 100 1') {
scrollPane(constraints:'span 3 2, growx, h 75px') {
runs = list(listData: model.mis.keySet().toArray())
}
label('Start:', constraints: 'right, gapbefore 50px')
textField(id:"startDate", text: bind { model.startDate }, action: plotAction, constraints:'wrap, right, growx')
label('End:', constraints: 'right, top')
textField(id:"endDate", text: bind { model.endDate }, action: plotAction, constraints:'wrap, right, top, growx')
label("+/-", constraints: 'right')
textField(id:"padding", text: "30", constraints: 'growx')
label("min")
button(action: plotAction, constraints:'span 2, bottom, right')
}
// our plots panel
panel(layout: new MigLayout(), border: titledBorder('Plots'), constraints: 'grow 100 1') {
model.data.each { id, map ->
checkBox(id: id, selected: false, action: plotAction, text: map.title, constraints:'wrap')
}
}
}
}
SwingBuilder gets rid of all the boilerplate code and MigLayout makes it possible to code decent Java UIs by hand:
We've covered the Model and the View, now it's time to focus on the Controller. The controller mediates between the model and view. It contains all of the logic for handling events from the UI and manipulating the model.
One common pattern in the existing Griffon examples is the use of Swing Action
objects to trigger actions from the UI. My UI was pretty simple so I could reuse a single action on all of the components to refresh the plot:
actions {
action(id: 'plotAction',
name: 'Update',
closure: controller.plot)
}
I put this code in my DrillingAnalyticsView
class, but it could just as easily be defined in its own file and imported into the view via the build()
method. You'll notice that I give the action an id--plotAction
--which I use to reference it from the components:
button(action: plotAction, constraints:'span 2, bottom, right')
You can also see that the action just delegates to the controller.plot
closure. This is convenient because it keeps all of the logic in one place and the controller has access to both the model and view. The actual code of the controller.plot
is unremarkable. The big consideration is to properly manage your threading. Don't do long running actions in the EDT as it will freeze the UI, and don't update the UI from outside the EDT as Swing is not thread safe. Andres Almiray has a good description of how Griffon makes this easy.
Since my app is fairly niche (I doubt there's many of you visualizing drilling data), I'm not going to post the whole source code here. However, I want to point out that the source code consists of just 327 lines of code, and that's including blank lines and comments! The bulk of that code is the logic to query the database and update the JFreeChart plots. This truly demonstrates how simple and easy it is to build an app with Griffon.
If you're looking for more Griffon examples, check out the samples included in the samples
directory of the Griffon distribution, and keep an eye on Griffon posts groovy.dzone.com
2 comments:
I just tried a new griffon 0.1.2 app with MigLayout. I just placed the MigLayout in the lib directort and added teh MigLayout class. Unfortunately my views fail, is there a special trick other than adding the jar and importing the classes in the views?
Is there an exception message or does it just not look right?
I've always just:
import net.miginfocom.swing.MigLayout
and then used new MigLayout() for the layout property on the app/view:
application(title:'Scheme Editor', layout: new MigLayout('fill'))
The only problem I've run into is if I try to use an incorrect constraint on a component, like 'fill' on a label.
Cheers,
Josh
http://bitbucket.org/joshareed/coretools/src/tip/tools/SchemeEditor/griffon-app/views/SchemeEditorView.groovy
Post a Comment