I've been working with Ext JS 4's pure Javascript charting package for a while now and due to the lack of decent real-world examples (i.e. those that don't just use almost all default settings) and a few undue omissions in the documentation, it's not always been plain sailing. In an attempt to rectify this situation, in this post I'll show some of the tricks that can be used to customise an Ext JS chart. The result is a daily stock chart with tracking behavior and customised styling. All the source code for this example can be downloaded here: extjs4-chart-blog-mr.zip.
The stock chart I'll run though is given below and is similar to those a lot of financial companies use on their websites to show stock or index data (the ScottLogic homepage has one). It even uses the "industry standard blue" colour for the series!
This chart shows the percentage difference between a previous closing price and today's prices for a fake stock. The data does not update in real time but is randomly generated - try refreshing the page to see how it looks with different values. To make it easier to see what's going on I'll descibe how to build the chart up step by step; starting with basic version that's not very customised.
Basic ChartThe following code defines a stripped down version of the above chart.
The chart is backed by a JSONStore which expects an JSON object in the form:
This is representative of something that could be generated server side from a data feed, however, in this case it's just created by the generateMockData
function which is defined in the mockStockData.js file. The fields
and data
properties are mandatory, the stockName
is a custom value which isn't used yet - in general there's nothing stopping you adding any properties to the object that is passed to the data store.
The above code is fairly standard, except perhaps for the use of a "Numeric
" axis for the time values. There is a "Date
" axis included in the chart package, but oddly, it's based on a "Category
" axis, which means it's not able to handle time series data, particularly when the time difference between points is variable. Since the times are being rendered on a numeric axis, we need to convert them to numbers before adding them to the chart; this is done on lines 16-18. The numeric axis doesn't handle large numbers correctly so the convertTimeToValue
function which deals with the conversion assumes that the times it gets are on the date to the epoch (Jan 1st 1970), so that the number of milliseconds is minimised.
The resulting chart looks like this:
Axes FormattingYou'll probably agree that the labels on the axes in the above image are less than ideal - firstly they need formating, but also they need to be positioned at more sensible values.
These problems are solveable, however, without writing a new axis class from scratch, the best you can do is to be able to determine the values for the start and end labels and configure the number of steps to use inbetween. This might not seem like much of a draw back, but consider the time axis on this chart; the time range is always the same - 8.00am till 4.30pm (the time that the fake exchange is open), and we'd like to put labels at sensible points in time and not too often. However, we can't set the gap between values to be say two hourly because the time difference is 8.5 hours which isn't divisible by 2. Therefore, we are forced to set the time gap to half an hour then add a bit of a kludge so as to only render labels at sensible gaps. The result looks ok, but it would be nice to be able to do it with fewer vertical lines!
The new axis configuration and accompanying functions when added to the base code look like this:
In the above code snippet I've tried to indicate where the bits should be added to the above basic chart code by including the lines that define the scope that code goes in, and "..."'s to indicate where code has been omitted.
There are two basic things that can be done to sort the axis labels - override its applyData
function and set the label renderer
. The applyData
function returns what is referred to in the Sencha code as a stepsCalc
object. This sets out the smallest and largest axis values and the step between the major ticks (where the labels appear). The use of this function isn't documented, so you can't blame Sencha if they remove it from a future release (however nor is the axisStyle
property on the Axis class which is fairly essential!); what is documented instead is the use of the minimum
, maximum
and steps
configuration properties to control the axis tick positions. However, for some reason, setting these is more like a suggestion - it isn't guaranteed to work. You should be aware that if you do override the applyData
function for an axis, you must also set the maximum
and minimum
axis properties to the to
and from
values of the stepsCalc
object which this function generates - failure to do this means that the chart will render fine, but the values it shows won't appear in the right place!
By providing a custom label renderer
function you can not only format the values but also, by returning the empty string, control which major ticks have labels on them. This is what is done on lines 91-96 with the time axis; this function only returns a visible value in the case that it's a multiple of 2 hours. The reason that the label renderer doesn't just return the empty string or a space character in the case that we want to hide the label is that, due to a bug, this prevents the entire chart rendering in Opera. If you still can't get the effect you want using just the label renderer
and applyData
properties your only choice is to customise the drawAxis
function which essentially controls exactly what the axis looks like - however doing this generally requires a significant amount of code, and you'll need to pick through Sencha's source to figure out what you need to do.
The most interesting bit of code in the above block is in the getValueAxisStepsCalc
function which provides the stepsCalc
for the value axis. Of particular note are lines 24-27 which calculate the steps between the labels based on the range. It takes a while to get what's going on in line 27, but it's fairly useful. It's based on the fact that I (and I guess most people) are generally most happy dealing with things of units of 1,2 and 5 - anything else is complicated.
Another oddity in the above axis configuration is the inclusion of the top axis - this doesn't actually do anything but its inclusion forces the top most label on the left hand axis to be positioned in the position I would expect it to be - otherwise it appears slightly lower.
Series StylingA series is styled in much the same way as any other sprite in Ext - by supplying a style
configuration parameter which takes values for the fill
(a colour), stroke
(the line colour), stroke-width
and an opacity
value (between 0 and 1). This style param is effectively just copied onto the SVG element that represents it. One consequence of this is that you can also set other SVG style properties in there too - including the stroke-dasharray
which allows you make the series line dashed. The only downside is that it won't work in VML which is what it the chart is rendered using in IE6 - IE8 (in fact for some reason it doesn't work in IE9 either, even when using SVG).
Although it might appear that there is only a single series on the chart in fact there are three. One for the line of the stock series, one for the fill and one for the red dashed line at zero. The reason there are two separate series for the stock is simply a hack to get round a couple of rendering bugs - firstly that the opacity
value is used for both the line and the fill - therefore there's no way to get a solid line and a see-through fill in a single series and secondly (although it's not important in this case) the fill overlaps part of the series line (try setting the stroke-width
high and changing the fill colour to see the effect!). To get the red "zero-line" series in, I add an extra previousClose
property to the JSON object backing the chart, and add points at 8am and 4.30pm with a value of zero for this property prior to creating the chart object. The code you need to add to do this is:
The series configuration object (before adding the tracking behaviour), looks like this:
LegendAlthough Ext charts can be configured with a legend parameter, it's a bit limited in terms of what you can achieve with it. You can set the general position and style of it but not exactly what goes on it and perhaps most annoyingly there doesn't seem to be any way to easily turn off the click handler on it which hides the series being highlighted - if someone figures out how please let me know!
To get around this, I create the legend myself using good old HTML and CSS. The DOM elements for the legend are created using Ext's templating library which is pretty powerful and worth checking out.
The code that needs to be added for the legend is this:
At this point the chart is the same as the live version, except that nothing happens when the mouse goes over it. The point described in the legend is the last (current) data point. The above code is fairly standard Ext JS code, the addition of the 'cls' property is so that custom css can be used to style the legend as required without interfering elsewhere. There's nothing particularly special about the css used; this is specified in the dailyStockChart.css file.
Tracking BehaviourThe mouse tracking behaviour on the chart is produced by adding Ext's standard series highlighting behavior to the stock series (which is done simply by setting the highlight
property to true
), and then overriding some of the functions it uses.
The three things that need to be overridden in the series configuration for it to work are the getItemForPoint
function which fires whenever the mouse moves over the drawing surface of the chart and returns the item to highlight (or null if nothing should be highlighted), highlightItem
which takes the newly highlighted item and alters its style to highlight it and unhighlightItem
which should perform the inverse of this action and actually takes no parameters. The reason unhighlightItem
takes no parameters is because it must unhighlight all highlighted items - which is (unfortunately) potentially more than one - this is because if you move the mouse off the drawing surface quickly then back on again it's possible for the highlightItem
function to fire twice in a row.
The trick to getting style right is to set showMarkers
on the stock series to true
, set the style of the markers so that they are invisible; then, when highlighted, the legend is updated and the marker style changed to make it visible.
The code changes needed to make it work are:
The only excessive bit here is the use of a timeout when resetting the legend after the item is unhighlighted; this is so as to prevent it updating the DOM by setting it to the default value in the case that highlightItem
is called straight afterward.
If you're starting developing with the Ext JS 4's chart library, I hope this has given you at least a few tips to put into your own charts; let me know what you think or if there's anything up with the code. I should probably also point out that I've not had the chance to test this on IE6-IE8, so it's pot luck if it works in them too!