lazy_high_charts 1.4.1 → 1.4.2.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ /*
2
+ Data plugin for Highcharts
3
+
4
+ (c) 2012-2013 Torstein Hønsi
5
+ Last revision 2012-11-27
6
+
7
+ License: www.highcharts.com/license
8
+ */
9
+ (function(f){var k=f.each,n=function(a){this.init(a)};f.extend(n.prototype,{init:function(a){this.options=a;this.columns=a.columns||this.rowsToColumns(a.rows)||[];this.columns.length?this.dataFound():(this.parseCSV(),this.parseTable(),this.parseGoogleSpreadsheet())},dataFound:function(){this.parseTypes();this.findHeaderRow();this.parsed();this.complete()},parseCSV:function(){var a=this,b=this.options,c=b.csv,d=this.columns,e=b.startRow||0,g=b.endRow||Number.MAX_VALUE,h=b.startColumn||0,l=b.endColumn||
10
+ Number.MAX_VALUE,q=0;c&&(c=c.replace(/\r\n/g,"\n").replace(/\r/g,"\n").split(b.lineDelimiter||"\n"),k(c,function(c,m){var o=a.trim(c),f=o.indexOf("#")===0;m>=e&&m<=g&&!f&&o!==""&&(o=c.split(b.itemDelimiter||","),k(o,function(b,a){a>=h&&a<=l&&(d[a-h]||(d[a-h]=[]),d[a-h][q]=b)}),q+=1)}),this.dataFound())},parseTable:function(){var a=this.options,b=a.table,c=this.columns,d=a.startRow||0,e=a.endRow||Number.MAX_VALUE,g=a.startColumn||0,h=a.endColumn||Number.MAX_VALUE,l;b&&(typeof b==="string"&&(b=document.getElementById(b)),
11
+ k(b.getElementsByTagName("tr"),function(a,b){l=0;b>=d&&b<=e&&k(a.childNodes,function(a){if((a.tagName==="TD"||a.tagName==="TH")&&l>=g&&l<=h)c[l]||(c[l]=[]),c[l][b-d]=a.innerHTML,l+=1})}),this.dataFound())},parseGoogleSpreadsheet:function(){var a=this,b=this.options,c=b.googleSpreadsheetKey,d=this.columns,e=b.startRow||0,g=b.endRow||Number.MAX_VALUE,h=b.startColumn||0,l=b.endColumn||Number.MAX_VALUE,f,j;c&&jQuery.getJSON("https://spreadsheets.google.com/feeds/cells/"+c+"/"+(b.googleSpreadsheetWorksheet||
12
+ "od6")+"/public/values?alt=json-in-script&callback=?",function(b){var b=b.feed.entry,c,k=b.length,n=0,p=0,i;for(i=0;i<k;i++)c=b[i],n=Math.max(n,c.gs$cell.col),p=Math.max(p,c.gs$cell.row);for(i=0;i<n;i++)if(i>=h&&i<=l)d[i-h]=[],d[i-h].length=Math.min(p,g-e);for(i=0;i<k;i++)if(c=b[i],f=c.gs$cell.row-1,j=c.gs$cell.col-1,j>=h&&j<=l&&f>=e&&f<=g)d[j-h][f-e]=c.content.$t;a.dataFound()})},findHeaderRow:function(){k(this.columns,function(){});this.headerRow=0},trim:function(a){return typeof a==="string"?a.replace(/^\s+|\s+$/g,
13
+ ""):a},parseTypes:function(){for(var a=this.columns,b=a.length,c,d,e,g;b--;)for(c=a[b].length;c--;)d=a[b][c],e=parseFloat(d),g=this.trim(d),g==e?(a[b][c]=e,e>31536E6?a[b].isDatetime=!0:a[b].isNumeric=!0):(d=this.parseDate(d),b===0&&typeof d==="number"&&!isNaN(d)?(a[b][c]=d,a[b].isDatetime=!0):a[b][c]=g===""?null:g)},dateFormats:{"YYYY-mm-dd":{regex:"^([0-9]{4})-([0-9]{2})-([0-9]{2})$",parser:function(a){return Date.UTC(+a[1],a[2]-1,+a[3])}}},parseDate:function(a){var b=this.options.parseDate,c,d,
14
+ e;b&&(c=b(a));if(typeof a==="string")for(d in this.dateFormats)b=this.dateFormats[d],(e=a.match(b.regex))&&(c=b.parser(e));return c},rowsToColumns:function(a){var b,c,d,e,g;if(a){g=[];c=a.length;for(b=0;b<c;b++){e=a[b].length;for(d=0;d<e;d++)g[d]||(g[d]=[]),g[d][b]=a[b][d]}}return g},parsed:function(){this.options.parsed&&this.options.parsed.call(this,this.columns)},complete:function(){var a=this.columns,b,c,d,e,g=this.options,h,f,k,j,m;if(g.complete){a.length>1&&(d=a.shift(),this.headerRow===0&&
15
+ d.shift(),(b=d.isNumeric||d.isDatetime)||(c=d),d.isDatetime&&(e="datetime"));h=[];for(j=0;j<a.length;j++){this.headerRow===0&&(k=a[j].shift());f=[];for(m=0;m<a[j].length;m++)f[m]=a[j][m]!==void 0?b?[d[m],a[j][m]]:a[j][m]:null;h[j]={name:k,data:f}}g.complete({xAxis:{categories:c,type:e},series:h})}}});f.Data=n;f.data=function(a){return new n(a)};f.wrap(f.Chart.prototype,"init",function(a,b,c){var d=this;b&&b.data?f.data(f.extend(b.data,{complete:function(e){b.series&&k(b.series,function(a,c){b.series[c]=
16
+ f.merge(a,e.series[c])});b=f.merge(e,b);a.call(d,b,c)}})):a.call(d,b,c)})})(Highcharts);
@@ -0,0 +1,537 @@
1
+ /**
2
+ * @license Data plugin for Highcharts
3
+ *
4
+ * (c) 2012-2013 Torstein Hønsi
5
+ * Last revision 2012-11-27
6
+ *
7
+ * License: www.highcharts.com/license
8
+ */
9
+
10
+ /*
11
+ * The Highcharts Data plugin is a utility to ease parsing of input sources like
12
+ * CSV, HTML tables or grid views into basic configuration options for use
13
+ * directly in the Highcharts constructor.
14
+ *
15
+ * Demo: http://jsfiddle.net/highcharts/SnLFj/
16
+ *
17
+ * --- OPTIONS ---
18
+ *
19
+ * - columns : Array<Array<Mixed>>
20
+ * A two-dimensional array representing the input data on tabular form. This input can
21
+ * be used when the data is already parsed, for example from a grid view component.
22
+ * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns
23
+ * are interpreted as series. See also the rows option.
24
+ *
25
+ * - complete : Function(chartOptions)
26
+ * The callback that is evaluated when the data is finished loading, optionally from an
27
+ * external source, and parsed. The first argument passed is a finished chart options
28
+ * object, containing series and an xAxis with categories if applicable. Thise options
29
+ * can be extended with additional options and passed directly to the chart constructor.
30
+ *
31
+ * - csv : String
32
+ * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn
33
+ * and endColumn to delimit what part of the table is used. The lineDelimiter and
34
+ * itemDelimiter options define the CSV delimiter formats.
35
+ *
36
+ * - endColumn : Integer
37
+ * In tabular input data, the first row (indexed by 0) to use. Defaults to the last
38
+ * column containing data.
39
+ *
40
+ * - endRow : Integer
41
+ * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row
42
+ * containing data.
43
+ *
44
+ * - googleSpreadsheetKey : String
45
+ * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
46
+ * for general information on GS.
47
+ *
48
+ * - googleSpreadsheetWorksheet : String
49
+ * The Google Spreadsheet worksheet. The available id's can be read from
50
+ * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
51
+ *
52
+ * - itemDelimiter : String
53
+ * Item or cell delimiter for parsing CSV. Defaults to ",".
54
+ *
55
+ * - lineDelimiter : String
56
+ * Line delimiter for parsing CSV. Defaults to "\n".
57
+ *
58
+ * - parsed : Function
59
+ * A callback function to access the parsed columns, the two-dimentional input data
60
+ * array directly, before they are interpreted into series data and categories.
61
+ *
62
+ * - parseDate : Function
63
+ * A callback function to parse string representations of dates into JavaScript timestamps.
64
+ * Return an integer on success.
65
+ *
66
+ * - rows : Array<Array<Mixed>>
67
+ * The same as the columns input option, but defining rows intead of columns.
68
+ *
69
+ * - startColumn : Integer
70
+ * In tabular input data, the first column (indexed by 0) to use.
71
+ *
72
+ * - startRow : Integer
73
+ * In tabular input data, the first row (indexed by 0) to use.
74
+ *
75
+ * - table : String|HTMLElement
76
+ * A HTML table or the id of such to be parsed as input data. Related options ara startRow,
77
+ * endRow, startColumn and endColumn to delimit what part of the table is used.
78
+ */
79
+
80
+ // JSLint options:
81
+ /*global jQuery */
82
+
83
+ (function (Highcharts) {
84
+
85
+ // Utilities
86
+ var each = Highcharts.each;
87
+
88
+
89
+ // The Data constructor
90
+ var Data = function (options) {
91
+ this.init(options);
92
+ };
93
+
94
+ // Set the prototype properties
95
+ Highcharts.extend(Data.prototype, {
96
+
97
+ /**
98
+ * Initialize the Data object with the given options
99
+ */
100
+ init: function (options) {
101
+ this.options = options;
102
+ this.columns = options.columns || this.rowsToColumns(options.rows) || [];
103
+
104
+ // No need to parse or interpret anything
105
+ if (this.columns.length) {
106
+ this.dataFound();
107
+
108
+ // Parse and interpret
109
+ } else {
110
+
111
+ // Parse a CSV string if options.csv is given
112
+ this.parseCSV();
113
+
114
+ // Parse a HTML table if options.table is given
115
+ this.parseTable();
116
+
117
+ // Parse a Google Spreadsheet
118
+ this.parseGoogleSpreadsheet();
119
+ }
120
+
121
+ },
122
+
123
+ dataFound: function () {
124
+ // Interpret the values into right types
125
+ this.parseTypes();
126
+
127
+ // Use first row for series names?
128
+ this.findHeaderRow();
129
+
130
+ // Handle columns if a handleColumns callback is given
131
+ this.parsed();
132
+
133
+ // Complete if a complete callback is given
134
+ this.complete();
135
+
136
+ },
137
+
138
+ /**
139
+ * Parse a CSV input string
140
+ */
141
+ parseCSV: function () {
142
+ var self = this,
143
+ options = this.options,
144
+ csv = options.csv,
145
+ columns = this.columns,
146
+ startRow = options.startRow || 0,
147
+ endRow = options.endRow || Number.MAX_VALUE,
148
+ startColumn = options.startColumn || 0,
149
+ endColumn = options.endColumn || Number.MAX_VALUE,
150
+ lines,
151
+ activeRowNo = 0;
152
+
153
+ if (csv) {
154
+
155
+ lines = csv
156
+ .replace(/\r\n/g, "\n") // Unix
157
+ .replace(/\r/g, "\n") // Mac
158
+ .split(options.lineDelimiter || "\n");
159
+
160
+ each(lines, function (line, rowNo) {
161
+ var trimmed = self.trim(line),
162
+ isComment = trimmed.indexOf('#') === 0,
163
+ isBlank = trimmed === '',
164
+ items;
165
+
166
+ if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
167
+ items = line.split(options.itemDelimiter || ',');
168
+ each(items, function (item, colNo) {
169
+ if (colNo >= startColumn && colNo <= endColumn) {
170
+ if (!columns[colNo - startColumn]) {
171
+ columns[colNo - startColumn] = [];
172
+ }
173
+
174
+ columns[colNo - startColumn][activeRowNo] = item;
175
+ }
176
+ });
177
+ activeRowNo += 1;
178
+ }
179
+ });
180
+
181
+ this.dataFound();
182
+ }
183
+ },
184
+
185
+ /**
186
+ * Parse a HTML table
187
+ */
188
+ parseTable: function () {
189
+ var options = this.options,
190
+ table = options.table,
191
+ columns = this.columns,
192
+ startRow = options.startRow || 0,
193
+ endRow = options.endRow || Number.MAX_VALUE,
194
+ startColumn = options.startColumn || 0,
195
+ endColumn = options.endColumn || Number.MAX_VALUE,
196
+ colNo;
197
+
198
+ if (table) {
199
+
200
+ if (typeof table === 'string') {
201
+ table = document.getElementById(table);
202
+ }
203
+
204
+ each(table.getElementsByTagName('tr'), function (tr, rowNo) {
205
+ colNo = 0;
206
+ if (rowNo >= startRow && rowNo <= endRow) {
207
+ each(tr.childNodes, function (item) {
208
+ if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
209
+ if (!columns[colNo]) {
210
+ columns[colNo] = [];
211
+ }
212
+ columns[colNo][rowNo - startRow] = item.innerHTML;
213
+
214
+ colNo += 1;
215
+ }
216
+ });
217
+ }
218
+ });
219
+
220
+ this.dataFound(); // continue
221
+ }
222
+ },
223
+
224
+ /**
225
+ * TODO:
226
+ * - switchRowsAndColumns
227
+ */
228
+ parseGoogleSpreadsheet: function () {
229
+ var self = this,
230
+ options = this.options,
231
+ googleSpreadsheetKey = options.googleSpreadsheetKey,
232
+ columns = this.columns,
233
+ startRow = options.startRow || 0,
234
+ endRow = options.endRow || Number.MAX_VALUE,
235
+ startColumn = options.startColumn || 0,
236
+ endColumn = options.endColumn || Number.MAX_VALUE,
237
+ gr, // google row
238
+ gc; // google column
239
+
240
+ if (googleSpreadsheetKey) {
241
+ jQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' +
242
+ googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
243
+ '/public/values?alt=json-in-script&callback=?',
244
+ function (json) {
245
+
246
+ // Prepare the data from the spreadsheat
247
+ var cells = json.feed.entry,
248
+ cell,
249
+ cellCount = cells.length,
250
+ colCount = 0,
251
+ rowCount = 0,
252
+ i;
253
+
254
+ // First, find the total number of columns and rows that
255
+ // are actually filled with data
256
+ for (i = 0; i < cellCount; i++) {
257
+ cell = cells[i];
258
+ colCount = Math.max(colCount, cell.gs$cell.col);
259
+ rowCount = Math.max(rowCount, cell.gs$cell.row);
260
+ }
261
+
262
+ // Set up arrays containing the column data
263
+ for (i = 0; i < colCount; i++) {
264
+ if (i >= startColumn && i <= endColumn) {
265
+ // Create new columns with the length of either end-start or rowCount
266
+ columns[i - startColumn] = [];
267
+
268
+ // Setting the length to avoid jslint warning
269
+ columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
270
+ }
271
+ }
272
+
273
+ // Loop over the cells and assign the value to the right
274
+ // place in the column arrays
275
+ for (i = 0; i < cellCount; i++) {
276
+ cell = cells[i];
277
+ gr = cell.gs$cell.row - 1; // rows start at 1
278
+ gc = cell.gs$cell.col - 1; // columns start at 1
279
+
280
+ // If both row and col falls inside start and end
281
+ // set the transposed cell value in the newly created columns
282
+ if (gc >= startColumn && gc <= endColumn &&
283
+ gr >= startRow && gr <= endRow) {
284
+ columns[gc - startColumn][gr - startRow] = cell.content.$t;
285
+ }
286
+ }
287
+ self.dataFound();
288
+ });
289
+ }
290
+ },
291
+
292
+ /**
293
+ * Find the header row. For now, we just check whether the first row contains
294
+ * numbers or strings. Later we could loop down and find the first row with
295
+ * numbers.
296
+ */
297
+ findHeaderRow: function () {
298
+ var headerRow = 0;
299
+ each(this.columns, function (column) {
300
+ if (typeof column[0] !== 'string') {
301
+ headerRow = null;
302
+ }
303
+ });
304
+ this.headerRow = 0;
305
+ },
306
+
307
+ /**
308
+ * Trim a string from whitespace
309
+ */
310
+ trim: function (str) {
311
+ return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
312
+ },
313
+
314
+ /**
315
+ * Parse numeric cells in to number types and date types in to true dates.
316
+ * @param {Object} columns
317
+ */
318
+ parseTypes: function () {
319
+ var columns = this.columns,
320
+ col = columns.length,
321
+ row,
322
+ val,
323
+ floatVal,
324
+ trimVal,
325
+ dateVal;
326
+
327
+ while (col--) {
328
+ row = columns[col].length;
329
+ while (row--) {
330
+ val = columns[col][row];
331
+ floatVal = parseFloat(val);
332
+ trimVal = this.trim(val);
333
+
334
+ /*jslint eqeq: true*/
335
+ if (trimVal == floatVal) { // is numeric
336
+ /*jslint eqeq: false*/
337
+ columns[col][row] = floatVal;
338
+
339
+ // If the number is greater than milliseconds in a year, assume datetime
340
+ if (floatVal > 365 * 24 * 3600 * 1000) {
341
+ columns[col].isDatetime = true;
342
+ } else {
343
+ columns[col].isNumeric = true;
344
+ }
345
+
346
+ } else { // string, continue to determine if it is a date string or really a string
347
+ dateVal = this.parseDate(val);
348
+
349
+ if (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
350
+ columns[col][row] = dateVal;
351
+ columns[col].isDatetime = true;
352
+
353
+ } else { // string
354
+ columns[col][row] = trimVal === '' ? null : trimVal;
355
+ }
356
+ }
357
+
358
+ }
359
+ }
360
+ },
361
+ //*
362
+ dateFormats: {
363
+ 'YYYY-mm-dd': {
364
+ regex: '^([0-9]{4})-([0-9]{2})-([0-9]{2})$',
365
+ parser: function (match) {
366
+ return Date.UTC(+match[1], match[2] - 1, +match[3]);
367
+ }
368
+ }
369
+ },
370
+ // */
371
+ /**
372
+ * Parse a date and return it as a number. Overridable through options.parseDate.
373
+ */
374
+ parseDate: function (val) {
375
+ var parseDate = this.options.parseDate,
376
+ ret,
377
+ key,
378
+ format,
379
+ match;
380
+
381
+ if (parseDate) {
382
+ ret = parseDate(val);
383
+ }
384
+
385
+ if (typeof val === 'string') {
386
+ for (key in this.dateFormats) {
387
+ format = this.dateFormats[key];
388
+ match = val.match(format.regex);
389
+ if (match) {
390
+ ret = format.parser(match);
391
+ }
392
+ }
393
+ }
394
+ return ret;
395
+ },
396
+
397
+ /**
398
+ * Reorganize rows into columns
399
+ */
400
+ rowsToColumns: function (rows) {
401
+ var row,
402
+ rowsLength,
403
+ col,
404
+ colsLength,
405
+ columns;
406
+
407
+ if (rows) {
408
+ columns = [];
409
+ rowsLength = rows.length;
410
+ for (row = 0; row < rowsLength; row++) {
411
+ colsLength = rows[row].length;
412
+ for (col = 0; col < colsLength; col++) {
413
+ if (!columns[col]) {
414
+ columns[col] = [];
415
+ }
416
+ columns[col][row] = rows[row][col];
417
+ }
418
+ }
419
+ }
420
+ return columns;
421
+ },
422
+
423
+ /**
424
+ * A hook for working directly on the parsed columns
425
+ */
426
+ parsed: function () {
427
+ if (this.options.parsed) {
428
+ this.options.parsed.call(this, this.columns);
429
+ }
430
+ },
431
+
432
+ /**
433
+ * If a complete callback function is provided in the options, interpret the
434
+ * columns into a Highcharts options object.
435
+ */
436
+ complete: function () {
437
+
438
+ var columns = this.columns,
439
+ hasXData,
440
+ categories,
441
+ firstCol,
442
+ type,
443
+ options = this.options,
444
+ series,
445
+ data,
446
+ name,
447
+ i,
448
+ j;
449
+
450
+
451
+ if (options.complete) {
452
+
453
+ // Use first column for X data or categories?
454
+ if (columns.length > 1) {
455
+ firstCol = columns.shift();
456
+ if (this.headerRow === 0) {
457
+ firstCol.shift(); // remove the first cell
458
+ }
459
+
460
+ // Use the first column for categories or X values
461
+ hasXData = firstCol.isNumeric || firstCol.isDatetime;
462
+ if (!hasXData) { // means type is neither datetime nor linear
463
+ categories = firstCol;
464
+ }
465
+
466
+ if (firstCol.isDatetime) {
467
+ type = 'datetime';
468
+ }
469
+ }
470
+
471
+ // Use the next columns for series
472
+ series = [];
473
+ for (i = 0; i < columns.length; i++) {
474
+ if (this.headerRow === 0) {
475
+ name = columns[i].shift();
476
+ }
477
+ data = [];
478
+ for (j = 0; j < columns[i].length; j++) {
479
+ data[j] = columns[i][j] !== undefined ?
480
+ (hasXData ?
481
+ [firstCol[j], columns[i][j]] :
482
+ columns[i][j]
483
+ ) :
484
+ null;
485
+ }
486
+ series[i] = {
487
+ name: name,
488
+ data: data
489
+ };
490
+ }
491
+
492
+ // Do the callback
493
+ options.complete({
494
+ xAxis: {
495
+ categories: categories,
496
+ type: type
497
+ },
498
+ series: series
499
+ });
500
+ }
501
+ }
502
+ });
503
+
504
+ // Register the Data prototype and data function on Highcharts
505
+ Highcharts.Data = Data;
506
+ Highcharts.data = function (options) {
507
+ return new Data(options);
508
+ };
509
+
510
+ // Extend Chart.init so that the Chart constructor accepts a new configuration
511
+ // option group, data.
512
+ Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {
513
+ var chart = this;
514
+
515
+ if (userOptions && userOptions.data) {
516
+ Highcharts.data(Highcharts.extend(userOptions.data, {
517
+ complete: function (dataOptions) {
518
+
519
+ // Merge series configs
520
+ if (userOptions.series) {
521
+ each(userOptions.series, function (series, i) {
522
+ userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);
523
+ });
524
+ }
525
+
526
+ // Do the merge
527
+ userOptions = Highcharts.merge(dataOptions, userOptions);
528
+
529
+ proceed.call(chart, userOptions, callback);
530
+ }
531
+ }));
532
+ } else {
533
+ proceed.call(chart, userOptions, callback);
534
+ }
535
+ });
536
+
537
+ }(Highcharts));