highcharts-rails 2.3.3.1 → 2.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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));