Highstock extension for ExtJs 4
Here is a Highstock extension for ExtJs 4. The extension is derived from the original work of Highcharts ExtJs adapter with significant modifications. Although Highstock shares the same APIs and similar configs as the Highcharts, I tried my best to change as little as possible in terms of usage. With the correct setup, this extension can do all the different plots as the original Highstock demos in ExtJs 4 framework.
This is a rather long article. The first part is to demonstrate how to create a Highstock config in ExtJs 4. The second part is to describe how to use the extension to achieve more advanced plots shown in the Highstock demos. Hopefully, you won’t find it too difficult to follow.
Download & Demo
You can download the extension through my github or try the demo on joekuan.org. Inside this package, there are two main examples: standalone.html and stockcharts.html. The standalone version shows a single chart with simple code for the purpose of quick start guide, whereas the stockcharts version is implemented in MVC implementation which provides a comprehensive Highstock demo.
The core file for the extension is HighStock.js in Chart/ux directory. Two other files you may find it useful. SampleConfigs.js contains all the config setup for all the demo. The controller file, Charts.js, contains the implementation of data store in single series, multiple series, dynamic update and data grouping.
At the time of writing, this extension is developed with Highstock 1.1.2 and ExtJs 4.0.7 and tested on Safari 5.1.2, IE 8, Chrome 17 and Firefox 10.0.2.
How to implement your first Highstock chart in ExtJs 4
Migrating Highstock chart config
First, I strongly recommend to start from a working Highchart’s stock chart with all the desired setup. I use the single line demo from Highstock to illustrate how. Here is the demo code:
$(function() { $.getJSON('http://www.highcharts.com/samples/data/jsonp.php?filename=aapl-c.json&callback=?', function(data) { // Create the chart window.chart = new Highcharts.StockChart({ chart : { renderTo : 'container' }, rangeSelector : { selected : 1 }, title : { text : 'AAPL Stock Price' }, series : [{ name : 'AAPL', data : data, tooltip: { valueDecimals: 2 } }] }); });
As you can see, the stock chart starts rendering when the data from the query arrives. The returned data is an array of 2 dimensional data which are time and price values.
Ext.data.JsonP.callback2(/* AAPL historical OHLC data from the Google Finance API */ [ /* Feb 2005 */ [1109030400000,42.64], [1109116800000,44.12], [1109203200000,44.46], [1109289600000,44.50], [1109548800000,44.86], ....
Stage 1. Defines Data Model and Store
Before using the Highstock extension, the first step is to create ExtJs Store and define the data model for the JsonP query. The fields in StockData model basically maps the first and second indices in the returned array to ‘time’ and ‘price’ fields respectively. Then a JsonP store is created with a target URL and an array reader.
Ext.define('StockData', { extend : 'Ext.data.Model', fields : [{ name :'time', mapping: 0 }, {name: 'price', mapping: 1 }] }); // Use JSONP store for the stock query var store = Ext.create('Ext.data.Store', { model : 'StockData', proxy : { type : 'jsonp', url : 'http://www.highcharts.com/samples/data/jsonp.php?filename=aapl-c.json', reader: { type: 'array' } }, autoLoad : false });
Stage 2. Defines Highstock Config
Then we can start constructing the config object for the Highstock extension.
- First, copies the whole config object inside the original Highcharts.StockChart({ … }) into a chartConfig object (similar to the way Highcharts ExtJs Adapter config does) and put the chartConfig object inside a xtype: ‘highstock’ config.
- Then binds the store object which is created earlier.
- Removes renderTo property which is handled by the parent container.
Here is the result of the config:
xtype : 'highstock', height : 500, width : 700, store : store, chartConfig : { chart : { }, rangeSelector : { selected : 1 }, title : { text : 'AAPL Stock Price' }, series : [{ name : 'AAPL', data : data, tooltip: { valueDecimals: 2 } }] }
Stage 3. Modifies Highstock Series Config
The final step is to modify the series array config by adding data model specification.
- Removes the data field definition from the series object because the series data does not exist yet which is created by Highstock extension later when the store is loaded
- Convert any type property name in the series to plot, eg converts
type: 'candlestick'
to
plot: 'candlestick'
- Adds data model specification of how to map the store data into ‘data‘ field. In this example, the stock chart is a X,Y axises graph, so it only needs to map the fields to both axises. The extension properties, xField and yField, are used to map ‘time’ and ‘price’ inside the series array.
- Moves the modified series array to upper level. The reason is that the Highstock extension reads the series config and loads the store. Then packs the store records according to the data model definition (from the series array) and internally constructs the ‘data‘ field inside the series array. Finally, it moves the whole series array object back into the chartConfig which becomes the same config as the original one StockChart.
So here is the final Highstock ExtJs config object should look like:
xtype : 'highstock', id: 'stockchart', height : 500, width : 700, store : store, series : [{ name : 'AAPL', tooltip: { valueDecimals: 2 }, xField: 'time', yField: 'price' }], chartConfig : { chart : { }, rangeSelector : { selected : 1 }, title : { text : 'AAPL Stock Price' } }
Once the Highstock object is created, calls the loadStores method to load the store. The chart will display the graphs after the data are returned from the store.
Ext.getCmp('stockchart').loadStores();
The full implementation of this example can be found in standalone.js file.
Here is the screenshot of the example:
Part 2. Other Advance Highstock Examples
There are other Highstock demo configs that are worth mentioning because the series configurations can be significant different from each other. For instance, you can have a single store for multiple series, multiple stores for multiple series, different graphs in the same chart, etc.
To create a Highstock chart in ExtJs, the crucial part is to know
- how to map the data model from your ExtJs store query data into individual fields
- then how to map the ExtJs store fields into the chart series data
On mapping the fields into the series data (above Step 2), there are further two different approaches you can use
- If the series only involves X,Y data, then simply uses xField and yField properties for mapping.
- If the series involves more than two dimensions (such as OHLC data), then overrides the extension function getData.
getData: function(record) { return [ record.data.time, record.data.open, record.data.high, record.data.low, record.data.price ]; }
Alternatively, you can define getData with raw data access:
getData: function(record) { return [ record.raw[0], record.raw[1], record.raw[2], record.raw[3], record.raw[4] ]; }
You can find examples of how the series being configured for various demo in SampleConfigs.js.
Two panes, candlestick and volume (Single store, multiple series)
As show in the demo page, the two panes chart consists of two separate graphs: OHLC and volume data. Both OHLC and volume data are returned from the same query: OHLC is a multiple fields series, volume data is a simple X,Y tuple series.
So the series array should contain two entries, the candlestick plot with OHLC data (5 fields) and column plot with volume data (2 fields)
series : [{ plot : 'candlestick', name : 'AAPL', getData : function(record) { return [record.raw[0], // the date record.raw[1], // open record.raw[2], // high record.raw[3], // low record.raw[4] // close ]; }, dataGrouping : { // unit name, allowed multiples units : [['week', [1]], ['month', [1, 2, 3, 4, 6]]] } }, { plot : 'column', name : 'Volume', getData : function(record) { return [record.data.time, record.data.volume]; }, yAxis : 1, dataGrouping : { units : [['week', [1]], ['month', [1, 2, 3, 4, 6]]] } }]
Note that I can use xField and yField properties for the volume plot instead of redefining getData function. This is just to show different ways of defining the function.
Compare multiple series (Multiple stores, multiple series)
Referring to the original demo, the multiple series chart issues 3 different stock queries for time vs. price data. In order to achieve that the Highstock extension is implemented to allow multiple stores binding with each store corresponding to a series.
To bind an array of stores, uses the stores property. The sequence of the store array must pattern match the order of the series.
stores : [ store1, store2, store3 ], // Multiple series series : [{ name : 'MSFT', xField : 'time', yField : 'price' }, { name : 'AAPL', xField : 'time', yField : 'price' }, { name : 'GOOG', xField : 'time', yField : 'price' }]
Data grouping (Single store, an array of 52,000 1-dimensional data)
The data grouping is quite different to other examples in which the returned query data is a single dimensional array. The chart configuration defines the start time of the first data point and the time interval between the data.
Since the query returns data in the following form:
Ext.data.JsonP.callback2([1.7,1.7,1.3,1.3,1.2,0.9, .... // 52,000 of these numbers
and the ExtJs Json reader will interpret above as a single row of 52,000 fields!! It is because the query is not returning as each item inside an individual array as below:
Ext.data.JsonP.callback2([ [1.7],[1.7],[1.3],[1.3],[1.2],....
It is a bit tricky to do the same in ExtJs. The only way I can think of is to first create an array store with single field. Then overrides the extension’s loadStores method to directly launch a JsonP request with callback defined. This callback function reads the large array and wraps individual item with another array like above. Then feeds this re-arranged data array into the Highstock’s store object.
// Create the config from the original demo hcConfig = { .... }; // Just use a simple array store store = Ext.create('Ext.data.ArrayStore', { fields : ['temperature'], }); // Bind the store hcConfig.store = store; // Override the extension's loadStores function to use a dedicate JsonP request // to get the very large single array back and then feed each data into the // array store hcConfig.loadStores = function() { Ext.data.JsonP.request({ url : 'http://www.highcharts.com/samples/data/jsonp.php?filename=large-dataset.json&callback=?', callback : function(data, result) { if(Ext.isArray(result)) { var bigData = []; Ext.each(result, function(item) { this.push([item]); }, bigData); // Get the array store to load the re-arranged data array this.loadData(bigData); } }, // callback scope : store }); };
As for the series definition, the xField and yField properties cannot be used for this example because single field data. So the series needs to override the getData function.
series : [{ name : 'Temperature', getData : function(record) { return record.data.temperature; }, pointStart : Date.UTC(2004, 3, 1), pointInterval : 3600 * 1000, tooltip : { yDecimals : 1, ySuffix : '°C' } }]
Dynamic update (No external query, self generated array store)
The dynamic update example uses self generated data without any external query and the chart is automatically updated with new data every second.
As in ExtJs 4, an array store is constructed and initiated with a set of random generated data. The store’s add listener is defined, so that once a new data point is arrived, the data generation task (Ext.util.DelayedTask) is scheduled for the next second.
// Just use a simple array store store = Ext.create('Ext.data.ArrayStore', { fields : ['time', 'value'], // Auto generated the data - same as the original highstock example data : (function() { // generate an array of random data var data = [], time = (new Date()).getTime(), i; for( i = -999; i < = 0; i++) { data.push([time + i * 1000, Math.round(Math.random() * 100)]); } return data; })(), listeners : { // Once the store is added with new point, setup another // delayed task for the next second add : function(store, records) { if(HighCharts.updateTask) { HighCharts.updateTask.delay(1000); } } } }); hcConfig = { store: store, series : [{ name : 'Random data', xField : 'time', yField : 'value' }], chartConfig: { // config same as Highstock demo ... } }; // The store's add event should kick up the chart's onAdd function HighCharts.updateTask = new Ext.util.DelayedTask(function() { // New random data var x = (new Date()).getTime(), // current time y = Math.round(Math.random() * 100); this.add({ time : x, value : y }); }, store); // Initiated the first add task HighCharts.updateTask.delay(3000); chart = Ext.widget('highstock', hcConfig); // Display the chart chart.loadStores();
It’s great, you are a champion…..!!!!
Is it possible use two y-axes?
Thank you
Haven’t tried it but as long as Highstock can do it, this should it. Otherwise it is a bug in the extension
I have a problem. When I have a chart with two yAxis and add serie with propertie, cursor: ‘pointer’ in the base yAxis, the cursor pointer dont appears…
Sorry too frantic busy. Ok, I will have a look at the end of next week.
No have problem. Thank you for answer
series:[
{
name:’Precio’,
plot:’candlestick’,
dataGrouping: {
enabled: false
},
getData:function (record) {
return [record.raw.tso, record.raw.opn, record.raw.hgh, record.raw.low, record.raw.cls ];
}
},
{
name:’Volumen’,
plot:’column’,
cursor: ‘pointer’,
dataGrouping: {
enabled: false
},
getData:function (record) {
return [record.raw.tso, record.raw.vol];
},
yAxis:1
}
],
yAxis:[
{
title:{
text:’Precio’
},
height:350, // TODO Setear el height desde initComponent y/o al renderizar
lineWidth:0,
labels:{
enabled:true
},
opposite:true
},
{
labels:{
enabled:true
},
title:{
text:’Vol’
},
top:390,
height:90,
offset:0,
gridLineWidth:0,
lineWidth:0
}
],
After a long investigation, it seems this may be a bug from Highstock because I can replicate this with Highstock 1.1.2. As soon as I upgrade to 1.1.5, the problem disappears. Will update the new version in my demo website. Thanks
Joe
thanks joe!!!. I am waiting for the new version
I mean it is not the bug in my extension. It is to do with the old version of Highstock. If you download the latest Highstock (1.1.5) from the highcharts.com, then the problem should go away.
Is true!! sorry I dont understood. the problem is resolved. thank you
Hello your extension is AMAZING!!!!!! thanks so much!!
i have this big problem, i cant get the DATE to work (xField) using the standalone example.
The thing is, i test the unix timestamp (example http://190.3.64.6/reportes/dev3/consulta.php ) the first value of the 2 dimensional data array.
For ex: this value: 915159600 , i tested in http://www.onlineconversion.com/unix_time.htm,, in gives me this value “Fri, 01 Jan 1999 03:00:00 GMT”
but the thing is that the standalone.html example give me the first value date to: “Sun 11 Jan 1970 14:12”
….
i cant get working the unix date into the standalone example..
:S
here you can see the example http://190.3.64.6/reportes/dev3/standalone.html
the thing is, altough the date unix timestamp values are correct from the store (consulta.php), the standalone.html just gaves me incorrect date values:
code for:
http://190.3.64.6/reportes/dev3/standalone.html:
Ext.Loader.setConfig({
enabled : true,
disableCaching : false,
paths : {
‘Chart’ : ‘Chart’
}
});
Ext.require(‘Chart.ux.HighStock’);
Ext.application({
name : ‘HighStock’,
launch : function() {
Ext.define(‘StockData’, {
extend : ‘Ext.data.Model’,
fields : [{
name : ‘fecha’,
mapping : 0
}, {
name : ‘cant’,
mapping : 1
}]
});
var store = Ext.create(‘Ext.data.Store’, {
model : ‘StockData’,
proxy : {
type : ‘jsonp’,
url : ‘http://190.3.64.6/reportes/dev3/consulta.php’,
reader : {
type : ‘array’
}
},
autoLoad : false
});
var win = Ext.create(‘Ext.Window’, {
width : 800,
height : 600,
minHeight : 400,
minWidth : 550,
hidden : false,
shadow : false,
maximizable : true,
renderTo : Ext.getBody(),
layout : ‘fit’,
items : [{
xtype : ‘highstock’,
id : ‘chart’,
series : [{
tooltip : {
yDecimals : 2
},
//name : ‘nombre’,
xField : ‘fecha’,
yField : ‘cant’
}],
height : 500,
width : 700,
store : store,
chartConfig : {
chart : {
marginLeft : 50,
marginRight : 50
},
rangeSelector : {
selected : 1
},
title : {
text : ‘Titulo’
}
}
}]
});
win.show();
store.load();
}
});
Well, Javascript uses milliseconds for datetime, not seconds.
Estoy viendo el archivo http://190.3.64.6/reportes/dev3/consulta.php y el grafico del standlone.html y el primer dato del grafico coincide con el de consulta.php porque decis que te muestra valores incorrectos?
Hi, really nice extension !
I managed to have the highchart extension running but with highstock I get a problem:
on line 255 in highstock.js: this.chartConfig.series[i] = this.series[i];
I get an uncaught typeerror= cannot set property 0 of undefined
Just a code sample:
var s = Ext.create(“Ext.data.Store”, {
fields:[“timeStamp”, this.node.get(‘alias’)],
pageSize:1000,
proxy:{
type:”rest”, // …. mouais
url:’/webmoni/default/values.json’,
reader:{
type:’json’,
root:’result’
},
extraParams:{
aliases:[this.node.parentNode.get(‘id’) + ‘,’ + this.node.get(‘alias’)]
}
},
listeners:{
beforeload:function (store, op, opt) {
//TODOO: refresh only last points
store.getProxy().extraParams.after = new Date().addHours(-1).toString(“yyyy-MM-dd H:m:s”);
},
load:function (store, op) {
console.log(this.totalCount);
}
}
});
var chart = Ext.create(“Chart.ux.HighStock”, {
// TODOO: get a series dict with type alias and name and match name to dataIndex for store sync
series:[
{
type:’line’,
dataIndex:this.node.get(‘alias’),
name:this.node.get(‘alias’)
}
],
xField:’timeStamp’,
chartConfig:{
chart:{
zoomType:’x’
},
title:{
text:this.node.get(‘alias’),
x:-20 //center
},
subtitle:{
text:’Last ‘ + s.pageSize + ‘ points..’,
x:-20
},
plotOptions:{
line:{
marker:{
enabled:false,
states:{
hover:{enabled:true}
}
}
}
},
xAxis:[
{
type:’datetime’,
title:{
text:’Time’,
margin:20
},
tickInterval:15*10,
labels:{
rotation:270,
y:35,
formatter:function () {
var dt = Ext.Date.parse(parseInt(this.value) / 1000, “U”);
if (dt) {
return Ext.Date.format(dt, “H:i:s”);
}
return this.value;
}
}
}
],
yAxis:{
title:{
text:”
}
},
tooltip:{
formatter:function () {
var dt = Ext.Date.parse(parseInt(this.x) / 1000, “U”);
return Ext.Date.format(dt, “l, F d, Y g:i:s A”) + ‘‘ + this.series.name + ‘ is : ‘ + this.y;
}
},
legend:{
enabled:false
}
}
});
this.store = s;
this.chart = chart;
chart.bindStore(s);
this.add(chart);
I forget I’m with highstock 1.1.6 :)
It may very well be my error, but using addSeries(…) to add flags to a chart will do exactly that – namely display the flags on the chart. However checking the series.length property before and after adding series will show no difference. Also, trying to access the series by id (Ext.getCmp(‘some_id’)) fails.
Example:
”’
chartConfig: {
chart: {
events: {
load: function(chart) {
var wpd_loaded = this;
Ext.each([‘A’,’I’,’C’], function(t) {
response = Ext.Ajax.request({
async: false,
url: ‘http://127.0.0.1:6543/get_Annotations’,
method: ‘get’,
params: {
title: t
},
headers: {
‘Accept’: ‘application/json’
}
});
dta=Ext.decode(response.responseText);
wpd_loaded.addSeries({
type: dta.type,
id: dta.id,
shape: dta.shape,
width: dta.width,
color: dta.color,
data: dta.data
});
console.log(wpd_loaded.series.length);
});
}
}
},
”’
The ‘console.log(wpd_loaded.series.length)’ is showing up correct results, but outside of this scope Ext.getCmp(‘chart_id’).series.length stays as no series have been added. This results in not being able to create any hide/show actions on the added flags series.
Sorry – my error!
Just noticed it takes a ‘.chart.’ in-between there like so Ext.getCmp(‘chart_id’).chart.series.length
Sorry, for the late reply. Is it working fine for you now?
It’s working just fine now yes – once I spotted the error I made.
Hi,
I am trying to show 2 series on the same chart but couldn’t make it work using the code provided in the examples. I created 2 stores pointing at the same URL (for test purpose): http://www.highcharts.com/samples/data/jsonp.php?filename=aapl-c.json
Then, I wrote the following code, based on the example of the page https://joekuan.wordpress.com/2012/02/22/highstock-extension-for-extjs-4/ at the section Part 2. Other Advance Highstock Examples. Here is my code:
store=this.getDataPointsStore();
store1=this.getStockStoreStore();
//console.log(store);
var win = Ext.create(‘Ext.panel.Panel’, {
minHeight : 700,
minWidth : 550,
flex:1,
id:’chartZonePanelid’,
hidden : false,
shadow : false,
header: false,
border: false,
title : false,
renderTo : ‘chartZone’,
layout : ‘fit’,
//tbar : [{
// text : ‘Reload Data’,
// handler : function() {
// store.load();
// }
//}],
items : [{
xtype : ‘highstock’,
id : ‘chart’,
stores : [store,store1],
series : [{
tooltip : {
yDecimals : 2
},
name: ‘store’,
xField : ‘time’,
yField : ‘price’
},{
name: ‘store1’,
xField : ‘time’,
yField : ‘price’
}],
height : 500,
width : 700,
chartConfig : {
chart : {
marginLeft : 50,
marginRight : 50,
zoomType : ‘x’
},
credits : false,
rangeSelector : {
selected : 1
},
title : {
//text : ‘AAPL Stock Price’
}
}
}]
});
Ext.getCmp(‘chartZonePanelid’).loadStores();
What’s wrong? It looks like the code doesn’t work.
Sorry for the late reply. I will have a look sometime this week.
PS: I don’t actively maintain the Highstock extension due to low user activities. It may be not compatible with the latest release.
Hi I am not able to render highcharts on the same page. I have divs with id = report_1 and id = report_2. I iterate through the list of reports (length = 2) and on the “report panel” I have the timeseries highcharts chart and summary table. The problem is that I can only ever see highcharts chart on one div – the report_1 (or last such div if # of reports were 3). This is not the same behavior with Ext.Chart. Please help…
Ext.each(dashboard.reports, function(val, index) {
Ext.create(“Ext.panel.Panel”,{
id: ‘Report_’ + index,
title : “Some Traffic Report”,
renderTo : ‘report_’ + index,
layout : {type : ‘hbox’, align: ‘stretch’},
loadingMask: true,
items : [{xtype: ‘timeseries’}, {xtype : ‘summary’}]
});
});
i am facing an issue while integrating highstocks with extjs 4.2.
the follwing exception
Uncaught TypeError: undefined is not a function in HighStock.js at line 336 ( this.chart = new Highcharts.StockChart(this.chartConfig);)
What am i missing here. Please help me in this.
Sorry for the late reply. I haven’t maintained Highstocks for a long time due to my limited resources.
Have you included the appropriate JS for highstocks? Have you checked it from the developer console?
Hi,
I have the same issue: Uncaught ReferenceError: Highcharts is not defined
I think because in stockcharts.html or in standalone.html you have this line :
(line 336 : this.chart = new Highcharts.StockChart(this.chartConfig);)
and this file (and folder) can’t be found in the archive …
So where can we found /Highstock-1.1.2/js/highstock.src.js ?? I don’t find it on GitHub :(
Than in advance for your help.
Ludo
PS : just another question, do you think it is possible to run Highstock-1.1.2 over ExtJS 5 ?
Hi,
I too got the same issue :Uncaught ReferenceError: Highcharts is not defined.
I have downloaded charts/ux/HighStock.js file from git hub, do we need to include any other js files other than charts/ux/HighStock.js file.
please help me.
thanks in advance.
This generally means you are not loading the Highcharts library and extension properly. See the demo code inside the package.