I was in a need for a simple application that displays a nice grid and a chart alongside, both "look" at the same data source with all it means.
The technology at hand is ExtJS (4) and I was wondering, being a Flex fan (wait… yeah, ok), how well did Sencha made their charting framework, and their MVC infra. If you don't have the time to read further, I will sum it up for you - they did a pretty good work at it, but there is still room for improvement. I will try to explain what I mean, and hopefully let you learn from my experience so that you will know how to workaround issues I came across.
Ok, so ExtJS MVC infra has really made it simple. I had a Model that defined the fields and the proxy (a RESTFul service, providing JSON responses). Then I created a Store that would link to that Model and have it auto load the data when initialized. This pretty much concludes all the "data" section of the application. Out-of-the-box configuration, nothing to it.
Moving to the view, I've created a simple Viewport that contains the Grid and the Chart. On this note, I would like to say that Sencha still have a lot to improve on layout-ing. I always get mixed up with "flex", "fit", "stretch" etc. it is so confusing and not intuitive, unlike Flex layout-ing. I ended up using an "hbox" layout… oh, well.
No controller was involved in the process yet, but we'll get to that in a minute.
My data model contained a "name" field and 2 numeric fields. I wanted these 2 to be on 2 different Y axis, where the name played the part of the "category" axle. So here are my axes:
axes: [
{
title: 'Score',
type: 'Numeric',
position: 'left',
fields: ['score'],
minimum: 0
},
{
title: 'Hits',
type: 'Numeric',
position: 'right',
fields: ['hits'],
minimum: 0
},
{
title: 'Player',
type: 'Category',
position: 'bottom',
fields: ['player'],
label: {
rotate: {
degrees: 60
}
}
}
]
{
title: 'Score',
type: 'Numeric',
position: 'left',
fields: ['score'],
minimum: 0
},
{
title: 'Hits',
type: 'Numeric',
position: 'right',
fields: ['hits'],
minimum: 0
},
{
title: 'Player',
type: 'Category',
position: 'bottom',
fields: ['player'],
label: {
rotate: {
degrees: 60
}
}
}
]
You see what I did there with the label on the "Player" axle? I had to, for if the labels don't have enough room to be displayed to the fullest, they are simply trimmed off leaving not-so-nice gaps , so having them rotated a bit was a good enough solution for that.
I then created the series, and here comes the frustrating part. It is not as trivial as you might think. I assumed that if I create 2 column series and define the yField that each relates to, I'm done. But no. when you do that you get an overlapping series, where the columns stand on over the other on the same category, and not one next to the other. If you want it to be one next to the other they need to share the same Y axle, which was not my goal.
After a quick search I understood that this is a well-known bug and if I desire something that will be readable, I have to use different types of series, so I ended up using "line" and "columns". That's kinda sucks. As I see it, it is a level 0 defect of Sencha charting.
Ok, so after having that figured out I wanted to change the scale of the Y axes I had, meaning that each will go from 0 to it's max, where max is relative to the max value it may contain according to the data given. I've set the axes "minimum" property to 0 and didn't set the max, as the documentation suggested. Running the application caused the 2 series to be relative to a single axle. What? Why? Well, it appears that if you don't specify the series the axe it relates to, it relates to the first axle it finds. It’s pretty stupid, since I've already defined the yField the series works with, and on the axes definition I've defined which axle work with which yField so… can't ExtJS figure it out by itslef? That should be the default behavior where if I wanted something more advanced I would gladly define the axes for each - not mentioning that the documentation really lacks in that aspect. Why should I find this "trivial" information on a blog? Why should you?
I ended up with the following definition of my Series:
series: [
{
type: 'column',
xField: 'player',
yField: 'score',
axis: 'left',
style: {
fill:'#77AECE'
},
tips: {
trackMouse: true,
width: 140,
height: 28,
renderer: function(storeItem, item) {
this.setTitle(storeItem.get('player') + ' score: ' + storeItem.get('score'));
}
}
},
{
type: 'line',
xField: 'player',
yField: 'hits',
axis: 'right',
style: {
stroke:'#77AECE'
},
tips: {
trackMouse: true,
width: 140,
height: 28,
renderer: function(storeItem, item) {
this.setTitle(storeItem.get('player') + ' hits: ' + storeItem.get('hits'));
}
}
}
]
{
type: 'column',
xField: 'player',
yField: 'score',
axis: 'left',
style: {
fill:'#77AECE'
},
tips: {
trackMouse: true,
width: 140,
height: 28,
renderer: function(storeItem, item) {
this.setTitle(storeItem.get('player') + ' score: ' + storeItem.get('score'));
}
}
},
{
type: 'line',
xField: 'player',
yField: 'hits',
axis: 'right',
style: {
stroke:'#77AECE'
},
tips: {
trackMouse: true,
width: 140,
height: 28,
renderer: function(storeItem, item) {
this.setTitle(storeItem.get('player') + ' hits: ' + storeItem.get('hits'));
}
}
}
]
Ok, this was starting to look good. I must admit that the binding done between the Store and the grid/chart was impressive. I would sort the grid, and the chart would change accordingly with no intervention by me. The animation was playing smoothly, everything was scaling and resizing nicely. Well done, Sencha. The code lines that ware involved were narrowed to just what needed. A simple application remained simple in code terms, while still enforcing the principals of MVC.
Moving forward, I wanted to create a "stupid" polling that will fetch the data every 5 minutes. This is where the Controller came to the rescue. I've created a controller, that referenced the store I'm working with, and when initializing it, I used the TaskManager to create a timed event. Here is the code:
stores : ['PlayersStore'],
init : function() {
Ext.TaskManager.start({
run: this.refreshData,
interval: 300000,
scope: this
});
},
refreshData: function() {
this.getPlayersStoreStore().load();
}
init : function() {
Ext.TaskManager.start({
run: this.refreshData,
interval: 300000,
scope: this
});
},
refreshData: function() {
this.getPlayersStoreStore().load();
}
That is it!
Pretty simple, won't you think? I must say that I am (yet again) impressed with the work Sencha have done. I admit that having this in Flex would have been probably as easy to do, but playing with it's scale would have been a little scary. Altogether, ExtJS gives you some kind of assurance that everything will render as expected. This is something, I have to admit, that I did not take for granted using Flex.
1 hour and you have an application that sums up the best part of UI concepts in RIA development today.
Hope it helped you.
Cheers.
No comments:
Post a Comment