highcharts-rails 2.3.3.1 → 2.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,9 +7,76 @@
7
7
  */
8
8
 
9
9
  /*
10
+ * The Highcharts Data plugin is a utility to ease parsing of input sources like
11
+ * CSV, HTML tables or grid views into basic configuration options for use
12
+ * directly in the Highcharts constructor.
13
+ *
10
14
  * Demo: http://jsfiddle.net/highcharts/SnLFj/
15
+ *
16
+ * --- OPTIONS ---
17
+ *
18
+ * - columns : Array<Array<Mixed>>
19
+ * A two-dimensional array representing the input data on tabular form. This input can
20
+ * be used when the data is already parsed, for example from a grid view component.
21
+ * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns
22
+ * are interpreted as series. See also the rows option.
23
+ *
24
+ * - complete : Function(chartOptions)
25
+ * The callback that is evaluated when the data is finished loading, optionally from an
26
+ * external source, and parsed. The first argument passed is a finished chart options
27
+ * object, containing series and an xAxis with categories if applicable. Thise options
28
+ * can be extended with additional options and passed directly to the chart constructor.
29
+ *
30
+ * - csv : String
31
+ * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn
32
+ * and endColumn to delimit what part of the table is used. The lineDelimiter and
33
+ * itemDelimiter options define the CSV delimiter formats.
34
+ *
35
+ * - endColumn : Integer
36
+ * In tabular input data, the first row (indexed by 0) to use. Defaults to the last
37
+ * column containing data.
38
+ *
39
+ * - endRow : Integer
40
+ * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row
41
+ * containing data.
42
+ *
43
+ * - googleSpreadsheetKey : String
44
+ * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
45
+ * for general information on GS.
46
+ *
47
+ * - googleSpreadsheetKey : String
48
+ * The Google Spreadsheet worksheet. The available id's can be read from
49
+ * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
50
+ *
51
+ * - itemDilimiter : String
52
+ * Item or cell delimiter for parsing CSV. Defaults to ",".
53
+ *
54
+ * - lineDilimiter : String
55
+ * Line delimiter for parsing CSV. Defaults to "\n".
56
+ *
57
+ * - parsed : Function
58
+ * A callback function to access the parsed columns, the two-dimentional input data
59
+ * array directly, before they are interpreted into series data and categories.
60
+ *
61
+ * - parseDate : Function
62
+ * A callback function to parse string representations of dates into JavaScript timestamps.
63
+ * Return an integer on success.
64
+ *
65
+ * - rows : Array<Array<Mixed>>
66
+ * The same as the columns input option, but defining rows intead of columns.
67
+ *
68
+ * - startColumn : Integer
69
+ * In tabular input data, the first column (indexed by 0) to use.
70
+ *
71
+ * - startRow : Integer
72
+ * In tabular input data, the first row (indexed by 0) to use.
73
+ *
74
+ * - table : String|HTMLElement
75
+ * A HTML table or the id of such to be parsed as input data. Related options ara startRow,
76
+ * endRow, startColumn and endColumn to delimit what part of the table is used.
11
77
  */
12
78
 
79
+ /*global jQuery */
13
80
  (function (Highcharts) {
14
81
 
15
82
  // Utilities
@@ -29,14 +96,28 @@
29
96
  */
30
97
  init: function (options) {
31
98
  this.options = options;
32
- this.columns = [];
33
-
34
-
35
- // Parse a CSV string if options.csv is given
36
- this.parseCSV();
37
-
38
- // Parse a HTML table if options.table is given
39
- this.parseTable();
99
+ this.columns = options.columns || this.rowsToColumns(options.rows) || [];
100
+
101
+ // No need to parse or interpret anything
102
+ if (this.columns.length) {
103
+ this.dataFound();
104
+
105
+ // Parse and interpret
106
+ } else {
107
+
108
+ // Parse a CSV string if options.csv is given
109
+ this.parseCSV();
110
+
111
+ // Parse a HTML table if options.table is given
112
+ this.parseTable();
113
+
114
+ // Parse a Google Spreadsheet
115
+ this.parseGoogleSpreadsheet();
116
+ }
117
+
118
+ },
119
+
120
+ dataFound: function () {
40
121
 
41
122
  // Interpret the values into right types
42
123
  this.parseTypes();
@@ -66,7 +147,11 @@
66
147
  lines;
67
148
 
68
149
  if (csv) {
69
- lines = csv.split(options.lineDelimiter || '\n');
150
+
151
+ lines = csv
152
+ .replace(/\r\n/g, "\n") // Unix
153
+ .replace(/\r/g, "\n") // Mac
154
+ .split(options.lineDelimiter || "\n");
70
155
 
71
156
  each(lines, function (line, rowNo) {
72
157
  if (rowNo >= startRow && rowNo <= endRow) {
@@ -81,7 +166,8 @@
81
166
  }
82
167
  });
83
168
  }
84
- });
169
+ });
170
+ this.dataFound();
85
171
  }
86
172
  },
87
173
 
@@ -119,6 +205,58 @@
119
205
  });
120
206
  }
121
207
  });
208
+
209
+ this.dataFound(); // continue
210
+ }
211
+ },
212
+
213
+ /**
214
+ * TODO:
215
+ * - switchRowsAndColumns
216
+ * - startRow, endRow etc.
217
+ */
218
+ parseGoogleSpreadsheet: function () {
219
+ var self = this,
220
+ options = this.options,
221
+ googleSpreadsheetKey = options.googleSpreadsheetKey,
222
+ columns = this.columns;
223
+
224
+ if (googleSpreadsheetKey) {
225
+ jQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' +
226
+ googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
227
+ '/public/values?alt=json-in-script&callback=?',
228
+ function (json) {
229
+
230
+ // Prepare the data from the spreadsheat
231
+ var cells = json.feed.entry,
232
+ cell,
233
+ cellCount = cells.length,
234
+ colCount = 0,
235
+ rowCount = 0,
236
+ i;
237
+
238
+ // First, find the total number of columns and rows that
239
+ // are actually filled with data
240
+ for (i = 0; i < cellCount; i++) {
241
+ cell = cells[i];
242
+ colCount = Math.max(colCount, cell.gs$cell.col);
243
+ rowCount = Math.max(rowCount, cell.gs$cell.row);
244
+ }
245
+
246
+ // Set up arrays containing the column data
247
+ for (i = 0; i < colCount; i++) {
248
+ columns[i] = new Array(rowCount);
249
+ }
250
+
251
+ // Loop over the cells and assign the value to the right
252
+ // place in the column arrays
253
+ for (i = 0; i < cellCount; i++) {
254
+ cell = cells[i];
255
+ columns[cell.gs$cell.col - 1][cell.gs$cell.row - 1] =
256
+ cell.content.$t;
257
+ }
258
+ self.dataFound();
259
+ });
122
260
  }
123
261
  },
124
262
 
@@ -141,7 +279,8 @@
141
279
  * Trim a string from whitespace
142
280
  */
143
281
  trim: function (str) {
144
- return str.replace(/^\s+|\s+$/g, '');
282
+ //return typeof str === 'number' ? str : str.replace(/^\s+|\s+$/g, ''); // fails with spreadsheet
283
+ return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
145
284
  },
146
285
 
147
286
  /**
@@ -176,7 +315,7 @@
176
315
  }
177
316
 
178
317
  } else { // string, continue to determine if it is a date string or really a string
179
- dateVal = Date.parse(val);
318
+ dateVal = this.parseDate(val);
180
319
 
181
320
  if (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
182
321
  columns[col][row] = dateVal;
@@ -188,9 +327,73 @@
188
327
  }
189
328
 
190
329
  }
191
- }
330
+ }
331
+ },
332
+ //*
333
+ dateFormats: {
334
+ 'YYYY-mm-dd': {
335
+ regex: '^([0-9]{4})-([0-9]{2})-([0-9]{2})$',
336
+ parser: function (match) {
337
+ return Date.UTC(+match[1], match[2] - 1, +match[3]);
338
+ }
339
+ }
340
+ },
341
+ // */
342
+ /**
343
+ * Parse a date and return it as a number. Overridable through options.parseDate.
344
+ */
345
+ parseDate: function (val) {
346
+ var parseDate = this.options.parseDate,
347
+ ret,
348
+ key,
349
+ format,
350
+ match;
351
+
352
+ if (parseDate) {
353
+ ret = parseDate;
354
+ }
355
+
356
+ if (typeof val === 'string') {
357
+ for (key in this.dateFormats) {
358
+ format = this.dateFormats[key];
359
+ match = val.match(format.regex);
360
+ if (match) {
361
+ ret = format.parser(match);
362
+ }
363
+ }
364
+ }
365
+ return ret;
366
+ },
367
+
368
+ /**
369
+ * Reorganize rows into columns
370
+ */
371
+ rowsToColumns: function (rows) {
372
+ var row,
373
+ rowsLength,
374
+ col,
375
+ colsLength,
376
+ columns;
377
+
378
+ if (rows) {
379
+ columns = [];
380
+ rowsLength = rows.length;
381
+ for (row = 0; row < rowsLength; row++) {
382
+ colsLength = rows[row].length;
383
+ for (col = 0; col < colsLength; col++) {
384
+ if (!columns[col]) {
385
+ columns[col] = [];
386
+ }
387
+ columns[col][row] = rows[row][col];
388
+ }
389
+ }
390
+ }
391
+ return columns;
192
392
  },
193
393
 
394
+ /**
395
+ * A hook for working directly on the parsed columns
396
+ */
194
397
  parsed: function () {
195
398
  if (this.options.parsed) {
196
399
  this.options.parsed.call(this, this.columns);
@@ -274,4 +477,36 @@
274
477
  Highcharts.data = function (options) {
275
478
  return new Data(options);
276
479
  };
480
+
481
+ // Extend Chart.init so that the Chart constructor accepts a new configuration
482
+ // option group, data.
483
+ Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {
484
+ var chart = this;
485
+
486
+ if (userOptions && userOptions.data) {
487
+ Highcharts.data(Highcharts.extend(userOptions.data, {
488
+ complete: function (dataOptions) {
489
+ var datasets = [];
490
+
491
+ // Don't merge the data arrays themselves
492
+ each(dataOptions.series, function (series, i) {
493
+ datasets[i] = series.data;
494
+ series.data = null;
495
+ });
496
+
497
+ // Do the merge
498
+ userOptions = Highcharts.merge(dataOptions, userOptions);
499
+
500
+ // Re-insert the data
501
+ each(datasets, function (data, i) {
502
+ userOptions.series[i].data = data;
503
+ });
504
+ proceed.call(chart, userOptions, callback);
505
+ }
506
+ }));
507
+ } else {
508
+ proceed.call(chart, userOptions, callback);
509
+ }
510
+ });
511
+
277
512
  }(Highcharts));
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v2.3.3 (2012-10-04)
2
+ * @license Highcharts JS v2.3.5 (2012-12-19)
3
3
  * Exporting module
4
4
  *
5
5
  * (c) 2010-2011 Torstein Hønsi
@@ -10,24 +10,23 @@
10
10
  // JSLint options:
11
11
  /*global Highcharts, document, window, Math, setTimeout */
12
12
 
13
- (function () { // encapsulate
13
+ (function (Highcharts) { // encapsulate
14
14
 
15
15
  // create shortcuts
16
- var HC = Highcharts,
17
- Chart = HC.Chart,
18
- addEvent = HC.addEvent,
19
- removeEvent = HC.removeEvent,
20
- createElement = HC.createElement,
21
- discardElement = HC.discardElement,
22
- css = HC.css,
23
- merge = HC.merge,
24
- each = HC.each,
25
- extend = HC.extend,
16
+ var Chart = Highcharts.Chart,
17
+ addEvent = Highcharts.addEvent,
18
+ removeEvent = Highcharts.removeEvent,
19
+ createElement = Highcharts.createElement,
20
+ discardElement = Highcharts.discardElement,
21
+ css = Highcharts.css,
22
+ merge = Highcharts.merge,
23
+ each = Highcharts.each,
24
+ extend = Highcharts.extend,
26
25
  math = Math,
27
26
  mathMax = math.max,
28
27
  doc = document,
29
28
  win = window,
30
- hasTouch = doc.documentElement.ontouchstart !== undefined,
29
+ isTouchDevice = Highcharts.isTouchDevice,
31
30
  M = 'M',
32
31
  L = 'L',
33
32
  DIV = 'div',
@@ -37,7 +36,8 @@ var HC = Highcharts,
37
36
  ABSOLUTE = 'absolute',
38
37
  PX = 'px',
39
38
  UNDEFINED,
40
- defaultOptions = HC.getOptions();
39
+ symbols = Highcharts.Renderer.prototype.symbols,
40
+ defaultOptions = Highcharts.getOptions();
41
41
 
42
42
  // Add language
43
43
  extend(defaultOptions.lang, {
@@ -60,7 +60,7 @@ defaultOptions.navigation = {
60
60
  padding: '0 5px',
61
61
  background: NONE,
62
62
  color: '#303030',
63
- fontSize: hasTouch ? '14px' : '11px'
63
+ fontSize: isTouchDevice ? '14px' : '11px'
64
64
  },
65
65
  menuItemHoverStyle: {
66
66
  background: '#4572A5',
@@ -172,7 +172,35 @@ defaultOptions.exporting = {
172
172
  }
173
173
  };
174
174
 
175
+ // Add the Highcharts.post utility
176
+ Highcharts.post = function (url, data) {
177
+ var name,
178
+ form;
179
+
180
+ // create the form
181
+ form = createElement('form', {
182
+ method: 'post',
183
+ action: url,
184
+ enctype: 'multipart/form-data'
185
+ }, {
186
+ display: NONE
187
+ }, doc.body);
188
+
189
+ // add the data
190
+ for (name in data) {
191
+ createElement('input', {
192
+ type: HIDDEN,
193
+ name: name,
194
+ value: data[name]
195
+ }, null, form);
196
+ }
197
+
198
+ // submit
199
+ form.submit();
175
200
 
201
+ // clean up
202
+ discardElement(form);
203
+ };
176
204
 
177
205
  extend(Chart.prototype, {
178
206
  /**
@@ -223,12 +251,6 @@ extend(Chart.prototype, {
223
251
  });
224
252
 
225
253
  if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
226
-
227
- // remove image markers
228
- if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
229
- seriesOptions.marker.symbol = 'circle';
230
- }
231
-
232
254
  options.series.push(seriesOptions);
233
255
  }
234
256
  });
@@ -308,43 +330,23 @@ extend(Chart.prototype, {
308
330
  * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
309
331
  */
310
332
  exportChart: function (options, chartOptions) {
311
- var form,
312
- chart = this,
313
- svg = chart.getSVG(merge(chart.options.exporting.chartOptions, chartOptions)); // docs
333
+ var exportingOptions = this.options.exporting,
334
+ svg = this.getSVG(merge(exportingOptions.chartOptions, chartOptions));
314
335
 
315
336
  // merge the options
316
- options = merge(chart.options.exporting, options);
317
-
318
- // create the form
319
- form = createElement('form', {
320
- method: 'post',
321
- action: options.url,
322
- enctype: 'multipart/form-data'
323
- }, {
324
- display: NONE
325
- }, doc.body);
326
-
327
- // add the values
328
- each(['filename', 'type', 'width', 'svg'], function (name) {
329
- createElement('input', {
330
- type: HIDDEN,
331
- name: name,
332
- value: {
333
- filename: options.filename || 'chart',
334
- type: options.type,
335
- width: options.width,
336
- svg: svg
337
- }[name]
338
- }, null, form);
337
+ options = merge(exportingOptions, options);
338
+
339
+ // do the post
340
+ Highcharts.post(options.url, {
341
+ filename: options.filename || 'chart',
342
+ type: options.type,
343
+ width: options.width,
344
+ scale: options.scale || 2,
345
+ svg: svg
339
346
  });
340
347
 
341
- // submit
342
- form.submit();
343
-
344
- // clean up
345
- discardElement(form);
346
348
  },
347
-
349
+
348
350
  /**
349
351
  * Print the chart
350
352
  */
@@ -418,6 +420,7 @@ extend(Chart.prototype, {
418
420
  boxShadow = '3px 3px 10px #888',
419
421
  innerMenu,
420
422
  hide,
423
+ hideTimer,
421
424
  menuStyle;
422
425
 
423
426
  // create the menu only the first time
@@ -444,7 +447,13 @@ extend(Chart.prototype, {
444
447
  css(menu, { display: NONE });
445
448
  };
446
449
 
447
- addEvent(menu, 'mouseleave', hide);
450
+ // Hide the menu some time after mouse leave (#1357)
451
+ addEvent(menu, 'mouseleave', function () {
452
+ hideTimer = setTimeout(hide, 500);
453
+ });
454
+ addEvent(menu, 'mouseenter', function () {
455
+ clearTimeout(hideTimer);
456
+ });
448
457
 
449
458
 
450
459
  // create the items
@@ -462,7 +471,7 @@ extend(Chart.prototype, {
462
471
  cursor: 'pointer'
463
472
  }, menuItemStyle), innerMenu);
464
473
 
465
- div[hasTouch ? 'ontouchstart' : 'onclick'] = function () {
474
+ div.onclick = function () {
466
475
  hide();
467
476
  item.onclick.apply(chart, arguments);
468
477
  };
@@ -511,6 +520,7 @@ extend(Chart.prototype, {
511
520
  box,
512
521
  symbol,
513
522
  button,
523
+ menuKey,
514
524
  borderWidth = btnOptions.borderWidth,
515
525
  boxAttr = {
516
526
  stroke: btnOptions.borderColor
@@ -522,6 +532,11 @@ extend(Chart.prototype, {
522
532
  },
523
533
  symbolSize = btnOptions.symbolSize || 12;
524
534
 
535
+ if (!chart.btnCount) {
536
+ chart.btnCount = 0;
537
+ }
538
+ menuKey = chart.btnCount++;
539
+
525
540
  // Keeps references to the button elements
526
541
  if (!chart.exportDivElements) {
527
542
  chart.exportDivElements = [];
@@ -587,10 +602,11 @@ extend(Chart.prototype, {
587
602
 
588
603
  // add the click event
589
604
  if (menuItems) {
605
+
590
606
  onclick = function () {
591
607
  revert();
592
608
  var bBox = button.getBBox();
593
- chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
609
+ chart.contextMenu('menu' + menuKey, menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
594
610
  };
595
611
  }
596
612
  /*addEvent(button.element, 'click', function() {
@@ -665,7 +681,7 @@ function crisp(arr) {
665
681
  }
666
682
 
667
683
  // Create the export icon
668
- HC.Renderer.prototype.symbols.exportIcon = function (x, y, width, height) {
684
+ symbols.exportIcon = function (x, y, width, height) {
669
685
  return crisp([
670
686
  M, // the disk
671
687
  x, y + width,
@@ -687,7 +703,7 @@ HC.Renderer.prototype.symbols.exportIcon = function (x, y, width, height) {
687
703
  ]);
688
704
  };
689
705
  // Create the print icon
690
- HC.Renderer.prototype.symbols.printIcon = function (x, y, width, height) {
706
+ symbols.printIcon = function (x, y, width, height) {
691
707
  return crisp([
692
708
  M, // the printer
693
709
  x, y + height * 0.7,
@@ -733,4 +749,4 @@ Chart.prototype.callbacks.push(function (chart) {
733
749
  });
734
750
 
735
751
 
736
- }());
752
+ }(Highcharts));