highcharts-js-rails 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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) {