highcharts-js-rails 0.2.1 → 1.0.0

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/.rspec +0 -1
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +7 -0
  6. data/{lib/LICENSE → LICENSE} +1 -1
  7. data/README.md +16 -13
  8. data/highcharts-js-rails.gemspec +3 -5
  9. data/lib/highcharts-js-rails.rb +1 -1
  10. data/lib/highcharts.rb +4 -3
  11. data/lib/highcharts/axis/plot_bands.rb +1 -1
  12. data/lib/highcharts/axis/plot_lines.rb +1 -1
  13. data/lib/highcharts/axis/x.rb +6 -8
  14. data/lib/highcharts/axis/y.rb +6 -6
  15. data/lib/highcharts/base.rb +8 -8
  16. data/lib/highcharts/{color.rb → colors.rb} +0 -0
  17. data/lib/highcharts/engine.rb +2 -0
  18. data/lib/highcharts/legend.rb +2 -2
  19. data/lib/highcharts/plot_options.rb +13 -13
  20. data/lib/highcharts/plot_options/plot_type.rb +7 -7
  21. data/lib/highcharts/plot_options/plot_type/marker.rb +1 -1
  22. data/lib/highcharts/plot_options/plot_type/marker/states.rb +2 -2
  23. data/lib/highcharts/plot_options/plot_type/states.rb +1 -1
  24. data/lib/highcharts/plot_options/plot_type/states/hover.rb +1 -1
  25. data/lib/highcharts/point.rb +2 -2
  26. data/spec/highcharts/axis/x_spec.rb +51 -0
  27. data/spec/highcharts/base_spec.rb +55 -1
  28. data/spec/highcharts/series_spec.rb +25 -0
  29. data/spec/highcharts_spec.rb +65 -0
  30. data/spec/spec_helper.rb +4 -4
  31. data/vendor/assets/javascripts/highcharts-more.js +2290 -1387
  32. data/vendor/assets/javascripts/highcharts.js +2712 -1720
  33. data/vendor/assets/javascripts/highcharts/adapters/mootools.js +15 -30
  34. data/vendor/assets/javascripts/highcharts/adapters/prototype.js +10 -79
  35. data/vendor/assets/javascripts/highcharts/modules/canvas-tools.js +1 -1
  36. data/vendor/assets/javascripts/highcharts/modules/data.js +57 -32
  37. data/vendor/assets/javascripts/highcharts/modules/exporting.js +180 -229
  38. data/vendor/assets/javascripts/highcharts/modules/funnel.js +284 -0
  39. data/vendor/assets/javascripts/highcharts/themes/dark-blue.js +11 -20
  40. data/vendor/assets/javascripts/highcharts/themes/dark-green.js +12 -20
  41. data/vendor/assets/javascripts/highcharts/themes/gray.js +15 -20
  42. data/vendor/assets/javascripts/highcharts/themes/grid.js +8 -0
  43. metadata +34 -38
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @license Highcharts JS v2.3.5 (2012-12-19)
2
+ * @license Highcharts JS v3.0.1 (2013-04-09)
3
3
  * MooTools adapter
4
4
  *
5
- * (c) 2010-2011 Torstein Hønsi
5
+ * (c) 2010-2013 Torstein Hønsi
6
6
  *
7
7
  * License: www.highcharts.com/license
8
8
  */
@@ -190,36 +190,11 @@ win.HighchartsAdapter = {
190
190
  return arr.indexOf(item, from);
191
191
  },
192
192
 
193
- /**
194
- * Deep merge two objects and return a third
195
- */
196
- merge: function () {
197
- var args = arguments,
198
- args13 = [{}], // MooTools 1.3+
199
- i = args.length,
200
- ret;
201
-
202
- if (legacy) {
203
- ret = $merge.apply(null, args);
204
- } else {
205
- while (i--) {
206
- // Boolean argumens should not be merged.
207
- // JQuery explicitly skips this, so we do it here as well.
208
- if (typeof args[i] !== 'boolean') {
209
- args13[i + 1] = args[i];
210
- }
211
- }
212
- ret = Object.merge.apply(Object, args13);
213
- }
214
-
215
- return ret;
216
- },
217
-
218
193
  /**
219
194
  * Get the offset of an element relative to the top left corner of the web page
220
195
  */
221
196
  offset: function (el) {
222
- var offsets = $(el).getOffsets();
197
+ var offsets = el.getPosition(); // #1496
223
198
  return {
224
199
  left: offsets.x,
225
200
  top: offsets.y
@@ -291,6 +266,12 @@ win.HighchartsAdapter = {
291
266
  // create an event object that keeps all functions
292
267
  event = legacyEvent ? new Event(eventArgs) : new DOMEvent(eventArgs);
293
268
  event = $extend(event, eventArguments);
269
+
270
+ // When running an event on the Chart.prototype, MooTools nests the target in event.event
271
+ if (!event.target && event.event) {
272
+ event.target = event.event.target;
273
+ }
274
+
294
275
  // override the preventDefault function to be able to use
295
276
  // this for custom events
296
277
  event.preventDefault = function () {
@@ -309,10 +290,14 @@ win.HighchartsAdapter = {
309
290
  },
310
291
 
311
292
  /**
312
- * Set back e.pageX and e.pageY that MooTools has abstracted away
293
+ * Set back e.pageX and e.pageY that MooTools has abstracted away. #1165, #1346.
313
294
  */
314
295
  washMouseEvent: function (e) {
315
- return e.event || e;
296
+ if (e.page) {
297
+ e.pageX = e.page.x;
298
+ e.pageY = e.page.y;
299
+ }
300
+ return e;
316
301
  },
317
302
 
318
303
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v2.3.5 (2012-12-19)
2
+ * @license Highcharts JS v3.0.1 (2013-04-09)
3
3
  * Prototype adapter
4
4
  *
5
5
  * @author Michael Nelson, Torstein Hønsi.
@@ -83,7 +83,10 @@ return {
83
83
  }
84
84
 
85
85
  if (element.attr) { // SVGElement
86
- element.attr(this.options.attribute, position);
86
+
87
+ if (element.element) { // If not, it has been destroyed (#1405)
88
+ element.attr(this.options.attribute, position);
89
+ }
87
90
 
88
91
  } else { // HTML, #409
89
92
  obj = {};
@@ -95,7 +98,9 @@ return {
95
98
  finish: function () {
96
99
  // Delete the property that holds this animation now that it is finished.
97
100
  // Both canceled animations and complete ones gets a 'finish' call.
98
- delete this.element._highchart_animation[this.key];
101
+ if (this.element && this.element._highchart_animation) { // #1405
102
+ delete this.element._highchart_animation[this.key];
103
+ }
99
104
  }
100
105
  });
101
106
  }
@@ -261,82 +266,6 @@ return {
261
266
  return arr.map(fn);
262
267
  },
263
268
 
264
- // deep merge. merge({a : 'a', b : {b1 : 'b1', b2 : 'b2'}}, {b : {b2 : 'b2_prime'}, c : 'c'}) => {a : 'a', b : {b1 : 'b1', b2 : 'b2_prime'}, c : 'c'}
265
- /*merge: function(){
266
- function doCopy(copy, original) {
267
- var value,
268
- key,
269
- undef,
270
- nil,
271
- same,
272
- obj,
273
- arr,
274
- node;
275
-
276
- for (key in original) {
277
- value = original[key];
278
- undef = typeof(value) === 'undefined';
279
- nil = value === null;
280
- same = original === copy[key];
281
-
282
- if (undef || nil || same) {
283
- continue;
284
- }
285
-
286
- obj = typeof(value) === 'object';
287
- arr = value && obj && value.constructor == Array;
288
- node = !!value.nodeType;
289
-
290
- if (obj && !arr && !node) {
291
- copy[key] = doCopy(typeof copy[key] == 'object' ? copy[key] : {}, value);
292
- }
293
- else {
294
- copy[key] = original[key];
295
- }
296
- }
297
- return copy;
298
- }
299
-
300
- var args = arguments, retVal = {};
301
-
302
- for (var i = 0; i < args.length; i++) {
303
- retVal = doCopy(retVal, args[i]);
304
- }
305
-
306
- return retVal;
307
- },*/
308
- merge: function () { // the built-in prototype merge function doesn't do deep copy
309
- function doCopy(copy, original) {
310
- var value, key;
311
-
312
- for (key in original) {
313
- value = original[key];
314
- if (value && typeof value === 'object' && value.constructor !== Array &&
315
- typeof value.nodeType !== 'number') {
316
- copy[key] = doCopy(copy[key] || {}, value); // copy
317
-
318
- } else {
319
- copy[key] = original[key];
320
- }
321
- }
322
- return copy;
323
- }
324
-
325
- function merge() {
326
- var args = arguments,
327
- i,
328
- retVal = {};
329
-
330
- for (i = 0; i < args.length; i++) {
331
- retVal = doCopy(retVal, args[i]);
332
-
333
- }
334
- return retVal;
335
- }
336
-
337
- return merge.apply(this, arguments);
338
- },
339
-
340
269
  // extend an object to handle highchart events (highchart objects, not svg elements).
341
270
  // this is a very simple way of handling events but whatever, it works (i think)
342
271
  _extend: function (object) {
@@ -360,6 +289,7 @@ return {
360
289
  }
361
290
  },
362
291
  _highcharts_fire: function (name, args) {
292
+ var target = this;
363
293
  (this._highchart_events[name] || []).each(function (fn) {
364
294
  // args is never null here
365
295
  if (args.stopped) {
@@ -370,6 +300,7 @@ return {
370
300
  args.preventDefault = function () {
371
301
  args.defaultPrevented = true;
372
302
  };
303
+ args.target = target;
373
304
 
374
305
  // If the event handler return false, prevent the default handler from executing
375
306
  if (fn.bind(this)(args) === false) {
@@ -2908,7 +2908,7 @@ if (CanvasRenderingContext2D) {
2908
2908
  });
2909
2909
  }
2910
2910
  }/**
2911
- * @license Highcharts JS v2.3.5 (2012-12-19)
2911
+ * @license Highcharts JS v3.0.1 (2013-04-09)
2912
2912
  * CanVGRenderer Extension module
2913
2913
  *
2914
2914
  * (c) 2011-2012 Torstein Hønsi, Erik Olsson
@@ -1,7 +1,8 @@
1
1
  /**
2
- * @license Data plugin for Highcharts v0.1
2
+ * @license Data plugin for Highcharts
3
3
  *
4
- * (c) 2012 Torstein Hønsi
4
+ * (c) 2012-2013 Torstein Hønsi
5
+ * Last revision 2012-11-27
5
6
  *
6
7
  * License: www.highcharts.com/license
7
8
  */
@@ -44,14 +45,14 @@
44
45
  * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
45
46
  * for general information on GS.
46
47
  *
47
- * - googleSpreadsheetKey : String
48
+ * - googleSpreadsheetWorksheet : String
48
49
  * The Google Spreadsheet worksheet. The available id's can be read from
49
50
  * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
50
51
  *
51
- * - itemDilimiter : String
52
+ * - itemDelimiter : String
52
53
  * Item or cell delimiter for parsing CSV. Defaults to ",".
53
54
  *
54
- * - lineDilimiter : String
55
+ * - lineDelimiter : String
55
56
  * Line delimiter for parsing CSV. Defaults to "\n".
56
57
  *
57
58
  * - parsed : Function
@@ -76,7 +77,9 @@
76
77
  * endRow, startColumn and endColumn to delimit what part of the table is used.
77
78
  */
78
79
 
80
+ // JSLint options:
79
81
  /*global jQuery */
82
+
80
83
  (function (Highcharts) {
81
84
 
82
85
  // Utilities
@@ -118,7 +121,6 @@
118
121
  },
119
122
 
120
123
  dataFound: function () {
121
-
122
124
  // Interpret the values into right types
123
125
  this.parseTypes();
124
126
 
@@ -137,14 +139,16 @@
137
139
  * Parse a CSV input string
138
140
  */
139
141
  parseCSV: function () {
140
- var options = this.options,
142
+ var self = this,
143
+ options = this.options,
141
144
  csv = options.csv,
142
145
  columns = this.columns,
143
146
  startRow = options.startRow || 0,
144
147
  endRow = options.endRow || Number.MAX_VALUE,
145
148
  startColumn = options.startColumn || 0,
146
149
  endColumn = options.endColumn || Number.MAX_VALUE,
147
- lines;
150
+ lines,
151
+ activeRowNo = 0;
148
152
 
149
153
  if (csv) {
150
154
 
@@ -154,19 +158,26 @@
154
158
  .split(options.lineDelimiter || "\n");
155
159
 
156
160
  each(lines, function (line, rowNo) {
157
- if (rowNo >= startRow && rowNo <= endRow) {
158
- var items = line.split(options.itemDelimiter || ',');
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 || ',');
159
168
  each(items, function (item, colNo) {
160
169
  if (colNo >= startColumn && colNo <= endColumn) {
161
170
  if (!columns[colNo - startColumn]) {
162
171
  columns[colNo - startColumn] = [];
163
172
  }
164
173
 
165
- columns[colNo - startColumn][rowNo - startRow] = item;
174
+ columns[colNo - startColumn][activeRowNo] = item;
166
175
  }
167
176
  });
177
+ activeRowNo += 1;
168
178
  }
169
- });
179
+ });
180
+
170
181
  this.dataFound();
171
182
  }
172
183
  },
@@ -213,13 +224,18 @@
213
224
  /**
214
225
  * TODO:
215
226
  * - switchRowsAndColumns
216
- * - startRow, endRow etc.
217
227
  */
218
228
  parseGoogleSpreadsheet: function () {
219
229
  var self = this,
220
230
  options = this.options,
221
231
  googleSpreadsheetKey = options.googleSpreadsheetKey,
222
- columns = this.columns;
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
223
239
 
224
240
  if (googleSpreadsheetKey) {
225
241
  jQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' +
@@ -245,15 +261,28 @@
245
261
 
246
262
  // Set up arrays containing the column data
247
263
  for (i = 0; i < colCount; i++) {
248
- columns[i] = new Array(rowCount);
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
+ }
249
271
  }
250
272
 
251
273
  // Loop over the cells and assign the value to the right
252
274
  // place in the column arrays
253
275
  for (i = 0; i < cellCount; i++) {
254
276
  cell = cells[i];
255
- columns[cell.gs$cell.col - 1][cell.gs$cell.row - 1] =
256
- cell.content.$t;
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
+ }
257
286
  }
258
287
  self.dataFound();
259
288
  });
@@ -279,7 +308,6 @@
279
308
  * Trim a string from whitespace
280
309
  */
281
310
  trim: function (str) {
282
- //return typeof str === 'number' ? str : str.replace(/^\s+|\s+$/g, ''); // fails with spreadsheet
283
311
  return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
284
312
  },
285
313
 
@@ -302,6 +330,7 @@
302
330
  val = columns[col][row];
303
331
  floatVal = parseFloat(val);
304
332
  trimVal = this.trim(val);
333
+
305
334
  /*jslint eqeq: true*/
306
335
  if (trimVal == floatVal) { // is numeric
307
336
  /*jslint eqeq: false*/
@@ -322,7 +351,7 @@
322
351
  columns[col].isDatetime = true;
323
352
 
324
353
  } else { // string
325
- columns[col][row] = trimVal;
354
+ columns[col][row] = trimVal === '' ? null : trimVal;
326
355
  }
327
356
  }
328
357
 
@@ -350,7 +379,7 @@
350
379
  match;
351
380
 
352
381
  if (parseDate) {
353
- ret = parseDate;
382
+ ret = parseDate(val);
354
383
  }
355
384
 
356
385
  if (typeof val === 'string') {
@@ -486,21 +515,17 @@
486
515
  if (userOptions && userOptions.data) {
487
516
  Highcharts.data(Highcharts.extend(userOptions.data, {
488
517
  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
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
+
497
526
  // Do the merge
498
527
  userOptions = Highcharts.merge(dataOptions, userOptions);
499
-
500
- // Re-insert the data
501
- each(datasets, function (data, i) {
502
- userOptions.series[i].data = data;
503
- });
528
+
504
529
  proceed.call(chart, userOptions, callback);
505
530
  }
506
531
  }));
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @license Highcharts JS v2.3.5 (2012-12-19)
2
+ * @license Highcharts JS v3.0.1 (2013-04-09)
3
3
  * Exporting module
4
4
  *
5
- * (c) 2010-2011 Torstein Hønsi
5
+ * (c) 2010-2013 Torstein Hønsi
6
6
  *
7
7
  * License: www.highcharts.com/license
8
8
  */
@@ -37,16 +37,17 @@ var Chart = Highcharts.Chart,
37
37
  PX = 'px',
38
38
  UNDEFINED,
39
39
  symbols = Highcharts.Renderer.prototype.symbols,
40
- defaultOptions = Highcharts.getOptions();
40
+ defaultOptions = Highcharts.getOptions(),
41
+ buttonOffset;
41
42
 
42
43
  // Add language
43
44
  extend(defaultOptions.lang, {
45
+ printChart: 'Print chart',
44
46
  downloadPNG: 'Download PNG image',
45
47
  downloadJPEG: 'Download JPEG image',
46
48
  downloadPDF: 'Download PDF document',
47
49
  downloadSVG: 'Download SVG vector image',
48
- exportButtonTitle: 'Export to raster or vector image',
49
- printButtonTitle: 'Print the chart'
50
+ contextButtonTitle: 'Chart context menu'
50
51
  });
51
52
 
52
53
  // Buttons and menus are collected in a separate config option set called 'navigation'.
@@ -54,10 +55,11 @@ var Chart = Highcharts.Chart,
54
55
  defaultOptions.navigation = {
55
56
  menuStyle: {
56
57
  border: '1px solid #A0A0A0',
57
- background: '#FFFFFF'
58
+ background: '#FFFFFF',
59
+ padding: '5px 0'
58
60
  },
59
61
  menuItemStyle: {
60
- padding: '0 5px',
62
+ padding: '0 10px',
61
63
  background: NONE,
62
64
  color: '#303030',
63
65
  fontSize: isTouchDevice ? '14px' : '11px'
@@ -68,31 +70,22 @@ defaultOptions.navigation = {
68
70
  },
69
71
 
70
72
  buttonOptions: {
71
- align: 'right',
72
- backgroundColor: {
73
- linearGradient: [0, 0, 0, 20],
74
- stops: [
75
- [0.4, '#F7F7F7'],
76
- [0.6, '#E3E3E3']
77
- ]
78
- },
79
- borderColor: '#B0B0B0',
80
- borderRadius: 3,
81
- borderWidth: 1,
82
- //enabled: true,
83
- height: 20,
84
- hoverBorderColor: '#909090',
85
- hoverSymbolFill: '#81A7CF',
86
- hoverSymbolStroke: '#4572A5',
87
73
  symbolFill: '#E0E0E0',
88
- //symbolSize: 12,
89
- symbolStroke: '#A0A0A0',
90
- //symbolStrokeWidth: 1,
91
- symbolX: 11.5,
74
+ symbolSize: 14,
75
+ symbolStroke: '#666',
76
+ symbolStrokeWidth: 3,
77
+ symbolX: 12.5,
92
78
  symbolY: 10.5,
79
+ align: 'right',
80
+ buttonSpacing: 3,
81
+ height: 22,
82
+ // text: null,
83
+ theme: {
84
+ fill: 'white', // capture hover
85
+ stroke: 'none'
86
+ },
93
87
  verticalAlign: 'top',
94
- width: 24,
95
- y: 10
88
+ width: 24
96
89
  }
97
90
  };
98
91
 
@@ -104,17 +97,21 @@ defaultOptions.exporting = {
104
97
  //filename: 'chart',
105
98
  type: 'image/png',
106
99
  url: 'http://export.highcharts.com/',
107
- width: 800,
100
+ //width: undefined,
101
+ //scale: 2
108
102
  buttons: {
109
- exportButton: {
110
- //enabled: true,
111
- symbol: 'exportIcon',
112
- x: -10,
113
- symbolFill: '#A8BF77',
114
- hoverSymbolFill: '#768F3E',
115
- _id: 'exportButton',
116
- _titleKey: 'exportButtonTitle',
103
+ contextButton: {
104
+ //x: -10,
105
+ symbol: 'menu',
106
+ _titleKey: 'contextButtonTitle',
117
107
  menuItems: [{
108
+ textKey: 'printChart',
109
+ onclick: function () {
110
+ this.print();
111
+ }
112
+ }, {
113
+ separator: true
114
+ }, {
118
115
  textKey: 'downloadPNG',
119
116
  onclick: function () {
120
117
  this.exportChart();
@@ -155,19 +152,6 @@ defaultOptions.exporting = {
155
152
  }
156
153
  } // */
157
154
  ]
158
-
159
- },
160
- printButton: {
161
- //enabled: true,
162
- symbol: 'printIcon',
163
- x: -36,
164
- symbolFill: '#B5C9DF',
165
- hoverSymbolFill: '#779ABF',
166
- _id: 'printButton',
167
- _titleKey: 'printButtonTitle',
168
- onclick: function () {
169
- this.print();
170
- }
171
155
  }
172
156
  }
173
157
  };
@@ -203,6 +187,7 @@ Highcharts.post = function (url, data) {
203
187
  };
204
188
 
205
189
  extend(Chart.prototype, {
190
+
206
191
  /**
207
192
  * Return an SVG representation of the chart
208
193
  *
@@ -214,6 +199,10 @@ extend(Chart.prototype, {
214
199
  sandbox,
215
200
  svg,
216
201
  seriesOptions,
202
+ sourceWidth,
203
+ sourceHeight,
204
+ cssWidth,
205
+ cssHeight,
217
206
  options = merge(chart.options, additionalOptions); // copy the options and add extra options
218
207
 
219
208
  // IE compatibility hack for generating SVG content that it doesn't really understand
@@ -232,11 +221,26 @@ extend(Chart.prototype, {
232
221
  width: chart.chartWidth + PX,
233
222
  height: chart.chartHeight + PX
234
223
  }, doc.body);
224
+
225
+ // get the source size
226
+ cssWidth = chart.renderTo.style.width;
227
+ cssHeight = chart.renderTo.style.height;
228
+ sourceWidth = options.exporting.sourceWidth ||
229
+ options.chart.width ||
230
+ (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||
231
+ 600;
232
+ sourceHeight = options.exporting.sourceHeight ||
233
+ options.chart.height ||
234
+ (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||
235
+ 400;
235
236
 
236
237
  // override some options
237
238
  extend(options.chart, {
239
+ animation: false,
238
240
  renderTo: sandbox,
239
- forExport: true
241
+ forExport: true,
242
+ width: sourceWidth,
243
+ height: sourceHeight
240
244
  });
241
245
  options.exporting.enabled = false; // hide buttons in print
242
246
  options.chart.plotBackgroundImage = null; // the converter doesn't handle images
@@ -256,7 +260,7 @@ extend(Chart.prototype, {
256
260
  });
257
261
 
258
262
  // generate the chart copy
259
- chartCopy = new Highcharts.Chart(options);
263
+ chartCopy = new Highcharts.Chart(options, chart.callback);
260
264
 
261
265
  // reflect axis extremes in the export
262
266
  each(['xAxis', 'yAxis'], function (axisType) {
@@ -286,7 +290,6 @@ extend(Chart.prototype, {
286
290
  .replace(/isShadow="[^"]+"/g, '')
287
291
  .replace(/symbolName="[^"]+"/g, '')
288
292
  .replace(/jQuery[0-9]+="[^"]+"/g, '')
289
- .replace(/isTracker="[^"]+"/g, '')
290
293
  .replace(/url\([^#]+#/g, 'url(#')
291
294
  .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
292
295
  .replace(/ href=/g, ' xlink:href=')
@@ -330,17 +333,30 @@ extend(Chart.prototype, {
330
333
  * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
331
334
  */
332
335
  exportChart: function (options, chartOptions) {
333
- var exportingOptions = this.options.exporting,
334
- svg = this.getSVG(merge(exportingOptions.chartOptions, chartOptions));
336
+ options = options || {};
337
+
338
+ var chart = this,
339
+ chartExportingOptions = chart.options.exporting,
340
+ svg = chart.getSVG(merge(
341
+ { chart: { borderRadius: 0 } },
342
+ chartExportingOptions,
343
+ chartOptions,
344
+ {
345
+ exporting: {
346
+ sourceWidth: options.sourceWidth || chartExportingOptions.sourceWidth, // docs: option and parameter in exportChart()
347
+ sourceHeight: options.sourceHeight || chartExportingOptions.sourceHeight // docs
348
+ }
349
+ }
350
+ ));
335
351
 
336
352
  // merge the options
337
- options = merge(exportingOptions, options);
353
+ options = merge(chart.options.exporting, options);
338
354
 
339
355
  // do the post
340
356
  Highcharts.post(options.url, {
341
357
  filename: options.filename || 'chart',
342
358
  type: options.type,
343
- width: options.width,
359
+ width: options.width || 0, // IE8 fails to post undefined correctly, so use 0
344
360
  scale: options.scale || 2,
345
361
  svg: svg
346
362
  });
@@ -377,6 +393,7 @@ extend(Chart.prototype, {
377
393
  body.appendChild(container);
378
394
 
379
395
  // print
396
+ win.focus(); // #1510
380
397
  win.print();
381
398
 
382
399
  // allow the browser to prepare before reverting
@@ -408,7 +425,7 @@ extend(Chart.prototype, {
408
425
  * @param {Number} width The width of the opener button
409
426
  * @param {Number} height The height of the opener button
410
427
  */
411
- contextMenu: function (name, items, x, y, width, height) {
428
+ contextMenu: function (name, items, x, y, width, height, button) {
412
429
  var chart = this,
413
430
  navOptions = chart.options.navigation,
414
431
  menuItemStyle = navOptions.menuItemStyle,
@@ -445,6 +462,9 @@ extend(Chart.prototype, {
445
462
  // hide on mouse out
446
463
  hide = function () {
447
464
  css(menu, { display: NONE });
465
+ if (button) {
466
+ button.setState(0);
467
+ }
448
468
  };
449
469
 
450
470
  // Hide the menu some time after mouse leave (#1357)
@@ -459,25 +479,27 @@ extend(Chart.prototype, {
459
479
  // create the items
460
480
  each(items, function (item) {
461
481
  if (item) {
462
- var div = createElement(DIV, {
463
- onmouseover: function () {
464
- css(this, navOptions.menuItemHoverStyle);
465
- },
466
- onmouseout: function () {
467
- css(this, menuItemStyle);
468
- },
469
- innerHTML: item.text || chart.options.lang[item.textKey]
470
- }, extend({
471
- cursor: 'pointer'
472
- }, menuItemStyle), innerMenu);
473
-
474
- div.onclick = function () {
475
- hide();
476
- item.onclick.apply(chart, arguments);
477
- };
482
+ var element = item.separator ?
483
+ createElement('hr', null, null, innerMenu) :
484
+ createElement(DIV, {
485
+ onmouseover: function () {
486
+ css(this, navOptions.menuItemHoverStyle);
487
+ },
488
+ onmouseout: function () {
489
+ css(this, menuItemStyle);
490
+ },
491
+ onclick: function () {
492
+ hide();
493
+ item.onclick.apply(chart, arguments);
494
+ },
495
+ innerHTML: item.text || chart.options.lang[item.textKey]
496
+ }, extend({
497
+ cursor: 'pointer'
498
+ }, menuItemStyle), innerMenu);
499
+
478
500
 
479
501
  // Keep references to menu divs to be able to destroy them
480
- chart.exportDivElements.push(div);
502
+ chart.exportDivElements.push(element);
481
503
  }
482
504
  });
483
505
 
@@ -515,22 +537,14 @@ extend(Chart.prototype, {
515
537
  btnOptions = merge(chart.options.navigation.buttonOptions, options),
516
538
  onclick = btnOptions.onclick,
517
539
  menuItems = btnOptions.menuItems,
518
- buttonWidth = btnOptions.width,
519
- buttonHeight = btnOptions.height,
520
- box,
521
540
  symbol,
522
541
  button,
523
- menuKey,
524
- borderWidth = btnOptions.borderWidth,
525
- boxAttr = {
526
- stroke: btnOptions.borderColor
527
-
528
- },
529
542
  symbolAttr = {
530
543
  stroke: btnOptions.symbolStroke,
531
544
  fill: btnOptions.symbolFill
532
545
  },
533
- symbolSize = btnOptions.symbolSize || 12;
546
+ symbolSize = btnOptions.symbolSize || 12,
547
+ menuKey;
534
548
 
535
549
  if (!chart.btnCount) {
536
550
  chart.btnCount = 0;
@@ -547,99 +561,85 @@ extend(Chart.prototype, {
547
561
  return;
548
562
  }
549
563
 
550
- // element to capture the click
551
- function revert() {
552
- symbol.attr(symbolAttr);
553
- box.attr(boxAttr);
564
+
565
+ var attr = btnOptions.theme,
566
+ states = attr.states,
567
+ hover = states && states.hover,
568
+ select = states && states.select,
569
+ callback;
570
+
571
+ delete attr.states;
572
+
573
+ if (onclick) {
574
+ callback = function () {
575
+ onclick.apply(chart, arguments);
576
+ };
577
+
578
+ } else if (menuItems) {
579
+ callback = function () {
580
+ chart.contextMenu(
581
+ 'contextmenu',
582
+ menuItems,
583
+ button.translateX,
584
+ button.translateY,
585
+ button.width,
586
+ button.height,
587
+ button
588
+ );
589
+ button.setState(2);
590
+ };
591
+ }
592
+
593
+
594
+ if (btnOptions.text && btnOptions.symbol) {
595
+ attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);
596
+
597
+ } else if (!btnOptions.text) {
598
+ extend(attr, {
599
+ width: btnOptions.width,
600
+ height: btnOptions.height,
601
+ padding: 0
602
+ });
554
603
  }
555
604
 
556
- // the box border
557
- box = renderer.rect(
558
- 0,
559
- 0,
560
- buttonWidth,
561
- buttonHeight,
562
- btnOptions.borderRadius,
563
- borderWidth
564
- )
565
- //.translate(buttonLeft, buttonTop) // to allow gradients
566
- .align(btnOptions, true)
567
- .attr(extend({
568
- fill: btnOptions.backgroundColor,
569
- 'stroke-width': borderWidth,
570
- zIndex: 19
571
- }, boxAttr)).add();
572
-
573
- // the invisible element to track the clicks
574
- button = renderer.rect(
575
- 0,
576
- 0,
577
- buttonWidth,
578
- buttonHeight,
579
- 0
580
- )
581
- .align(btnOptions)
605
+ button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
582
606
  .attr({
583
- id: btnOptions._id,
584
- fill: 'rgba(255, 255, 255, 0.001)',
585
607
  title: chart.options.lang[btnOptions._titleKey],
586
- zIndex: 21
587
- }).css({
588
- cursor: 'pointer'
589
- })
590
- .on('mouseover', function () {
591
- symbol.attr({
592
- stroke: btnOptions.hoverSymbolStroke,
593
- fill: btnOptions.hoverSymbolFill
594
- });
595
- box.attr({
596
- stroke: btnOptions.hoverBorderColor
597
- });
598
- })
599
- .on('mouseout', revert)
600
- .on('click', revert)
601
- .add();
602
-
603
- // add the click event
604
- if (menuItems) {
605
-
606
- onclick = function () {
607
- revert();
608
- var bBox = button.getBBox();
609
- chart.contextMenu('menu' + menuKey, menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
610
- };
608
+ 'stroke-linecap': 'round'
609
+ });
610
+
611
+ if (btnOptions.symbol) {
612
+ symbol = renderer.symbol(
613
+ btnOptions.symbol,
614
+ btnOptions.symbolX - (symbolSize / 2),
615
+ btnOptions.symbolY - (symbolSize / 2),
616
+ symbolSize,
617
+ symbolSize
618
+ )
619
+ .attr(extend(symbolAttr, {
620
+ 'stroke-width': btnOptions.symbolStrokeWidth || 1,
621
+ zIndex: 1
622
+ })).add(button);
611
623
  }
612
- /*addEvent(button.element, 'click', function() {
613
- onclick.apply(chart, arguments);
614
- });*/
615
- button.on('click', function () {
616
- onclick.apply(chart, arguments);
617
- });
618
624
 
619
- // the icon
620
- symbol = renderer.symbol(
621
- btnOptions.symbol,
622
- btnOptions.symbolX - (symbolSize / 2),
623
- btnOptions.symbolY - (symbolSize / 2),
624
- symbolSize,
625
- symbolSize
626
- )
627
- .align(btnOptions, true)
628
- .attr(extend(symbolAttr, {
629
- 'stroke-width': btnOptions.symbolStrokeWidth || 1,
630
- zIndex: 20
631
- })).add();
632
-
633
- // Keep references to the renderer element so to be able to destroy them later.
634
- chart.exportSVGElements.push(box, button, symbol);
625
+ button.add()
626
+ .align(extend(btnOptions, {
627
+ width: button.width,
628
+ x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654
629
+ }), true, 'spacingBox');
630
+
631
+ buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);
632
+
633
+ chart.exportSVGElements.push(button, symbol);
634
+
635
635
  },
636
636
 
637
637
  /**
638
638
  * Destroy the buttons.
639
639
  */
640
- destroyExport: function () {
641
- var i,
642
- chart = this,
640
+ destroyExport: function (e) {
641
+ var chart = e.target,
642
+ i,
643
643
  elem;
644
644
 
645
645
  // Destroy the extra buttons added
@@ -666,76 +666,27 @@ extend(Chart.prototype, {
666
666
  }
667
667
  });
668
668
 
669
- /**
670
- * Crisp for 1px stroke width, which is default. In the future, consider a smarter,
671
- * global function.
672
- */
673
- function crisp(arr) {
674
- var i = arr.length;
675
- while (i--) {
676
- if (typeof arr[i] === 'number') {
677
- arr[i] = Math.round(arr[i]) - 0.5;
678
- }
679
- }
669
+
670
+ symbols.menu = function (x, y, width, height) {
671
+ var arr = [
672
+ M, x, y + 2.5,
673
+ L, x + width, y + 2.5,
674
+ M, x, y + height / 2 + 0.5,
675
+ L, x + width, y + height / 2 + 0.5,
676
+ M, x, y + height - 1.5,
677
+ L, x + width, y + height - 1.5
678
+ ];
680
679
  return arr;
681
- }
682
-
683
- // Create the export icon
684
- symbols.exportIcon = function (x, y, width, height) {
685
- return crisp([
686
- M, // the disk
687
- x, y + width,
688
- L,
689
- x + width, y + height,
690
- x + width, y + height * 0.8,
691
- x, y + height * 0.8,
692
- 'Z',
693
- M, // the arrow
694
- x + width * 0.5, y + height * 0.8,
695
- L,
696
- x + width * 0.8, y + height * 0.4,
697
- x + width * 0.4, y + height * 0.4,
698
- x + width * 0.4, y,
699
- x + width * 0.6, y,
700
- x + width * 0.6, y + height * 0.4,
701
- x + width * 0.2, y + height * 0.4,
702
- 'Z'
703
- ]);
704
- };
705
- // Create the print icon
706
- symbols.printIcon = function (x, y, width, height) {
707
- return crisp([
708
- M, // the printer
709
- x, y + height * 0.7,
710
- L,
711
- x + width, y + height * 0.7,
712
- x + width, y + height * 0.4,
713
- x, y + height * 0.4,
714
- 'Z',
715
- M, // the upper sheet
716
- x + width * 0.2, y + height * 0.4,
717
- L,
718
- x + width * 0.2, y,
719
- x + width * 0.8, y,
720
- x + width * 0.8, y + height * 0.4,
721
- 'Z',
722
- M, // the lower sheet
723
- x + width * 0.2, y + height * 0.7,
724
- L,
725
- x, y + height,
726
- x + width, y + height,
727
- x + width * 0.8, y + height * 0.7,
728
- 'Z'
729
- ]);
730
680
  };
731
681
 
732
-
733
682
  // Add the buttons on chart load
734
683
  Chart.prototype.callbacks.push(function (chart) {
735
684
  var n,
736
685
  exportingOptions = chart.options.exporting,
737
686
  buttons = exportingOptions.buttons;
738
687
 
688
+ buttonOffset = 0;
689
+
739
690
  if (exportingOptions.enabled !== false) {
740
691
 
741
692
  for (n in buttons) {