highcharts-rails 5.0.7 → 5.0.8

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 (26) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +41 -0
  3. data/README.markdown +3 -6
  4. data/app/assets/javascripts/highcharts.js +758 -455
  5. data/app/assets/javascripts/highcharts/highcharts-3d.js +58 -50
  6. data/app/assets/javascripts/highcharts/highcharts-more.js +21 -35
  7. data/app/assets/javascripts/highcharts/modules/accessibility.js +69 -39
  8. data/app/assets/javascripts/highcharts/modules/annotations.js +2 -2
  9. data/app/assets/javascripts/highcharts/modules/boost.js +2316 -337
  10. data/app/assets/javascripts/highcharts/modules/broken-axis.js +17 -3
  11. data/app/assets/javascripts/highcharts/modules/data.js +2 -2
  12. data/app/assets/javascripts/highcharts/modules/drilldown.js +6 -6
  13. data/app/assets/javascripts/highcharts/modules/exporting.js +4 -4
  14. data/app/assets/javascripts/highcharts/modules/funnel.js +4 -4
  15. data/app/assets/javascripts/highcharts/modules/grid-axis.js +6 -6
  16. data/app/assets/javascripts/highcharts/modules/heatmap.js +10 -7
  17. data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +2 -2
  18. data/app/assets/javascripts/highcharts/modules/offline-exporting.js +3 -3
  19. data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +3 -3
  20. data/app/assets/javascripts/highcharts/modules/series-label.js +2 -2
  21. data/app/assets/javascripts/highcharts/modules/solid-gauge.js +40 -4
  22. data/app/assets/javascripts/highcharts/modules/stock.js +100 -81
  23. data/app/assets/javascripts/highcharts/modules/treemap.js +36 -9
  24. data/app/assets/javascripts/highcharts/modules/xrange-series.js +2 -2
  25. data/lib/highcharts/version.rb +1 -1
  26. metadata +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v5.0.7 (2017-01-17)
2
+ * @license Highcharts JS v5.0.8 (2017-03-08)
3
3
  * Accessibility module
4
4
  *
5
5
  * (c) 2010-2016 Highsoft AS
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * License: www.highcharts.com/license
9
9
  */
10
+ 'use strict';
10
11
  (function(factory) {
11
12
  if (typeof module === 'object' && module.exports) {
12
13
  module.exports = factory;
@@ -23,7 +24,6 @@
23
24
  *
24
25
  * License: www.highcharts.com/license
25
26
  */
26
- 'use strict';
27
27
 
28
28
  var win = H.win,
29
29
  doc = win.document,
@@ -71,9 +71,20 @@
71
71
  funnel: ' Funnel charts are used to display reduction of data in stages. ',
72
72
  pyramid: ' Pyramid charts consist of a single pyramid with item heights corresponding to each point value. ',
73
73
  waterfall: ' A waterfall chart is a column chart where each column contributes towards a total end value. '
74
- },
75
- commonKeys = ['name', 'id', 'category', 'x', 'value', 'y'],
76
- specialKeys = ['z', 'open', 'high', 'q3', 'median', 'q1', 'low', 'close']; // Tell user about all properties if points have one of these defined
74
+ };
75
+
76
+ // If a point has one of the special keys defined, we expose all keys to the
77
+ // screen reader.
78
+ H.Series.prototype.commonKeys = ['name', 'id', 'category', 'x', 'value', 'y'];
79
+ H.Series.prototype.specialKeys = [
80
+ 'z', 'open', 'high', 'q3', 'median', 'q1', 'low', 'close'
81
+ ];
82
+
83
+ // A pie is always simple. Don't quote me on that.
84
+ if (H.seriesTypes.pie) {
85
+ H.seriesTypes.pie.prototype.specialKeys = [];
86
+ }
87
+
77
88
 
78
89
  // Default a11y options
79
90
  H.setOptions({
@@ -82,7 +93,7 @@
82
93
  pointDescriptionThreshold: 30, // set to false to disable
83
94
  keyboardNavigation: {
84
95
  enabled: true
85
- // skipNullPoints: false
96
+ // skipNullPoints: false
86
97
  }
87
98
  // describeSingleSeries: false
88
99
  }
@@ -114,7 +125,7 @@
114
125
  // Utility function to attempt to fake a click event on an element
115
126
  function fakeClickEvent(element) {
116
127
  var fakeEvent;
117
- if (element && element.onclick) {
128
+ if (element && element.onclick && doc.createEvent) {
118
129
  fakeEvent = doc.createEvent('Events');
119
130
  fakeEvent.initEvent('click', true, false);
120
131
  element.onclick(fakeEvent);
@@ -164,7 +175,7 @@
164
175
 
165
176
  // Return string with information about series
166
177
  H.Series.prototype.buildSeriesInfoString = function() {
167
- var typeInfo = typeToSeriesMap[this.type] || typeToSeriesMap.default,
178
+ var typeInfo = typeToSeriesMap[this.type] || typeToSeriesMap['default'], // eslint-disable-line dot-notation
168
179
  description = this.description || this.options.description;
169
180
  return (this.name ? this.name + ', ' : '') +
170
181
  (this.chart.types.length === 1 ? typeInfo[0] : 'series') + ' ' + (this.index + 1) + ' of ' + (this.chart.series.length) +
@@ -181,25 +192,21 @@
181
192
  series = point.series,
182
193
  a11yOptions = series.chart.options.accessibility,
183
194
  infoString = '',
184
- hasSpecialKey = false,
185
195
  dateTimePoint = series.xAxis && series.xAxis.isDatetimeAxis,
186
196
  timeDesc = dateTimePoint && dateFormat(a11yOptions.pointDateFormatter && a11yOptions.pointDateFormatter(point) || a11yOptions.pointDateFormat ||
187
- H.Tooltip.prototype.getXDateFormat(point, series.chart.options.tooltip, series.xAxis), point.x);
188
-
189
- each(specialKeys, function(key) {
190
- if (point[key] !== undefined) {
191
- hasSpecialKey = true;
192
- }
193
- });
197
+ H.Tooltip.prototype.getXDateFormat(point, series.chart.options.tooltip, series.xAxis), point.x),
198
+ hasSpecialKey = H.find(series.specialKeys, function(key) {
199
+ return point[key] !== undefined;
200
+ });
194
201
 
195
202
  // If the point has one of the less common properties defined, display all that are defined
196
203
  if (hasSpecialKey) {
197
204
  if (dateTimePoint) {
198
205
  infoString = timeDesc;
199
206
  }
200
- each(commonKeys.concat(specialKeys), function(key) {
207
+ each(series.commonKeys.concat(series.specialKeys), function(key) {
201
208
  if (point[key] !== undefined && !(dateTimePoint && key === 'x')) {
202
- infoString += (infoString ? '. ' : '') + key + ', ' + this[key];
209
+ infoString += (infoString ? '. ' : '') + key + ', ' + point[key];
203
210
  }
204
211
  });
205
212
  } else {
@@ -341,9 +348,14 @@
341
348
  }
342
349
  if (!this.isNull) {
343
350
  this.onMouseOver(); // Show the hover marker
344
- chart.tooltip.refresh(chart.tooltip.shared ? [this] : this); // Show the tooltip
351
+ // Show the tooltip
352
+ if (chart.tooltip) {
353
+ chart.tooltip.refresh(chart.tooltip.shared ? [this] : this);
354
+ }
345
355
  } else {
346
- chart.tooltip.hide(0);
356
+ if (chart.tooltip) {
357
+ chart.tooltip.hide(0);
358
+ }
347
359
  // Don't call blur on the element, as it messes up the chart div's focus
348
360
  }
349
361
  chart.highlightedPoint = this;
@@ -356,8 +368,13 @@
356
368
  var series = this.series,
357
369
  curPoint = this.highlightedPoint,
358
370
  curPointIndex = curPoint && curPoint.index || 0,
371
+ curPoints = curPoint && curPoint.series.points,
359
372
  newSeries,
360
- newPoint;
373
+ newPoint,
374
+ // Handle connecting ends - where the points array has an extra last
375
+ // point that is a reference to the first one. We skip this.
376
+ forwardSkipAmount = curPoint && curPoint.series.connectEnds &&
377
+ curPointIndex > curPoints.length - 3 ? 2 : 1;
361
378
 
362
379
  // If no points, return false
363
380
  if (!series[0] || !series[0].points) {
@@ -370,18 +387,23 @@
370
387
  }
371
388
 
372
389
  // Find index of current point in series.points array. Necessary for dataGrouping (and maybe zoom?)
373
- if (curPoint.series.points[curPointIndex] !== curPoint) {
374
- for (var i = 0; i < curPoint.series.points.length; ++i) {
375
- if (curPoint.series.points[i] === curPoint) {
390
+ if (curPoints[curPointIndex] !== curPoint) {
391
+ for (var i = 0; i < curPoints.length; ++i) {
392
+ if (curPoints[i] === curPoint) {
376
393
  curPointIndex = i;
377
394
  break;
378
395
  }
379
396
  }
380
397
  }
381
398
 
382
- // Try to grab next/prev point
399
+ // Grab next/prev point & series
383
400
  newSeries = series[curPoint.series.index + (next ? 1 : -1)];
384
- newPoint = curPoint.series.points[curPointIndex + (next ? 1 : -1)] || newSeries && newSeries.points[next ? 0 : newSeries.points.length - 1];
401
+ newPoint = curPoints[curPointIndex + (next ? forwardSkipAmount : -1)] ||
402
+ // Done with this series, try next one
403
+ newSeries &&
404
+ newSeries.points[next ? 0 : newSeries.points.length - (
405
+ newSeries.connectEnds ? 2 : 1
406
+ )];
385
407
 
386
408
  // If there is no adjacent point, we return false
387
409
  if (newPoint === undefined) {
@@ -869,8 +891,11 @@
869
891
  chart.keyboardNavigationModuleIndex = 0;
870
892
 
871
893
  // Make chart reachable by tab
872
- if (!chart.renderTo.tabIndex) {
873
- chart.renderTo.setAttribute('tabindex', '0');
894
+ if (
895
+ chart.container.hasAttribute &&
896
+ !chart.container.hasAttribute('tabIndex')
897
+ ) {
898
+ chart.container.setAttribute('tabindex', '0');
874
899
  }
875
900
 
876
901
  // Handle keyboard events
@@ -882,15 +907,15 @@
882
907
 
883
908
  // Add screen reader region to chart.
884
909
  // tableId is the HTML id of the table to focus when clicking the table anchor in the screen reader region.
885
- H.Chart.prototype.addScreenReaderRegion = function(tableId) {
910
+ H.Chart.prototype.addScreenReaderRegion = function(id, tableId) {
886
911
  var chart = this,
887
912
  series = chart.series,
888
913
  options = chart.options,
889
914
  a11yOptions = options.accessibility,
890
915
  hiddenSection = chart.screenReaderRegion = doc.createElement('div'),
891
- tableShortcut = doc.createElement('h3'),
916
+ tableShortcut = doc.createElement('h4'),
892
917
  tableShortcutAnchor = doc.createElement('a'),
893
- chartHeading = doc.createElement('h3'),
918
+ chartHeading = doc.createElement('h4'),
894
919
  hiddenStyle = { // CSS style to hide element from visual users while still exposing it to screen readers
895
920
  position: 'absolute',
896
921
  left: '-9999px',
@@ -902,18 +927,19 @@
902
927
  chartTypes = chart.types || [],
903
928
  // Build axis info - but not for pies and maps. Consider not adding for certain other types as well (funnel, pyramid?)
904
929
  axesDesc = (chartTypes.length === 1 && chartTypes[0] === 'pie' || chartTypes[0] === 'map') && {} || chart.getAxesDescription(),
905
- chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] || typeToSeriesMap.default;
930
+ chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] || typeToSeriesMap['default']; // eslint-disable-line dot-notation
906
931
 
932
+ hiddenSection.setAttribute('id', id);
907
933
  hiddenSection.setAttribute('role', 'region');
908
934
  hiddenSection.setAttribute('aria-label', 'Chart screen reader information.');
909
935
 
910
936
  hiddenSection.innerHTML = a11yOptions.screenReaderSectionFormatter && a11yOptions.screenReaderSectionFormatter(chart) ||
911
- '<div tabindex="0">Use regions/landmarks to skip ahead to chart' +
937
+ '<div>Use regions/landmarks to skip ahead to chart' +
912
938
  (series.length > 1 ? ' and navigate between data series' : '') +
913
- '.</div><h3>Summary.</h3><div>' + (options.title.text ? htmlencode(options.title.text) : 'Chart') +
939
+ '.</div><h3>' + (options.title.text ? htmlencode(options.title.text) : 'Chart') +
914
940
  (options.subtitle && options.subtitle.text ? '. ' + htmlencode(options.subtitle.text) : '') +
915
- '</div><h3>Long description.</h3><div>' + (options.chart.description || 'No description available.') +
916
- '</div><h3>Structure.</h3><div>Chart type: ' + (options.chart.typeDescription || chart.getTypeDescription()) + '</div>' +
941
+ '</h3><h4>Long description.</h4><div>' + (options.chart.description || 'No description available.') +
942
+ '</div><h4>Structure.</h4><div>Chart type: ' + (options.chart.typeDescription || chart.getTypeDescription()) + '</div>' +
917
943
  (series.length === 1 ? '<div>' + chartTypeInfo[0] + ' with ' + series[0].points.length + ' ' +
918
944
  (series[0].points.length === 1 ? chartTypeInfo[1] : chartTypeInfo[2]) + '.</div>' : '') +
919
945
  (axesDesc.xAxis ? ('<div>' + axesDesc.xAxis + '</div>') : '') +
@@ -929,10 +955,11 @@
929
955
  doc.getElementById(tableId).focus();
930
956
  };
931
957
  tableShortcut.appendChild(tableShortcutAnchor);
932
-
933
958
  hiddenSection.appendChild(tableShortcut);
934
959
  }
935
960
 
961
+ // Note: JAWS seems to refuse to read aria-label on the container, so add an
962
+ // h4 element as title for the chart.
936
963
  chartHeading.innerHTML = 'Chart graphic.';
937
964
  chart.renderTo.insertBefore(chartHeading, chart.renderTo.firstChild);
938
965
  chart.renderTo.insertBefore(hiddenSection, chart.renderTo.firstChild);
@@ -958,6 +985,7 @@
958
985
  textElements = chart.container.getElementsByTagName('text'),
959
986
  titleId = 'highcharts-title-' + chart.index,
960
987
  tableId = 'highcharts-data-table-' + chart.index,
988
+ hiddenSectionId = 'highcharts-information-region-' + chart.index,
961
989
  chartTitle = options.title.text || 'Chart',
962
990
  oldColumnHeaderFormatter = options.exporting && options.exporting.csv && options.exporting.csv.columnHeaderFormatter,
963
991
  topLevelColumns = [];
@@ -967,7 +995,9 @@
967
995
  titleElement.id = titleId;
968
996
  descElement.parentNode.insertBefore(titleElement, descElement);
969
997
  chart.renderTo.setAttribute('role', 'region');
970
- chart.renderTo.setAttribute('aria-label', chartTitle + '. Use up and down arrows to navigate.');
998
+ chart.container.setAttribute('aria-details', hiddenSectionId);
999
+ chart.renderTo.setAttribute('aria-label', 'Interactive chart. ' + chartTitle +
1000
+ '. Use up and down arrows to navigate with most screen readers.');
971
1001
 
972
1002
  // Set screen reader properties on export menu
973
1003
  if (chart.exportSVGElements && chart.exportSVGElements[0] && chart.exportSVGElements[0].element) {
@@ -1004,7 +1034,7 @@
1004
1034
  });
1005
1035
 
1006
1036
  // Add top-secret screen reader region
1007
- chart.addScreenReaderRegion(tableId);
1037
+ chart.addScreenReaderRegion(hiddenSectionId, tableId);
1008
1038
 
1009
1039
  // Enable keyboard navigation
1010
1040
  if (a11yOptions.keyboardNavigation) {
@@ -1,10 +1,11 @@
1
1
  /**
2
- * @license Highcharts JS v5.0.7 (2017-01-17)
2
+ * @license Highcharts JS v5.0.8 (2017-03-08)
3
3
  *
4
4
  * (c) 2009-2016 Torstein Honsi
5
5
  *
6
6
  * License: www.highcharts.com/license
7
7
  */
8
+ 'use strict';
8
9
  (function(factory) {
9
10
  if (typeof module === 'object' && module.exports) {
10
11
  module.exports = factory;
@@ -18,7 +19,6 @@
18
19
  *
19
20
  * License: www.highcharts.com/license
20
21
  */
21
- 'use strict';
22
22
 
23
23
  var defined = H.defined,
24
24
  isNumber = H.isNumber,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v5.0.7 (2017-01-17)
2
+ * @license Highcharts JS v5.0.8 (2017-03-08)
3
3
  * Boost module
4
4
  *
5
5
  * (c) 2010-2016 Highsoft AS
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * License: www.highcharts.com/license
9
9
  */
10
+ 'use strict';
10
11
  (function(factory) {
11
12
  if (typeof module === 'object' && module.exports) {
12
13
  module.exports = factory;
@@ -17,31 +18,20 @@
17
18
  (function(H) {
18
19
  /**
19
20
  * License: www.highcharts.com/license
20
- * Author: Torstein Honsi
21
+ * Author: Christer Vasseng, Torstein Honsi
21
22
  *
22
23
  * This is an experimental Highcharts module that draws long data series on a canvas
23
24
  * in order to increase performance of the initial load time and tooltip responsiveness.
24
25
  *
25
- * Compatible with HTML5 canvas compatible browsers (not IE < 9).
26
+ * Compatible with WebGL compatible browsers (not IE < 11).
26
27
  *
27
- *
28
- *
29
28
  * Development plan
30
29
  * - Column range.
31
- * - Heatmap. Modify the heatmap-canvas demo so that it uses this module.
32
- * - Treemap.
33
30
  * - Check how it works with Highstock and data grouping. Currently it only works when navigator.adaptToUpdatedData
34
31
  * is false. It is also recommended to set scrollbar.liveRedraw to false.
35
32
  * - Check inverted charts.
36
- * - Check reversed axes.
37
33
  * - Chart callback should be async after last series is drawn. (But not necessarily, we don't do
38
34
  that with initial series animation).
39
- * - Cache full-size image so we don't have to redraw on hide/show and zoom up. But k-d-tree still
40
- * needs to be built.
41
- * - Test IE9 and IE10.
42
- * - Stacking is not perhaps not correct since it doesn't use the translation given in
43
- * the translate method. If this gets to complicated, a possible way out would be to
44
- * have a simplified renderCanvas method that simply draws the areaPath on a canvas.
45
35
  *
46
36
  * If this module is taken in as part of the core
47
37
  * - All the loading logic should be merged with core. Update styles in the core.
@@ -49,44 +39,1984 @@
49
39
  *
50
40
  * Notes for boost mode
51
41
  * - Area lines are not drawn
52
- * - Point markers are not drawn on line-type series
53
42
  * - Lines are not drawn on scatter charts
54
43
  * - Zones and negativeColor don't work
55
- * - Initial point colors aren't rendered
56
44
  * - Columns are always one pixel wide. Don't set the threshold too low.
45
+ * - Disable animations
46
+ * - Marker shapes are not supported: markers will always be circles
57
47
  *
58
48
  * Optimizing tips for users
59
- * - For scatter plots, use a marker.radius of 1 or less. It results in a rectangle being drawn, which is
60
- * considerably faster than a circle.
61
49
  * - Set extremes (min, max) explicitly on the axes in order for Highcharts to avoid computing extremes.
62
50
  * - Set enableMouseTracking to false on the series to improve total rendering time.
63
51
  * - The default threshold is set based on one series. If you have multiple, dense series, the combined
64
52
  * number of points drawn gets higher, and you may want to set the threshold lower in order to
65
53
  * use optimizations.
54
+ * - If drawing large scatter charts, it's beneficial to set the marker radius to a value
55
+ * less than 1. This is to add additional spacing to make the chart more readable.
56
+ * - If the value increments on both the X and Y axis aren't small, consider setting
57
+ * useGPUTranslations to true on the boost settings object. If you do this and
58
+ * the increments are small (e.g. datetime axis with small time increments)
59
+ * it may cause rendering issues due to floating point rounding errors,
60
+ * so your millage may vary.
61
+ *
62
+ * Settings
63
+ * There are two ways of setting the boost threshold:
64
+ * - Per. series: boost based on number of points in individual series
65
+ * - Per. chart: boost based on the number of series
66
+ *
67
+ * To set the series boost threshold, set seriesBoostThreshold on the chart object.
68
+ * To set the series-specific threshold, set boostThreshold on the series object.
69
+ *
70
+ * In addition, the following can be set in the boost object:
71
+ * {
72
+ * //Wether or not to use alpha blending
73
+ * useAlpha: boolean - default: true
74
+ * //Set to true to perform translations on the GPU.
75
+ * //Much faster, but may cause rendering issues
76
+ * //when using values far from 0 due to floating point
77
+ * //rounding issues
78
+ * useGPUTranslations: boolean - default: false
79
+ * //Use pre-allocated buffers, much faster,
80
+ * //but may cause rendering issues with some data sets
81
+ * usePreallocated: boolean - default: false
82
+ * //Output rendering time in console
83
+ * timeRendering: boolean - default: false
84
+ * //Output processing time in console
85
+ * timeSeriesProcessing: boolean - default: false
86
+ * //Output setup time in console
87
+ * timeSetup: boolean - default: false
88
+ * }
89
+ */
90
+
91
+ /**
92
+ * Set the series threshold for when the boost should kick in globally.
93
+ *
94
+ * Setting to e.g. 20 will cause the whole chart to enter boost mode
95
+ * if there are 20 or more series active. When the chart is in boost mode,
96
+ * every series in it will be rendered to a common canvas. This offers
97
+ * a significant speed improvment in charts with a very high
98
+ * amount of series.
99
+ *
100
+ * Note: only available when including the boost module.
101
+ *
102
+ * @default null
103
+ * @apioption boost.seriesThreshold
104
+ */
105
+
106
+ /**
107
+ * Set the point threshold for when a series should enter boost mode.
108
+ *
109
+ * Setting it to e.g. 2000 will cause the series to enter boost mode
110
+ * when there are 2000 or more points in the series.
111
+ *
112
+ * Note: only available when including the boost module.
113
+ *
114
+ * @default 5000
115
+ * @apioption series.boostThreshold
116
+ */
117
+
118
+ /* global Float32Array, Image */
119
+
120
+
121
+ var win = H.win,
122
+ doc = win.document,
123
+ noop = function() {},
124
+ Color = H.Color,
125
+ Series = H.Series,
126
+ seriesTypes = H.seriesTypes,
127
+ each = H.each,
128
+ extend = H.extend,
129
+ addEvent = H.addEvent,
130
+ fireEvent = H.fireEvent,
131
+ grep = H.grep,
132
+ isNumber = H.isNumber,
133
+ merge = H.merge,
134
+ pick = H.pick,
135
+ wrap = H.wrap,
136
+ plotOptions = H.getOptions().plotOptions,
137
+ CHUNK_SIZE = 50000,
138
+ index;
139
+
140
+ // Register color names since GL can't render those directly.
141
+ Color.prototype.names = {
142
+ aliceblue: '#f0f8ff',
143
+ antiquewhite: '#faebd7',
144
+ aqua: '#00ffff',
145
+ aquamarine: '#7fffd4',
146
+ azure: '#f0ffff',
147
+ beige: '#f5f5dc',
148
+ bisque: '#ffe4c4',
149
+ black: '#000000',
150
+ blanchedalmond: '#ffebcd',
151
+ blue: '#0000ff',
152
+ blueviolet: '#8a2be2',
153
+ brown: '#a52a2a',
154
+ burlywood: '#deb887',
155
+ cadetblue: '#5f9ea0',
156
+ chartreuse: '#7fff00',
157
+ chocolate: '#d2691e',
158
+ coral: '#ff7f50',
159
+ cornflowerblue: '#6495ed',
160
+ cornsilk: '#fff8dc',
161
+ crimson: '#dc143c',
162
+ cyan: '#00ffff',
163
+ darkblue: '#00008b',
164
+ darkcyan: '#008b8b',
165
+ darkgoldenrod: '#b8860b',
166
+ darkgray: '#a9a9a9',
167
+ darkgreen: '#006400',
168
+ darkkhaki: '#bdb76b',
169
+ darkmagenta: '#8b008b',
170
+ darkolivegreen: '#556b2f',
171
+ darkorange: '#ff8c00',
172
+ darkorchid: '#9932cc',
173
+ darkred: '#8b0000',
174
+ darksalmon: '#e9967a',
175
+ darkseagreen: '#8fbc8f',
176
+ darkslateblue: '#483d8b',
177
+ darkslategray: '#2f4f4f',
178
+ darkturquoise: '#00ced1',
179
+ darkviolet: '#9400d3',
180
+ deeppink: '#ff1493',
181
+ deepskyblue: '#00bfff',
182
+ dimgray: '#696969',
183
+ dodgerblue: '#1e90ff',
184
+ feldspar: '#d19275',
185
+ firebrick: '#b22222',
186
+ floralwhite: '#fffaf0',
187
+ forestgreen: '#228b22',
188
+ fuchsia: '#ff00ff',
189
+ gainsboro: '#dcdcdc',
190
+ ghostwhite: '#f8f8ff',
191
+ gold: '#ffd700',
192
+ goldenrod: '#daa520',
193
+ gray: '#808080',
194
+ green: '#008000',
195
+ greenyellow: '#adff2f',
196
+ honeydew: '#f0fff0',
197
+ hotpink: '#ff69b4',
198
+ indianred: '#cd5c5c',
199
+ indigo: '#4b0082',
200
+ ivory: '#fffff0',
201
+ khaki: '#f0e68c',
202
+ lavender: '#e6e6fa',
203
+ lavenderblush: '#fff0f5',
204
+ lawngreen: '#7cfc00',
205
+ lemonchiffon: '#fffacd',
206
+ lightblue: '#add8e6',
207
+ lightcoral: '#f08080',
208
+ lightcyan: '#e0ffff',
209
+ lightgoldenrodyellow: '#fafad2',
210
+ lightgrey: '#d3d3d3',
211
+ lightgreen: '#90ee90',
212
+ lightpink: '#ffb6c1',
213
+ lightsalmon: '#ffa07a',
214
+ lightseagreen: '#20b2aa',
215
+ lightskyblue: '#87cefa',
216
+ lightslateblue: '#8470ff',
217
+ lightslategray: '#778899',
218
+ lightsteelblue: '#b0c4de',
219
+ lightyellow: '#ffffe0',
220
+ lime: '#00ff00',
221
+ limegreen: '#32cd32',
222
+ linen: '#faf0e6',
223
+ magenta: '#ff00ff',
224
+ maroon: '#800000',
225
+ mediumaquamarine: '#66cdaa',
226
+ mediumblue: '#0000cd',
227
+ mediumorchid: '#ba55d3',
228
+ mediumpurple: '#9370d8',
229
+ mediumseagreen: '#3cb371',
230
+ mediumslateblue: '#7b68ee',
231
+ mediumspringgreen: '#00fa9a',
232
+ mediumturquoise: '#48d1cc',
233
+ mediumvioletred: '#c71585',
234
+ midnightblue: '#191970',
235
+ mintcream: '#f5fffa',
236
+ mistyrose: '#ffe4e1',
237
+ moccasin: '#ffe4b5',
238
+ navajowhite: '#ffdead',
239
+ navy: '#000080',
240
+ oldlace: '#fdf5e6',
241
+ olive: '#808000',
242
+ olivedrab: '#6b8e23',
243
+ orange: '#ffa500',
244
+ orangered: '#ff4500',
245
+ orchid: '#da70d6',
246
+ palegoldenrod: '#eee8aa',
247
+ palegreen: '#98fb98',
248
+ paleturquoise: '#afeeee',
249
+ palevioletred: '#d87093',
250
+ papayawhip: '#ffefd5',
251
+ peachpuff: '#ffdab9',
252
+ peru: '#cd853f',
253
+ pink: '#ffc0cb',
254
+ plum: '#dda0dd',
255
+ powderblue: '#b0e0e6',
256
+ purple: '#800080',
257
+ red: '#ff0000',
258
+ rosybrown: '#bc8f8f',
259
+ royalblue: '#4169e1',
260
+ saddlebrown: '#8b4513',
261
+ salmon: '#fa8072',
262
+ sandybrown: '#f4a460',
263
+ seagreen: '#2e8b57',
264
+ seashell: '#fff5ee',
265
+ sienna: '#a0522d',
266
+ silver: '#c0c0c0',
267
+ skyblue: '#87ceeb',
268
+ slateblue: '#6a5acd',
269
+ slategray: '#708090',
270
+ snow: '#fffafa',
271
+ springgreen: '#00ff7f',
272
+ steelblue: '#4682b4',
273
+ tan: '#d2b48c',
274
+ teal: '#008080',
275
+ thistle: '#d8bfd8',
276
+ tomato: '#ff6347',
277
+ turquoise: '#40e0d0',
278
+ violet: '#ee82ee',
279
+ violetred: '#d02090',
280
+ wheat: '#f5deb3',
281
+ white: '#ffffff',
282
+ whitesmoke: '#f5f5f5',
283
+ yellow: '#ffff00',
284
+ yellowgreen: '#9acd32'
285
+ };
286
+
287
+
288
+ /*
289
+ * Returns true if the chart is in series boost mode
290
+ * @param chart {Highchart.Chart} - the chart to check
291
+ * @returns {Boolean} - true if the chart is in series boost mode
292
+ */
293
+ function isChartSeriesBoosting(chart) {
294
+ return chart.series.length >= pick(
295
+ chart.options.boost && chart.options.boost.seriesThreshold, // docs
296
+ 10
297
+ );
298
+ }
299
+
300
+ /*
301
+ * Returns true if the series is in boost mode
302
+ * @param series {Highchart.Series} - the series to check
303
+ * @returns {boolean} - true if the series is in boost mode
304
+ */
305
+ function isSeriesBoosting(series) {
306
+ function patientMax() {
307
+ var args = Array.prototype.slice.call(arguments),
308
+ r = -Number.MAX_VALUE;
309
+
310
+ each(args, function(t) {
311
+ if (typeof t !== 'undefined' && typeof t.length !== 'undefined') {
312
+ //r = r < t.length ? t.length : r;
313
+ if (t.length > 0) {
314
+ r = t.length;
315
+ return true;
316
+ }
317
+ }
318
+ });
319
+
320
+ return r;
321
+ }
322
+
323
+ return isChartSeriesBoosting(series.chart) ||
324
+ patientMax(
325
+ series.processedXData,
326
+ series.options.data,
327
+ series.points
328
+ ) >= (series.options.boostThreshold || Number.MAX_VALUE);
329
+ }
330
+
331
+ ////////////////////////////////////////////////////////////////////////////////
332
+ // START OF WEBGL ABSTRACTIONS
333
+
334
+ /*
335
+ * A static shader mimicing axis translation functions found in parts/Axis
336
+ * @param gl {WebGLContext} - the context in which the shader is active
337
+ */
338
+ function GLShader(gl) {
339
+ var vertShade = [
340
+ /* eslint-disable */
341
+ '#version 100',
342
+ 'precision highp float;',
343
+
344
+ 'attribute vec4 aVertexPosition;',
345
+ 'attribute vec4 aColor;',
346
+
347
+ 'varying highp vec2 position;',
348
+ 'varying highp vec4 vColor;',
349
+
350
+ 'uniform mat4 uPMatrix;',
351
+ 'uniform float pSize;',
352
+
353
+ 'uniform float translatedThreshold;',
354
+ 'uniform bool hasThreshold;',
355
+
356
+ 'uniform bool skipTranslation;',
357
+
358
+ 'uniform float xAxisTrans;',
359
+ 'uniform float xAxisMin;',
360
+ 'uniform float xAxisMinPad;',
361
+ 'uniform float xAxisPointRange;',
362
+ 'uniform float xAxisLen;',
363
+ 'uniform bool xAxisPostTranslate;',
364
+ 'uniform float xAxisOrdinalSlope;',
365
+ 'uniform float xAxisOrdinalOffset;',
366
+ 'uniform float xAxisPos;',
367
+ 'uniform bool xAxisCVSCoord;',
368
+
369
+ 'uniform float yAxisTrans;',
370
+ 'uniform float yAxisMin;',
371
+ 'uniform float yAxisMinPad;',
372
+ 'uniform float yAxisPointRange;',
373
+ 'uniform float yAxisLen;',
374
+ 'uniform bool yAxisPostTranslate;',
375
+ 'uniform float yAxisOrdinalSlope;',
376
+ 'uniform float yAxisOrdinalOffset;',
377
+ 'uniform float yAxisPos;',
378
+ 'uniform bool yAxisCVSCoord;',
379
+
380
+ 'uniform bool isBubble;',
381
+ 'uniform bool bubbleSizeByArea;',
382
+ 'uniform float bubbleZMin;',
383
+ 'uniform float bubbleZMax;',
384
+ 'uniform float bubbleZThreshold;',
385
+ 'uniform float bubbleMinSize;',
386
+ 'uniform float bubbleMaxSize;',
387
+ 'uniform bool bubbleSizeAbs;',
388
+ 'uniform bool isInverted;',
389
+
390
+ 'float bubbleRadius(){',
391
+ 'float value = aVertexPosition.w;',
392
+ 'float zMax = bubbleZMax;',
393
+ 'float zMin = bubbleZMin;',
394
+ 'float radius = 0.0;',
395
+ 'float pos = 0.0;',
396
+ 'float zRange = zMax - zMin;',
397
+
398
+ 'if (bubbleSizeAbs){',
399
+ 'value = value - bubbleZThreshold;',
400
+ 'zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);',
401
+ 'zMin = 0.0;',
402
+ '}',
403
+
404
+ 'if (value < zMin){',
405
+ 'radius = bubbleZMin / 2.0 - 1.0;',
406
+ '} else {',
407
+ 'pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;',
408
+ 'if (bubbleSizeByArea && pos > 0.0){',
409
+ 'pos = sqrt(pos);',
410
+ '}',
411
+ 'radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;',
412
+ '}',
413
+
414
+ 'return radius * 2.0;',
415
+ '}',
416
+
417
+ 'float translate(float val,',
418
+ 'float pointPlacement,',
419
+ 'float localA,',
420
+ 'float localMin,',
421
+ 'float minPixelPadding,',
422
+ 'float pointRange,',
423
+ 'float len,',
424
+ 'bool cvsCoord',
425
+ '){',
426
+
427
+ 'float sign = 1.0;',
428
+ 'float cvsOffset = 0.0;',
429
+
430
+ 'if (cvsCoord) {',
431
+ 'sign *= -1.0;',
432
+ 'cvsOffset = len;',
433
+ '}',
434
+
435
+ 'return sign * (val - localMin) * localA + cvsOffset + ',
436
+ '(sign * minPixelPadding);', //' + localA * pointPlacement * pointRange;',
437
+ '}',
438
+
439
+ 'float xToPixels(float value){',
440
+ 'if (skipTranslation){',
441
+ 'return value;// + xAxisPos;',
442
+ '}',
443
+
444
+ 'return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord);// + xAxisPos;',
445
+ '}',
446
+
447
+ 'float yToPixels(float value, float checkTreshold){',
448
+ 'float v;',
449
+ 'if (skipTranslation){',
450
+ 'v = value;// + yAxisPos;',
451
+ '} else {',
452
+ 'v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord);// + yAxisPos;',
453
+ '}',
454
+ 'if (checkTreshold > 0.0 && hasThreshold) {',
455
+ 'v = min(v, translatedThreshold);',
456
+ '}',
457
+ 'return v;',
458
+ '}',
459
+
460
+ 'void main(void) {',
461
+ 'if (isBubble){',
462
+ 'gl_PointSize = bubbleRadius();',
463
+ '} else {',
464
+ 'gl_PointSize = pSize;',
465
+ '}',
466
+ //'gl_PointSize = 10.0;',
467
+ 'vColor = aColor;',
468
+
469
+ 'if (isInverted) {',
470
+ 'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.y) + yAxisPos, yToPixels(aVertexPosition.x, aVertexPosition.z) + xAxisPos, 0.0, 1.0);',
471
+ '} else {',
472
+ 'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);',
473
+ '}',
474
+ //'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
475
+ '}'
476
+ /* eslint-enable */
477
+ ].join('\n'),
478
+ //Fragment shader source
479
+ fragShade = [
480
+ /* eslint-disable */
481
+ 'precision highp float;',
482
+ 'uniform vec4 fillColor;',
483
+ 'varying highp vec2 position;',
484
+ 'varying highp vec4 vColor;',
485
+ 'uniform sampler2D uSampler;',
486
+ 'uniform bool isCircle;',
487
+ 'uniform bool hasColor;',
488
+
489
+ // 'vec4 toColor(float value, vec2 point) {',
490
+ // 'return vec4(0.0, 0.0, 0.0, 0.0);',
491
+ // '}',
492
+
493
+ 'void main(void) {',
494
+ 'vec4 col = fillColor;',
495
+
496
+ 'if (hasColor) {',
497
+ 'col = vColor;',
498
+ '}',
499
+
500
+ 'if (isCircle) {',
501
+ 'gl_FragColor = col * texture2D(uSampler, gl_PointCoord.st);',
502
+ '} else {',
503
+ 'gl_FragColor = col;',
504
+ '}',
505
+ '}'
506
+ /* eslint-enable */
507
+ ].join('\n'),
508
+ uLocations = {},
509
+ //The shader program
510
+ shaderProgram,
511
+ //Uniform handle to the perspective matrix
512
+ pUniform,
513
+ //Uniform for point size
514
+ psUniform,
515
+ //Uniform for fill color
516
+ fillColorUniform,
517
+ //Uniform for isBubble
518
+ isBubbleUniform,
519
+ //Uniform for bubble abs sizing
520
+ bubbleSizeAbsUniform,
521
+ bubbleSizeAreaUniform,
522
+ //Skip translation uniform
523
+ skipTranslationUniform,
524
+ //Set to 1 if circle
525
+ isCircleUniform,
526
+ //Uniform for invertion
527
+ isInverted,
528
+ //Texture uniform
529
+ uSamplerUniform;
530
+
531
+ /* String to shader program
532
+ * @param {string} str - the program source
533
+ * @param {string} type - the program type: either `vertex` or `fragment`
534
+ * @returns {bool|shader}
535
+ */
536
+ function stringToProgram(str, type) {
537
+ var t = type === 'vertex' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER,
538
+ shader = gl.createShader(t);
539
+
540
+ gl.shaderSource(shader, str);
541
+ gl.compileShader(shader);
542
+
543
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
544
+ //console.error('shader error:', gl.getShaderInfoLog(shader));
545
+ return false;
546
+ }
547
+ return shader;
548
+ }
549
+
550
+ /*
551
+ * Create the shader.
552
+ * Loads the shader program statically defined above
553
+ */
554
+ function createShader() {
555
+ var v = stringToProgram(vertShade, 'vertex'),
556
+ f = stringToProgram(fragShade, 'fragment');
557
+
558
+ if (!v || !f) {
559
+ shaderProgram = false;
560
+ //console.error('error creating shader program');
561
+ return false;
562
+ }
563
+
564
+ function uloc(n) {
565
+ return gl.getUniformLocation(shaderProgram, n);
566
+ }
567
+
568
+ shaderProgram = gl.createProgram();
569
+
570
+ gl.attachShader(shaderProgram, v);
571
+ gl.attachShader(shaderProgram, f);
572
+ gl.linkProgram(shaderProgram);
573
+
574
+ gl.useProgram(shaderProgram);
575
+
576
+ gl.bindAttribLocation(shaderProgram, 0, 'aVertexPosition');
577
+
578
+ pUniform = uloc('uPMatrix');
579
+ psUniform = uloc('pSize');
580
+ fillColorUniform = uloc('fillColor');
581
+ isBubbleUniform = uloc('isBubble');
582
+ bubbleSizeAbsUniform = uloc('bubbleSizeAbs');
583
+ bubbleSizeAreaUniform = uloc('bubbleSizeByArea');
584
+ uSamplerUniform = uloc('uSampler');
585
+ skipTranslationUniform = uloc('skipTranslation');
586
+ isCircleUniform = uloc('isCircle');
587
+ isInverted = uloc('isInverted');
588
+
589
+ return true;
590
+ }
591
+
592
+ /*
593
+ * Destroy the shader
594
+ */
595
+ function destroy() {
596
+ if (gl && shaderProgram) {
597
+ gl.deleteProgram(shaderProgram);
598
+ }
599
+ }
600
+
601
+ /*
602
+ * Bind the shader.
603
+ * This makes the shader the active one until another one is bound,
604
+ * or until 0 is bound.
605
+ */
606
+ function bind() {
607
+ gl.useProgram(shaderProgram);
608
+ }
609
+
610
+ /*
611
+ * Set a uniform value.
612
+ * This uses a hash map to cache uniform locations.
613
+ * @param name {string} - the name of the uniform to set
614
+ * @param val {float} - the value to set
615
+ */
616
+ function setUniform(name, val) {
617
+ var u = uLocations[name] = uLocations[name] ||
618
+ gl.getUniformLocation(shaderProgram, name);
619
+ gl.uniform1f(u, val);
620
+ }
621
+
622
+ /*
623
+ * Set the active texture
624
+ * @param texture - the texture
625
+ */
626
+ function setTexture() {
627
+ gl.uniform1i(uSamplerUniform, 0);
628
+ }
629
+
630
+ /*
631
+ * Set if inversion state
632
+ * @flag is the state
633
+ */
634
+ function setInverted(flag) {
635
+ gl.uniform1i(isInverted, flag);
636
+ }
637
+
638
+ ////////////////////////////////////////////////////////////////////////////
639
+
640
+ /*
641
+ * Enable/disable circle drawing
642
+ */
643
+ function setDrawAsCircle(flag) {
644
+ gl.uniform1i(isCircleUniform, flag ? 1 : 0);
645
+ }
646
+
647
+ /*
648
+ * Flush
649
+ */
650
+ function reset() {
651
+ gl.uniform1i(isBubbleUniform, 0);
652
+ gl.uniform1i(isCircleUniform, 0);
653
+ }
654
+
655
+ /*
656
+ * Set bubble uniforms
657
+ * @param series {Highcharts.Series} - the series to use
658
+ */
659
+ function setBubbleUniforms(series, zCalcMin, zCalcMax) {
660
+ var seriesOptions = series.options,
661
+ zMin = Number.MAX_VALUE,
662
+ zMax = -Number.MAX_VALUE;
663
+
664
+ if (series.type === 'bubble') {
665
+ zMin = pick(seriesOptions.zMin, Math.min(
666
+ zMin,
667
+ Math.max(
668
+ zCalcMin,
669
+ seriesOptions.displayNegative === false ?
670
+ seriesOptions.zThreshold : -Number.MAX_VALUE
671
+ )
672
+ ));
673
+
674
+ zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
675
+
676
+ gl.uniform1i(isBubbleUniform, 1);
677
+ gl.uniform1i(isCircleUniform, 1);
678
+ gl.uniform1i(bubbleSizeAreaUniform, series.options.sizeBy !== 'width');
679
+ gl.uniform1i(bubbleSizeAbsUniform, series.options.sizeByAbsoluteValue);
680
+
681
+ setUniform('bubbleZMin', zMin);
682
+ setUniform('bubbleZMax', zMax);
683
+ setUniform('bubbleZThreshold', series.options.zThreshold);
684
+ setUniform('bubbleMinSize', series.minPxSize);
685
+ setUniform('bubbleMaxSize', series.maxPxSize);
686
+ }
687
+ }
688
+
689
+ /*
690
+ * Set the Color uniform.
691
+ * @param color {Array<float>} - an array with RGBA values
692
+ */
693
+ function setColor(color) {
694
+ gl.uniform4f(
695
+ fillColorUniform,
696
+ color[0] / 255.0,
697
+ color[1] / 255.0,
698
+ color[2] / 255.0,
699
+ color[3]
700
+ );
701
+ }
702
+
703
+ /*
704
+ * Set skip translation
705
+ */
706
+ function setSkipTranslation(flag) {
707
+ gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
708
+ }
709
+
710
+ /*
711
+ * Set the perspective matrix
712
+ * @param m {Matrix4x4} - the matrix
713
+ */
714
+ function setPMatrix(m) {
715
+ gl.uniformMatrix4fv(pUniform, false, m);
716
+ }
717
+
718
+ /*
719
+ * Set the point size.
720
+ * @param p {float} - point size
721
+ */
722
+ function setPointSize(p) {
723
+ gl.uniform1f(psUniform, p);
724
+ }
725
+
726
+ /*
727
+ * Get the shader program handle
728
+ * @returns {GLInt} - the handle for the program
729
+ */
730
+ function getProgram() {
731
+ return shaderProgram;
732
+ }
733
+
734
+ if (gl) {
735
+ createShader();
736
+ }
737
+
738
+ return {
739
+ psUniform: function() {
740
+ return psUniform;
741
+ },
742
+ pUniform: function() {
743
+ return pUniform;
744
+ },
745
+ fillColorUniform: function() {
746
+ return fillColorUniform;
747
+ },
748
+ setBubbleUniforms: setBubbleUniforms,
749
+ bind: bind,
750
+ program: getProgram,
751
+ create: createShader,
752
+ setUniform: setUniform,
753
+ setPMatrix: setPMatrix,
754
+ setColor: setColor,
755
+ setPointSize: setPointSize,
756
+ setSkipTranslation: setSkipTranslation,
757
+ setTexture: setTexture,
758
+ setDrawAsCircle: setDrawAsCircle,
759
+ reset: reset,
760
+ setInverted: setInverted,
761
+ destroy: destroy
762
+ };
763
+ }
764
+
765
+ /*
766
+ * Vertex Buffer abstraction
767
+ * A vertex buffer is a set of vertices which are passed to the GPU
768
+ * in a single call.
769
+ * @param gl {WebGLContext} - the context in which to create the buffer
770
+ * @param shader {GLShader} - the shader to use
66
771
  */
772
+ function GLVertexBuffer(gl, shader, dataComponents /*, type */ ) {
773
+ var buffer = false,
774
+ vertAttribute = false,
775
+ components = dataComponents || 2,
776
+ preAllocated = false,
777
+ iterator = 0,
778
+ data;
779
+
780
+ // type = type || 'float';
781
+
782
+ function destroy() {
783
+ if (buffer) {
784
+ gl.deleteBuffer(buffer);
785
+ }
786
+ }
787
+
788
+ /*
789
+ * Build the buffer
790
+ * @param dataIn {Array<float>} - a 0 padded array of indices
791
+ * @param attrib {String} - the name of the Attribute to bind the buffer to
792
+ * @param dataComponents {Integer} - the number of components per. indice
793
+ */
794
+ function build(dataIn, attrib, dataComponents) {
795
+
796
+ data = dataIn || [];
797
+
798
+ if ((!data || data.length === 0) && !preAllocated) {
799
+ //console.error('trying to render empty vbuffer');
800
+ buffer = false;
801
+ return false;
802
+ }
803
+
804
+ components = dataComponents || components;
805
+
806
+ if (buffer) {
807
+ gl.deleteBuffer(buffer);
808
+ }
809
+
810
+ buffer = gl.createBuffer();
811
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
812
+ gl.bufferData(
813
+ gl.ARRAY_BUFFER,
814
+ preAllocated || new Float32Array(data),
815
+ gl.STATIC_DRAW
816
+ );
817
+
818
+ // gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
819
+ vertAttribute = gl.getAttribLocation(shader.program(), attrib);
820
+ gl.enableVertexAttribArray(vertAttribute);
821
+
822
+ return true;
823
+ }
824
+
825
+ /*
826
+ * Bind the buffer
827
+ */
828
+ function bind() {
829
+ if (!buffer) {
830
+ return false;
831
+ }
832
+
833
+ // gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
834
+ //gl.enableVertexAttribArray(vertAttribute);
835
+ //gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
836
+ gl.vertexAttribPointer(vertAttribute, components, gl.FLOAT, false, 0, 0);
837
+ //gl.enableVertexAttribArray(vertAttribute);
838
+ }
839
+
840
+ /*
841
+ * Render the buffer
842
+ * @param from {Integer} - the start indice
843
+ * @param to {Integer} - the end indice
844
+ * @param drawMode {String} - the draw mode
845
+ */
846
+ function render(from, to, drawMode) {
847
+ var length = preAllocated ? preAllocated.length : data.length;
848
+
849
+ if (!buffer) {
850
+ return false;
851
+ }
852
+
853
+ if (!length) {
854
+ return false;
855
+ }
856
+
857
+ if (!from || from > length || from < 0) {
858
+ from = 0;
859
+ }
860
+
861
+ if (!to || to > length) {
862
+ to = length;
863
+ }
864
+
865
+ drawMode = drawMode || 'points';
866
+
867
+ gl.drawArrays(
868
+ gl[drawMode.toUpperCase()],
869
+ from / components,
870
+ (to - from) / components
871
+ );
872
+
873
+ return true;
874
+ }
875
+
876
+ function push(x, y, a, b) {
877
+ if (preAllocated) { // && iterator <= preAllocated.length - 4) {
878
+ preAllocated[++iterator] = x;
879
+ preAllocated[++iterator] = y;
880
+ preAllocated[++iterator] = a;
881
+ preAllocated[++iterator] = b;
882
+ }
883
+ }
884
+
885
+ /*
886
+ * Note about pre-allocated buffers:
887
+ * - This is slower for charts with many series
888
+ */
889
+ function allocate(size) {
890
+ size *= 4;
891
+ iterator = -1;
892
+
893
+ //if (!preAllocated || (preAllocated && preAllocated.length !== size)) {
894
+ preAllocated = new Float32Array(size);
895
+ //}
896
+ }
897
+
898
+ ////////////////////////////////////////////////////////////////////////////
899
+ return {
900
+ destroy: destroy,
901
+ bind: bind,
902
+ data: data,
903
+ build: build,
904
+ render: render,
905
+ allocate: allocate,
906
+ push: push
907
+ };
908
+ }
909
+
910
+ /* Main renderer. Used to render series.
911
+ * Notes to self:
912
+ * - May be able to build a point map by rendering to a separate canvas
913
+ * and encoding values in the color data.
914
+ * - Need to figure out a way to transform the data quicker
915
+ */
916
+ function GLRenderer(postRenderCallback) {
917
+ var // Shader
918
+ shader = false,
919
+ // Vertex buffers - keyed on shader attribute name
920
+ vbuffer = false,
921
+ // Opengl context
922
+ gl = false,
923
+ // Width of our viewport in pixels
924
+ width = 0,
925
+ // Height of our viewport in pixels
926
+ height = 0,
927
+ // The data to render - array of coordinates
928
+ data = false,
929
+ // The marker data
930
+ markerData = false,
931
+ // Is the texture ready?
932
+ textureIsReady = false,
933
+ // Exports
934
+ exports = {},
935
+ // Is it inited?
936
+ isInited = false,
937
+ // The series stack
938
+ series = [],
939
+ // Texture for circles
940
+ circleTexture = doc.createElement('canvas'),
941
+ // Context for circle texture
942
+ circleCtx = circleTexture.getContext('2d'),
943
+ // Handle for the circle texture
944
+ circleTextureHandle,
945
+ // Things to draw as "rectangles" (i.e lines)
946
+ asBar = {
947
+ 'column': true,
948
+ 'area': true
949
+ },
950
+ asCircle = {
951
+ 'scatter': true,
952
+ 'bubble': true
953
+ },
954
+ //Render settings
955
+ settings = {
956
+ pointSize: 1,
957
+ lineWidth: 3,
958
+ fillColor: '#AA00AA',
959
+ useAlpha: true,
960
+ usePreallocated: false,
961
+ useGPUTranslations: false,
962
+ timeRendering: false,
963
+ timeSeriesProcessing: false,
964
+ timeSetup: false
965
+ };
966
+
967
+ ////////////////////////////////////////////////////////////////////////////
968
+
969
+ function setOptions(options) {
970
+ merge(true, settings, options);
971
+ }
972
+
973
+ function seriesPointCount(series) {
974
+ var isStacked,
975
+ xData,
976
+ s;
977
+
978
+ if (isSeriesBoosting(series)) {
979
+ isStacked = !!series.options.stacking;
980
+ xData = series.xData || series.options.xData || series.processedXData;
981
+ s = (isStacked ? series.data : (xData || series.options.data)).length;
982
+
983
+ if (series.type === 'treemap') {
984
+ s *= 12;
985
+ } else if (series.type === 'heatmap') {
986
+ s *= 6;
987
+ } else if (asBar[series.type]) {
988
+ s *= 2;
989
+ }
990
+
991
+ return s;
992
+ }
993
+
994
+ return 0;
995
+ }
996
+
997
+ /* Allocate a float buffer to fit all series */
998
+ function allocateBuffer(chart) {
999
+ var s = 0;
1000
+
1001
+ if (!settings.usePreallocated) {
1002
+ return;
1003
+ }
1004
+
1005
+ each(chart.series, function(series) {
1006
+ if (isSeriesBoosting(series)) {
1007
+ s += seriesPointCount(series);
1008
+ }
1009
+ });
1010
+
1011
+ vbuffer.allocate(s);
1012
+ }
1013
+
1014
+ function allocateBufferForSingleSeries(series) {
1015
+ var s = 0;
1016
+
1017
+ if (!settings.usePreallocated) {
1018
+ return;
1019
+ }
1020
+
1021
+ if (isSeriesBoosting(series)) {
1022
+ s = seriesPointCount(series);
1023
+ }
1024
+
1025
+ vbuffer.allocate(s);
1026
+ }
1027
+
1028
+ /*
1029
+ * Returns an orthographic perspective matrix
1030
+ * @param {number} width - the width of the viewport in pixels
1031
+ * @param {number} height - the height of the viewport in pixels
1032
+ */
1033
+ function orthoMatrix(width, height) {
1034
+ var near = 0,
1035
+ far = 1;
1036
+
1037
+ return [
1038
+ 2 / width, 0, 0, 0,
1039
+ 0, -(2 / height), 0, 0,
1040
+ 0, 0, -2 / (far - near), 0, -1, 1, -(far + near) / (far - near), 1
1041
+ ];
1042
+ }
1043
+
1044
+ /*
1045
+ * Clear the depth and color buffer
1046
+ */
1047
+ function clear() {
1048
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
1049
+ }
67
1050
 
68
- 'use strict';
1051
+ /*
1052
+ * Get the WebGL context
1053
+ * @returns {WebGLContext} - the context
1054
+ */
1055
+ function getGL() {
1056
+ return gl;
1057
+ }
1058
+
1059
+ /*
1060
+ * Push data for a single series
1061
+ * This calculates additional vertices and transforms the data to be
1062
+ * aligned correctly in memory
1063
+ */
1064
+ function pushSeriesData(series, inst) {
1065
+ var isRange = series.pointArrayMap &&
1066
+ series.pointArrayMap.join(',') === 'low,high',
1067
+ chart = series.chart,
1068
+ options = series.options,
1069
+ isStacked = !!options.stacking,
1070
+ rawData = options.data,
1071
+ xExtremes = series.xAxis.getExtremes(),
1072
+ xMin = xExtremes.min,
1073
+ xMax = xExtremes.max,
1074
+ yExtremes = series.yAxis.getExtremes(),
1075
+ yMin = yExtremes.min,
1076
+ yMax = yExtremes.max,
1077
+ xData = series.xData || options.xData || series.processedXData,
1078
+ yData = series.yData || options.yData || series.processedYData,
1079
+ zData = series.zData || options.zData || series.processedZData,
1080
+ yAxis = series.yAxis,
1081
+ xAxis = series.xAxis,
1082
+ useRaw = !xData || xData.length === 0,
1083
+ // threshold = options.threshold,
1084
+ // yBottom = chart.yAxis[0].getThreshold(threshold),
1085
+ // hasThreshold = isNumber(threshold),
1086
+ // colorByPoint = series.options.colorByPoint,
1087
+ // This is required for color by point, so make sure this is
1088
+ // uncommented if enabling that
1089
+ // colorIndex = 0,
1090
+ // Required for color axis support
1091
+ // caxis,
1092
+ // connectNulls = options.connectNulls,
1093
+ // For some reason eslint doesn't pick up that this is actually used
1094
+ maxVal, //eslint-disable-line no-unused-vars
1095
+ points = series.points || false,
1096
+ lastX = false,
1097
+ minVal,
1098
+ color,
1099
+ scolor,
1100
+ sdata = isStacked ? series.data : (xData || rawData);
1101
+
1102
+ if (options.boostData && options.boostData.length > 0) {
1103
+ return;
1104
+ }
1105
+
1106
+ series.closestPointRangePx = Number.MAX_VALUE;
1107
+
1108
+ // Push color to color buffer - need to do this per. vertex
1109
+ function pushColor(color) {
1110
+ if (color) {
1111
+ inst.colorData.push(color[0]);
1112
+ inst.colorData.push(color[1]);
1113
+ inst.colorData.push(color[2]);
1114
+ inst.colorData.push(color[3]);
1115
+ }
1116
+ }
1117
+
1118
+ //Push a vertice to the data buffer
1119
+ function vertice(x, y, checkTreshold, pointSize, color) {
1120
+ pushColor(color);
1121
+ if (settings.usePreallocated) {
1122
+ vbuffer.push(x, y, checkTreshold ? 1 : 0, pointSize || 1);
1123
+ } else {
1124
+ data.push(x);
1125
+ data.push(y);
1126
+ data.push(checkTreshold ? 1 : 0);
1127
+ data.push(pointSize || 1);
1128
+ }
1129
+ }
1130
+
1131
+ // Push a rectangle to the data buffer
1132
+ function pushRect(x, y, w, h, color) {
1133
+ pushColor(color);
1134
+ vertice(x + w, y);
1135
+ pushColor(color);
1136
+ vertice(x, y);
1137
+ pushColor(color);
1138
+ vertice(x, y + h);
1139
+
1140
+ pushColor(color);
1141
+ vertice(x, y + h);
1142
+ pushColor(color);
1143
+ vertice(x + w, y + h);
1144
+ pushColor(color);
1145
+ vertice(x + w, y);
1146
+ }
1147
+
1148
+ // Special case for point shapes
1149
+ if (points && points.length > 0) {
1150
+
1151
+ // If we're doing points, we assume that the points are already
1152
+ // translated, so we skip the shader translation.
1153
+ inst.skipTranslation = true;
1154
+ // Force triangle draw mode
1155
+ inst.drawMode = 'triangles';
1156
+
1157
+ // We don't have a z component in the shader, so we need to sort.
1158
+ if (points[0].node && points[0].node.levelDynamic) {
1159
+ points.sort(function(a, b) {
1160
+ if (a.node) {
1161
+ if (a.node.levelDynamic > b.node.levelDynamic) {
1162
+ return 1;
1163
+ } else if (a.node.levelDynamic < b.node.levelDynamic) {
1164
+ return -1;
1165
+ }
1166
+ }
1167
+ return 0;
1168
+ });
1169
+ }
1170
+
1171
+ each(points, function(point) {
1172
+ var plotY = point.plotY,
1173
+ shapeArgs,
1174
+ swidth,
1175
+ pointAttr;
1176
+
1177
+ if (plotY !== undefined && !isNaN(plotY) && point.y !== null) {
1178
+ shapeArgs = point.shapeArgs;
1179
+ pointAttr = (point.pointAttr && point.pointAttr['']) ||
1180
+ point.series.pointAttribs(point);
1181
+ swidth = pointAttr['stroke-width'];
1182
+
1183
+ // Handle point colors
1184
+ color = H.color(pointAttr.fill).rgba;
1185
+ color[0] /= 255.0;
1186
+ color[1] /= 255.0;
1187
+ color[2] /= 255.0;
1188
+
1189
+ // So there are two ways of doing this. Either we can
1190
+ // create a rectangle of two triangles, or we can do a
1191
+ // point and use point size. Latter is faster, but
1192
+ // only supports squares. So we're doing triangles.
1193
+ // We could also use one color per. vertice to get
1194
+ // better color interpolation.
1195
+
1196
+ // If there's stroking, we do an additional rect
1197
+ //if (pointAttr.stroke !== 'none' && swidth && swidth > 0) {
1198
+ if (series.type === 'treemap') {
1199
+ swidth = swidth || 1;
1200
+ scolor = H.color(pointAttr.stroke).rgba;
1201
+
1202
+ scolor[0] /= 255.0;
1203
+ scolor[1] /= 255.0;
1204
+ scolor[2] /= 255.0;
1205
+
1206
+ pushRect(
1207
+ shapeArgs.x,
1208
+ shapeArgs.y,
1209
+ shapeArgs.width,
1210
+ shapeArgs.height,
1211
+ scolor
1212
+ );
1213
+
1214
+ swidth /= 2;
1215
+ }
1216
+ // } else {
1217
+ // swidth = 0;
1218
+ // }
1219
+
1220
+ pushRect(
1221
+ shapeArgs.x + swidth,
1222
+ shapeArgs.y + swidth,
1223
+ shapeArgs.width - (swidth * 2),
1224
+ shapeArgs.height - (swidth * 2),
1225
+ color
1226
+ );
1227
+ }
1228
+ });
1229
+
1230
+ return;
1231
+ }
1232
+
1233
+ // Extract color axis
1234
+ // each(chart.axes || [], function (a) {
1235
+ // if (H.ColorAxis && a instanceof H.ColorAxis) {
1236
+ // caxis = a;
1237
+ // }
1238
+ // });
1239
+
1240
+ each(sdata, function(d, i) {
1241
+ var x,
1242
+ y,
1243
+ z,
1244
+ px = false,
1245
+ nx = false,
1246
+ // This is in fact used.
1247
+ low, //eslint-disable-line no-unused-vars
1248
+ chartDestroyed = typeof chart.index === 'undefined',
1249
+ nextInside = false,
1250
+ prevInside = false,
1251
+ pcolor = false,
1252
+ drawAsBar = asBar[series.type],
1253
+ isXInside = false,
1254
+ isYInside = true;
1255
+
1256
+ if (chartDestroyed) {
1257
+ return false;
1258
+ }
1259
+
1260
+ // Uncomment this to enable color by point.
1261
+ // This currently left disabled as the charts look really ugly
1262
+ // when enabled and there's a lot of points.
1263
+ // Leaving in for the future (tm).
1264
+ // if (colorByPoint) {
1265
+ // colorIndex = ++colorIndex % series.chart.options.colors.length;
1266
+ // pcolor = toRGBAFast(series.chart.options.colors[colorIndex]);
1267
+ // pcolor[0] /= 255.0;
1268
+ // pcolor[1] /= 255.0;
1269
+ // pcolor[2] /= 255.0;
1270
+ // }
1271
+
1272
+ if (useRaw) {
1273
+ x = d[0];
1274
+ y = d[1];
1275
+
1276
+ if (sdata[i + 1]) {
1277
+ nx = sdata[i + 1][0];
1278
+ }
1279
+
1280
+ if (sdata[i - 1]) {
1281
+ px = sdata[i - 1][0];
1282
+ }
1283
+
1284
+ if (d.length >= 3) {
1285
+ z = d[2];
1286
+
1287
+ if (d[2] > inst.zMax) {
1288
+ inst.zMax = d[2];
1289
+ }
1290
+
1291
+ if (d[2] < inst.zMin) {
1292
+ inst.zMin = d[2];
1293
+ }
1294
+ }
1295
+
1296
+ } else {
1297
+ x = d;
1298
+ y = yData[i];
1299
+
1300
+ if (sdata[i + 1]) {
1301
+ nx = sdata[i + 1];
1302
+ }
1303
+
1304
+ if (sdata[i - 1]) {
1305
+ px = sdata[i - 1];
1306
+ }
1307
+
1308
+ if (zData && zData.length) {
1309
+ z = zData[i];
1310
+
1311
+ if (zData[i] > inst.zMax) {
1312
+ inst.zMax = zData[i];
1313
+ }
1314
+
1315
+ if (zData[i] < inst.zMin) {
1316
+ inst.zMin = zData[i];
1317
+ }
1318
+ }
1319
+ }
1320
+
1321
+ if (nx && nx >= xMin && nx <= xMax) {
1322
+ nextInside = true;
1323
+ }
1324
+
1325
+ if (px && px >= xMin && px <= xMax) {
1326
+ prevInside = true;
1327
+ }
1328
+
1329
+ if (isRange) {
1330
+ if (useRaw) {
1331
+ y = d.slice(1, 3);
1332
+ }
1333
+
1334
+ low = y[0];
1335
+ y = y[1];
1336
+
1337
+ } else if (isStacked) {
1338
+ x = d.x;
1339
+ y = d.stackY;
1340
+ low = y - d.y;
1341
+ }
1342
+
1343
+ if (!series.requireSorting) {
1344
+ isYInside = y >= yMin && y <= yMax;
1345
+ }
1346
+
1347
+ if ((!y || !isYInside)) {
1348
+ return;
1349
+ }
1350
+
1351
+ if (x >= xMin && x <= xMax) {
1352
+ isXInside = true;
1353
+ }
1354
+
1355
+ if (!isXInside && !nextInside && !prevInside) {
1356
+ return;
1357
+ }
1358
+
1359
+ // Skip translations - temporary floating point fix
1360
+ if (!settings.useGPUTranslations) {
1361
+ inst.skipTranslation = true;
1362
+ x = xAxis.toPixels(x, true);
1363
+ y = yAxis.toPixels(y, true);
1364
+ }
1365
+
1366
+ if (drawAsBar) {
1367
+
1368
+ maxVal = y;
1369
+ minVal = 0;
1370
+
1371
+ if (y < 0) {
1372
+ minVal = y;
1373
+ y = 0;
1374
+ }
1375
+
1376
+ if (!settings.useGPUTranslations) {
1377
+ minVal = yAxis.toPixels(minVal, true);
1378
+ }
1379
+
1380
+ // Need to add an extra point here
1381
+ vertice(x, minVal, 0, 0, pcolor);
1382
+ }
1383
+
1384
+ // No markers on out of bounds things.
1385
+ // Out of bound things are shown if and only if the next
1386
+ // or previous point is inside the rect.
1387
+ if (inst.hasMarkers) { // && isXInside) {
1388
+ // x = H.correctFloat(
1389
+ // Math.min(Math.max(-1e5, xAxis.translate(
1390
+ // x,
1391
+ // 0,
1392
+ // 0,
1393
+ // 0,
1394
+ // 1,
1395
+ // 0.5,
1396
+ // false
1397
+ // )), 1e5)
1398
+ // );
1399
+
1400
+ if (lastX !== false) {
1401
+ series.closestPointRangePx = Math.min(
1402
+ series.closestPointRangePx,
1403
+ Math.abs(x - lastX)
1404
+ );
1405
+ }
1406
+ }
1407
+
1408
+ vertice(
1409
+ x,
1410
+ y,
1411
+ 0,
1412
+ series.type === 'bubble' ? (z || 1) : 2,
1413
+ pcolor
1414
+ );
1415
+
1416
+ // Uncomment this to support color axis.
1417
+ // if (caxis) {
1418
+ // color = H.color(caxis.toColor(y)).rgba;
1419
+
1420
+ // inst.colorData.push(color[0] / 255.0);
1421
+ // inst.colorData.push(color[1] / 255.0);
1422
+ // inst.colorData.push(color[2] / 255.0);
1423
+ // inst.colorData.push(color[3]);
1424
+ // }
1425
+
1426
+ lastX = x;
1427
+
1428
+ //return true;
1429
+ });
1430
+ }
1431
+
1432
+ /*
1433
+ * Push a series to the renderer
1434
+ * If we render the series immediatly, we don't have to loop later
1435
+ * @param s {Highchart.Series} - the series to push
1436
+ */
1437
+ function pushSeries(s) {
1438
+ if (series.length > 0) {
1439
+ series[series.length - 1].to = data.length;
1440
+ if (series[series.length - 1].hasMarkers) {
1441
+ series[series.length - 1].markerTo = markerData.length;
1442
+ }
1443
+ }
1444
+
1445
+ if (settings.timeSeriesProcessing) {
1446
+ console.time('building ' + s.type + ' series'); //eslint-disable-line no-console
1447
+ }
1448
+
1449
+ series.push({
1450
+ from: data.length,
1451
+ markerFrom: markerData.length,
1452
+ // Push RGBA values to this array to use per. point coloring.
1453
+ // It should be 0-padded, so each component should be pushed in
1454
+ // succession.
1455
+ colorData: [],
1456
+ series: s,
1457
+ zMin: Number.MAX_VALUE,
1458
+ zMax: -Number.MAX_VALUE,
1459
+ hasMarkers: s.options.marker ? s.options.marker.enabled !== false : false,
1460
+ showMarksers: true,
1461
+ drawMode: ({
1462
+ 'area': 'lines',
1463
+ 'arearange': 'lines',
1464
+ 'areaspline': 'line_strip',
1465
+ 'column': 'lines',
1466
+ 'line': 'line_strip',
1467
+ 'scatter': 'points',
1468
+ 'heatmap': 'triangles',
1469
+ 'treemap': 'triangles',
1470
+ 'bubble': 'points'
1471
+ })[s.type] || 'line_strip'
1472
+ });
1473
+
1474
+ // Add the series data to our buffer(s)
1475
+ pushSeriesData(s, series[series.length - 1]);
1476
+
1477
+ if (settings.timeSeriesProcessing) {
1478
+ console.timeEnd('building ' + s.type + ' series'); //eslint-disable-line no-console
1479
+ }
1480
+ }
1481
+
1482
+ /*
1483
+ * Flush the renderer.
1484
+ * This removes pushed series and vertices.
1485
+ * Should be called after clearing and before rendering
1486
+ */
1487
+ function flush() {
1488
+ series = [];
1489
+ exports.data = data = [];
1490
+ markerData = [];
1491
+ }
1492
+
1493
+ /*
1494
+ * Pass x-axis to shader
1495
+ * @param axis {Highcharts.Axis} - the x-axis
1496
+ */
1497
+ function setXAxis(axis) {
1498
+ if (!shader) {
1499
+ return;
1500
+ }
1501
+
1502
+ shader.setUniform('xAxisTrans', axis.transA);
1503
+ shader.setUniform('xAxisMin', axis.min);
1504
+ shader.setUniform('xAxisMinPad', axis.minPixelPadding);
1505
+ shader.setUniform('xAxisPointRange', axis.pointRange);
1506
+ shader.setUniform('xAxisLen', axis.len);
1507
+ shader.setUniform('xAxisPos', axis.pos);
1508
+ shader.setUniform('xAxisCVSCoord', !axis.horiz);
1509
+ }
1510
+
1511
+ /*
1512
+ * Pass y-axis to shader
1513
+ * @param axis {Highcharts.Axis} - the y-axis
1514
+ */
1515
+ function setYAxis(axis) {
1516
+ if (!shader) {
1517
+ return;
1518
+ }
1519
+
1520
+ shader.setUniform('yAxisTrans', axis.transA);
1521
+ shader.setUniform('yAxisMin', axis.min);
1522
+ shader.setUniform('yAxisMinPad', axis.minPixelPadding);
1523
+ shader.setUniform('yAxisPointRange', axis.pointRange);
1524
+ shader.setUniform('yAxisLen', axis.len);
1525
+ shader.setUniform('yAxisPos', axis.pos);
1526
+ shader.setUniform('yAxisCVSCoord', !axis.horiz);
1527
+ }
1528
+
1529
+ /*
1530
+ * Set the translation threshold
1531
+ * @param has {boolean} - has threshold flag
1532
+ * @param translation {Float} - the threshold
1533
+ */
1534
+ function setThreshold(has, translation) {
1535
+ shader.setUniform('hasThreshold', has);
1536
+ shader.setUniform('translatedThreshold', translation);
1537
+ }
1538
+
1539
+ /*
1540
+ * Render the data
1541
+ * This renders all pushed series.
1542
+ */
1543
+ function render(chart) {
1544
+
1545
+ if (chart) {
1546
+ if (!chart.chartHeight || !chart.chartWidth) {
1547
+ //chart.setChartSize();
1548
+ }
1549
+
1550
+ width = chart.chartWidth || 800;
1551
+ height = chart.chartHeight || 400;
1552
+ } else {
1553
+ return false;
1554
+ }
1555
+
1556
+ if (!gl || !width || !height) {
1557
+ return false;
1558
+ }
1559
+
1560
+ if (settings.timeRendering) {
1561
+ console.time('gl rendering'); //eslint-disable-line no-console
1562
+ }
1563
+
1564
+ shader.bind();
1565
+
1566
+ gl.viewport(0, 0, width, height);
1567
+ shader.setPMatrix(orthoMatrix(width, height));
1568
+
1569
+ gl.lineWidth(settings.lineWidth);
1570
+
1571
+ vbuffer.build(exports.data, 'aVertexPosition', 4);
1572
+ vbuffer.bind();
1573
+
1574
+ if (textureIsReady) {
1575
+ gl.bindTexture(gl.TEXTURE_2D, circleTextureHandle);
1576
+ shader.setTexture(circleTextureHandle);
1577
+ }
1578
+
1579
+ shader.setInverted(chart.options.chart ? chart.options.chart.inverted : false);
1580
+
1581
+ // Render the series
1582
+ each(series, function(s, si) {
1583
+ var options = s.series.options,
1584
+ threshold = options.threshold,
1585
+ hasThreshold = isNumber(threshold),
1586
+ yBottom = s.series.yAxis.getThreshold(threshold),
1587
+ translatedThreshold = yBottom,
1588
+ cbuffer,
1589
+ showMarkers = pick(
1590
+ options.marker ? options.marker.enabled : null,
1591
+ s.series.xAxis.isRadial ? true : null,
1592
+ s.series.closestPointRangePx >
1593
+ 2 * ((
1594
+ options.marker ?
1595
+ options.marker.radius :
1596
+ 10
1597
+ ) || 10)
1598
+ ),
1599
+ fillColor = s.series.fillOpacity ?
1600
+ new Color(s.series.color).setOpacity(
1601
+ pick(options.fillOpacity, 0.85)
1602
+ ).get() :
1603
+ s.series.color,
1604
+ color;
1605
+
1606
+ vbuffer.bind();
1607
+
1608
+ if (options.colorByPoint) {
1609
+ fillColor = s.series.chart.options.colors[si];
1610
+ }
1611
+
1612
+ color = H.color(fillColor).rgba;
1613
+
1614
+ if (!settings.useAlpha) {
1615
+ color[3] = 1.0;
1616
+ }
1617
+
1618
+ //Blending
1619
+ if (options.boostBlending === 'add') { // docs
1620
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
1621
+ gl.blendEquation(gl.FUNC_ADD);
1622
+
1623
+ } else if (options.boostBlending === 'mult') {
1624
+ gl.blendFunc(gl.DST_COLOR, gl.ZERO);
1625
+
1626
+ } else if (options.boostBlending === 'darken') {
1627
+ gl.blendFunc(gl.ONE, gl.ONE);
1628
+ gl.blendEquation(gl.FUNC_MIN);
1629
+
1630
+ } else {
1631
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); //, gl.ONE, gl.ZERO);
1632
+ gl.blendEquation(gl.FUNC_ADD);
1633
+ }
1634
+
1635
+ shader.reset();
1636
+
1637
+ // If there are entries in the colorData buffer, build and bind it.
1638
+ if (s.colorData.length > 0) {
1639
+ shader.setUniform('hasColor', 1.0);
1640
+ cbuffer = GLVertexBuffer(gl, shader); //eslint-disable-line new-cap
1641
+ cbuffer.build(s.colorData, 'aColor', 4);
1642
+ cbuffer.bind();
1643
+ }
1644
+
1645
+ // Set series specific uniforms
1646
+ shader.setColor(color);
1647
+ setXAxis(s.series.xAxis);
1648
+ setYAxis(s.series.yAxis);
1649
+ setThreshold(hasThreshold, translatedThreshold);
1650
+
1651
+ if (s.drawMode === 'points') {
1652
+ if (options.marker && options.marker.radius) {
1653
+ shader.setPointSize(options.marker.radius * 2.0);
1654
+ } else {
1655
+ shader.setPointSize(1);
1656
+ }
1657
+ }
1658
+
1659
+ // If set to true, the toPixels translations in the shader
1660
+ // is skipped, i.e it's assumed that the value is a pixel coord.
1661
+ shader.setSkipTranslation(s.skipTranslation);
1662
+
1663
+ if (s.series.type === 'bubble') {
1664
+ shader.setBubbleUniforms(s.series, s.zMin, s.zMax);
1665
+ }
1666
+
1667
+ shader.setDrawAsCircle((asCircle[s.series.type] && textureIsReady) || false);
1668
+
1669
+ // Do the actual rendering
1670
+ vbuffer.render(s.from, s.to, s.drawMode);
1671
+
1672
+ if (s.hasMarkers && showMarkers) {
1673
+ if (options.marker && options.marker.radius) {
1674
+ shader.setPointSize(options.marker.radius * 2.0);
1675
+ } else {
1676
+ shader.setPointSize(10);
1677
+ }
1678
+ shader.setDrawAsCircle(true);
1679
+ vbuffer.render(s.from, s.to, 'POINTS');
1680
+ }
1681
+ });
1682
+
1683
+ vbuffer.destroy();
1684
+
1685
+ if (settings.timeRendering) {
1686
+ console.timeEnd('gl rendering'); //eslint-disable-line no-console
1687
+ }
1688
+
1689
+ flush();
1690
+
1691
+ if (postRenderCallback) {
1692
+ postRenderCallback();
1693
+ }
1694
+ }
1695
+
1696
+ /*
1697
+ * Render the data when ready
1698
+ */
1699
+ function renderWhenReady(chart) {
1700
+ clear();
1701
+
1702
+ if (chart.renderer.forExport) {
1703
+ return render(chart);
1704
+ }
1705
+
1706
+ if (isInited) {
1707
+ render(chart);
1708
+ } else {
1709
+ setTimeout(function() {
1710
+ renderWhenReady(chart);
1711
+ }, 1);
1712
+ }
1713
+ }
1714
+
1715
+ /*
1716
+ * Set the viewport size in pixels
1717
+ * Creates an orthographic perspective matrix and applies it.
1718
+ * @param w {Integer} - the width of the viewport
1719
+ * @param h {Integer} - the height of the viewport
1720
+ */
1721
+ function setSize(w, h) {
1722
+ // Skip if there's no change
1723
+ if (width === w && h === h) {
1724
+ return;
1725
+ }
1726
+
1727
+ width = w;
1728
+ height = h;
1729
+
1730
+ shader.bind();
1731
+ shader.setPMatrix(orthoMatrix(width, height));
1732
+ }
1733
+
1734
+ /*
1735
+ * Init OpenGL
1736
+ * @param canvas {HTMLCanvas} - the canvas to render to
1737
+ */
1738
+ function init(canvas, noFlush) {
1739
+ var i = 0,
1740
+ activeContext,
1741
+ contexts = [
1742
+ 'webgl',
1743
+ 'experimental-webgl',
1744
+ 'moz-webgl',
1745
+ 'webkit-3d'
1746
+ ];
1747
+
1748
+ isInited = false;
1749
+
1750
+ if (!canvas) {
1751
+ return false;
1752
+ }
1753
+
1754
+ if (settings.timeSetup) {
1755
+ console.time('gl setup'); //eslint-disable-line no-console
1756
+ }
1757
+
1758
+ for (; i < contexts.length; i++) {
1759
+ gl = canvas.getContext(contexts[i]);
1760
+ if (gl) {
1761
+ activeContext = contexts[i];
1762
+ break;
1763
+ }
1764
+ }
1765
+
1766
+ if (gl) {
1767
+ if (!noFlush) {
1768
+ flush();
1769
+ }
1770
+ } else {
1771
+ return false;
1772
+ }
1773
+
1774
+ gl.enable(gl.BLEND);
1775
+ // gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
1776
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
1777
+ gl.disable(gl.DEPTH_TEST);
1778
+ gl.depthMask(gl.FALSE);
1779
+
1780
+ shader = GLShader(gl); //eslint-disable-line new-cap
1781
+ vbuffer = GLVertexBuffer(gl, shader); //eslint-disable-line new-cap
1782
+
1783
+ textureIsReady = false;
1784
+
1785
+ // Set up the circle texture used for bubbles
1786
+ circleTextureHandle = gl.createTexture();
1787
+
1788
+ // Draw the circle
1789
+ circleTexture.width = 512;
1790
+ circleTexture.height = 512;
1791
+
1792
+ circleCtx.fillStyle = '#FFF';
1793
+ circleCtx.beginPath();
1794
+ circleCtx.arc(256, 256, 256, 0, 2 * Math.PI);
1795
+ circleCtx.fill();
1796
+
1797
+ try {
1798
+
1799
+ gl.bindTexture(gl.TEXTURE_2D, circleTextureHandle);
1800
+
1801
+ gl.texImage2D(
1802
+ gl.TEXTURE_2D,
1803
+ 0,
1804
+ gl.RGBA,
1805
+ gl.RGBA,
1806
+ gl.UNSIGNED_BYTE,
1807
+ circleTexture
1808
+ );
1809
+
1810
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1811
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1812
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1813
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
1814
+
1815
+ gl.generateMipmap(gl.TEXTURE_2D);
1816
+
1817
+ gl.bindTexture(gl.TEXTURE_2D, null);
1818
+
1819
+ textureIsReady = true;
1820
+ } catch (e) {}
1821
+
1822
+ isInited = true;
1823
+
1824
+ if (settings.timeSetup) {
1825
+ console.timeEnd('gl setup'); //eslint-disable-line no-console
1826
+ }
1827
+
1828
+ return true;
1829
+ }
1830
+
1831
+ /*
1832
+ * Check if we have a valid OGL context
1833
+ * @returns {Boolean} - true if the context is valid
1834
+ */
1835
+ function valid() {
1836
+ return gl !== false;
1837
+ }
1838
+
1839
+ /*
1840
+ * Check if the renderer has been initialized
1841
+ * @returns {Boolean} - true if it has, false if not
1842
+ */
1843
+ function inited() {
1844
+ return isInited;
1845
+ }
1846
+
1847
+ function destroy() {
1848
+ vbuffer.destroy();
1849
+ shader.destroy();
1850
+ if (gl) {
1851
+ //gl.deleteTexture(circleTextureHandle);
1852
+ }
1853
+ }
1854
+
1855
+ ////////////////////////////////////////////////////////////////////////////
1856
+ exports = {
1857
+ allocateBufferForSingleSeries: allocateBufferForSingleSeries,
1858
+ pushSeries: pushSeries,
1859
+ setSize: setSize,
1860
+ inited: inited,
1861
+ setThreshold: setThreshold,
1862
+ init: init,
1863
+ render: renderWhenReady,
1864
+ settings: settings,
1865
+ valid: valid,
1866
+ clear: clear,
1867
+ flush: flush,
1868
+ setXAxis: setXAxis,
1869
+ setYAxis: setYAxis,
1870
+ data: data,
1871
+ gl: getGL,
1872
+ allocateBuffer: allocateBuffer,
1873
+ destroy: destroy,
1874
+ setOptions: setOptions
1875
+ };
1876
+
1877
+ return exports;
1878
+ }
1879
+
1880
+ // END OF WEBGL ABSTRACTIONS
1881
+ ////////////////////////////////////////////////////////////////////////////////
1882
+
1883
+ /*
1884
+ * Create a canvas + context and attach it to the target
1885
+ * @param target {Highcharts.Chart|Highcharts.Series} - the canvas target
1886
+ * @param chart {Highcharts.Chart} - the chart
1887
+ */
1888
+ function createAndAttachRenderer(chart, series) {
1889
+ var width = chart.chartWidth,
1890
+ height = chart.chartHeight,
1891
+ target = chart,
1892
+ targetGroup = chart.seriesGroup || series.group,
1893
+ swapXY = function(proceed, x, y, a, b, c, d) {
1894
+ proceed.call(series, y, x, a, b, c, d);
1895
+ };
1896
+
1897
+ if (isChartSeriesBoosting(chart)) {
1898
+ target = chart;
1899
+ } else {
1900
+ target = series;
1901
+ }
1902
+
1903
+ if (target.ogl) {
1904
+ //target.ogl.destroy();
1905
+ }
1906
+
1907
+ if (!target.image) {
1908
+ target.canvas = doc.createElement('canvas');
1909
+
1910
+ target.image = chart.renderer.image(
1911
+ '',
1912
+ 0,
1913
+ 0,
1914
+ width,
1915
+ height
1916
+ ).add(targetGroup);
1917
+
1918
+ target.boostClipRect = chart.renderer.clipRect(
1919
+ chart.plotLeft,
1920
+ chart.plotTop,
1921
+ chart.plotWidth,
1922
+ chart.chartHeight
1923
+ );
1924
+
1925
+ target.image.clip(target.boostClipRect);
1926
+
1927
+ if (target.inverted) {
1928
+ each(['moveTo', 'lineTo', 'rect', 'arc'], function(fn) {
1929
+ wrap(false, fn, swapXY);
1930
+ });
1931
+ }
1932
+
1933
+ if (target instanceof H.Chart) {
1934
+ target.markerGroup = target.renderer.g().add(targetGroup);
1935
+
1936
+ target.markerGroup.translateX = series.xAxis.pos;
1937
+ target.markerGroup.translateY = series.yAxis.pos;
1938
+ target.markerGroup.updateTransform();
1939
+ }
1940
+ }
1941
+
1942
+ target.canvas.width = width;
1943
+ target.canvas.height = height;
1944
+
1945
+ target.image.attr({
1946
+ x: 0,
1947
+ y: 0,
1948
+ width: width,
1949
+ height: height,
1950
+ style: 'pointer-events: none'
1951
+ });
1952
+
1953
+ target.boostClipRect.attr({
1954
+ x: chart.plotLeft,
1955
+ y: chart.plotTop,
1956
+ width: chart.plotWidth,
1957
+ height: chart.chartHeight
1958
+ });
1959
+
1960
+ if (!target.ogl) {
1961
+
1962
+
1963
+ target.ogl = GLRenderer(function() { // eslint-disable-line new-cap
1964
+ target.image.attr({
1965
+ href: target.canvas.toDataURL('image/png')
1966
+ });
1967
+ }); //eslint-disable-line new-cap
1968
+
1969
+ target.ogl.init(target.canvas);
1970
+ // target.ogl.clear();
1971
+ target.ogl.setOptions(chart.options.boost || {});
1972
+
1973
+ if (target instanceof H.Chart) {
1974
+ target.ogl.allocateBuffer(chart);
1975
+ }
1976
+ }
1977
+
1978
+ target.ogl.setSize(width, height);
1979
+
1980
+ return target.ogl;
1981
+ }
1982
+
1983
+ /*
1984
+ * Performs the actual render if the renderer is
1985
+ * attached to the series.
1986
+ * @param renderer {OGLRenderer} - the renderer
1987
+ * @param series {Highcharts.Series} - the series
1988
+ */
1989
+ function renderIfNotSeriesBoosting(renderer, series, chart) {
1990
+ if (renderer &&
1991
+ series.image &&
1992
+ series.canvas &&
1993
+ !isChartSeriesBoosting(chart || series.chart)
1994
+ ) {
1995
+ renderer.render(chart || series.chart);
1996
+ }
1997
+ }
69
1998
 
70
- var win = H.win,
71
- doc = win.document,
72
- noop = function() {},
73
- Color = H.Color,
74
- Series = H.Series,
75
- seriesTypes = H.seriesTypes,
76
- each = H.each,
77
- extend = H.extend,
78
- addEvent = H.addEvent,
79
- fireEvent = H.fireEvent,
80
- grep = H.grep,
81
- isNumber = H.isNumber,
82
- merge = H.merge,
83
- pick = H.pick,
84
- wrap = H.wrap,
85
- plotOptions = H.getOptions().plotOptions,
86
- CHUNK_SIZE = 50000,
87
- destroyLoadingDiv;
1999
+ function allocateIfNotSeriesBoosting(renderer, series) {
2000
+ if (renderer &&
2001
+ series.image &&
2002
+ series.canvas &&
2003
+ !isChartSeriesBoosting(series.chart)
2004
+ ) {
2005
+ renderer.allocateBufferForSingleSeries(series);
2006
+ }
2007
+ }
88
2008
 
89
- function eachAsync(arr, fn, finalFunc, chunkSize, i) {
2009
+ /*
2010
+ * An "async" foreach loop.
2011
+ * Uses a setTimeout to keep the loop from blocking the UI thread
2012
+ * @param arr {Array} - the array to loop through
2013
+ * @param fn {Function} - the callback to call for each item
2014
+ * @param finalFunc {Function} - the callback to call when done
2015
+ * @param chunkSize {Number} - the number of iterations per. timeout
2016
+ * @param i {Number} - the current index
2017
+ * @param noTimeout {Boolean} - set to true to skip timeouts
2018
+ */
2019
+ function eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout) {
90
2020
  i = i || 0;
91
2021
  chunkSize = chunkSize || CHUNK_SIZE;
92
2022
 
@@ -95,41 +2025,154 @@
95
2025
 
96
2026
  while (proceed && i < threshold && i < arr.length) {
97
2027
  proceed = fn(arr[i], i);
98
- i = i + 1;
2028
+ ++i;
99
2029
  }
100
2030
  if (proceed) {
101
2031
  if (i < arr.length) {
102
- setTimeout(function() {
103
- eachAsync(arr, fn, finalFunc, chunkSize, i);
104
- });
2032
+
2033
+ if (noTimeout) {
2034
+ eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout);
2035
+ } else if (win.requestAnimationFrame) {
2036
+ //If available, do requestAnimationFrame - shaves off a few ms
2037
+ win.requestAnimationFrame(function() {
2038
+ eachAsync(arr, fn, finalFunc, chunkSize, i);
2039
+ });
2040
+ } else {
2041
+ setTimeout(function() {
2042
+ eachAsync(arr, fn, finalFunc, chunkSize, i);
2043
+ });
2044
+ }
2045
+
105
2046
  } else if (finalFunc) {
106
2047
  finalFunc();
107
2048
  }
108
2049
  }
109
2050
  }
110
2051
 
2052
+ ////////////////////////////////////////////////////////////////////////////////
2053
+ // Following is the parts of the boost that's common between OGL/Legacy
2054
+
2055
+ /**
2056
+ * Return a full Point object based on the index.
2057
+ * The boost module uses stripped point objects for performance reasons.
2058
+ * @param {Number} boostPoint A stripped-down point object
2059
+ * @returns {Object} A Point object as per http://api.highcharts.com/highcharts#Point
2060
+ */
2061
+ Series.prototype.getPoint = function(boostPoint) {
2062
+ var point = boostPoint,
2063
+ xData = this.xData || this.options.xData || this.processedXData || false;
2064
+
2065
+ if (boostPoint && !(boostPoint instanceof this.pointClass)) {
2066
+ point = (new this.pointClass()).init( // eslint-disable-line new-cap
2067
+ this,
2068
+ this.options.data[boostPoint.i],
2069
+ xData ? xData[boostPoint.i] : undefined
2070
+ );
2071
+
2072
+ point.category = point.x;
2073
+
2074
+ point.dist = boostPoint.dist;
2075
+ point.distX = boostPoint.distX;
2076
+ point.plotX = boostPoint.plotX;
2077
+ point.plotY = boostPoint.plotY;
2078
+ point.index = boostPoint.i;
2079
+ }
2080
+
2081
+ return point;
2082
+ };
2083
+
2084
+ /**
2085
+ * Return a point instance from the k-d-tree
2086
+ */
2087
+ wrap(Series.prototype, 'searchPoint', function(proceed) {
2088
+ return this.getPoint(
2089
+ proceed.apply(this, [].slice.call(arguments, 1))
2090
+ );
2091
+ });
2092
+
2093
+ /**
2094
+ * Extend series.destroy to also remove the fake k-d-tree points (#5137).
2095
+ * Normally this is handled by Series.destroy that calls Point.destroy,
2096
+ * but the fake search points are not registered like that.
2097
+ */
2098
+ wrap(Series.prototype, 'destroy', function(proceed) {
2099
+ var series = this,
2100
+ chart = series.chart;
2101
+
2102
+ if (chart.markerGroup === series.markerGroup) {
2103
+ series.markerGroup = null;
2104
+ }
2105
+
2106
+ if (chart.hoverPoints) {
2107
+ chart.hoverPoints = grep(chart.hoverPoints, function(point) {
2108
+ return point.series === series;
2109
+ });
2110
+ }
2111
+
2112
+ if (chart.hoverPoint && chart.hoverPoint.series === series) {
2113
+ chart.hoverPoint = null;
2114
+ }
2115
+
2116
+ proceed.call(this);
2117
+ });
2118
+
2119
+ /**
2120
+ * Do not compute extremes when min and max are set.
2121
+ * If we use this in the core, we can add the hook
2122
+ * to hasExtremes to the methods directly.
2123
+ */
2124
+ wrap(Series.prototype, 'getExtremes', function(proceed) {
2125
+ if (!isSeriesBoosting(this) || (!this.hasExtremes || !this.hasExtremes())) {
2126
+ return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
2127
+ }
2128
+ });
2129
+
111
2130
  // Set default options
112
- each(
113
- ['area', 'arearange', 'bubble', 'column', 'line', 'scatter'],
2131
+ each([
2132
+ 'area',
2133
+ 'arearange',
2134
+ 'column',
2135
+ 'line',
2136
+ 'scatter',
2137
+ 'heatmap',
2138
+ 'bubble',
2139
+ 'treemap',
2140
+ 'heatmap'
2141
+ ],
114
2142
  function(type) {
115
2143
  if (plotOptions[type]) {
116
2144
  plotOptions[type].boostThreshold = 5000;
2145
+ plotOptions[type].boostData = [];
117
2146
  }
118
2147
  }
119
2148
  );
120
2149
 
121
2150
  /**
122
- * Override a bunch of methods the same way. If the number of points is below the threshold,
123
- * run the original method. If not, check for a canvas version or do nothing.
2151
+ * Override a bunch of methods the same way. If the number of points is
2152
+ * below the threshold, run the original method. If not, check for a
2153
+ * canvas version or do nothing.
2154
+ *
2155
+ * Note that we're not overriding any of these for heatmaps.
124
2156
  */
125
- each(['translate', 'generatePoints', 'drawTracker', 'drawPoints', 'render'], function(method) {
2157
+ each([
2158
+ 'translate',
2159
+ 'generatePoints',
2160
+ 'drawTracker',
2161
+ 'drawPoints',
2162
+ 'render'
2163
+ ], function(method) {
126
2164
  function branch(proceed) {
127
- var letItPass = this.options.stacking && (method === 'translate' || method === 'generatePoints');
128
- if ((this.processedXData || this.options.data).length < (this.options.boostThreshold || Number.MAX_VALUE) ||
129
- letItPass) {
2165
+ var letItPass = this.options.stacking &&
2166
+ (method === 'translate' || method === 'generatePoints');
2167
+
2168
+ if (!isSeriesBoosting(this) ||
2169
+ letItPass ||
2170
+ this.type === 'heatmap' ||
2171
+ this.type === 'treemap'
2172
+ ) {
130
2173
 
131
2174
  // Clear image
132
- if (method === 'render' && this.image) {
2175
+ if (method === 'render' && this.image && !isChartSeriesBoosting(this.chart)) {
133
2176
  this.image.attr({
134
2177
  href: ''
135
2178
  });
@@ -140,58 +2183,108 @@
140
2183
 
141
2184
  // If a canvas version of the method exists, like renderCanvas(), run
142
2185
  } else if (this[method + 'Canvas']) {
143
-
144
2186
  this[method + 'Canvas']();
145
2187
  }
146
2188
  }
2189
+
147
2190
  wrap(Series.prototype, method, branch);
148
2191
 
149
- // A special case for some types - its translate method is already wrapped
2192
+ // A special case for some types - their translate method is already wrapped
150
2193
  if (method === 'translate') {
151
- each(['arearange', 'bubble', 'column'], function(type) {
152
- if (seriesTypes[type]) {
153
- wrap(seriesTypes[type].prototype, method, branch);
154
- }
155
- });
2194
+ if (seriesTypes.column) {
2195
+ wrap(seriesTypes.column.prototype, method, branch);
2196
+ }
2197
+
2198
+ if (seriesTypes.arearange) {
2199
+ wrap(seriesTypes.arearange.prototype, method, branch);
2200
+ }
2201
+
2202
+ if (seriesTypes.treemap) {
2203
+ wrap(seriesTypes.treemap.prototype, method, branch);
2204
+ }
156
2205
  }
157
2206
  });
158
2207
 
159
- /**
160
- * Do not compute extremes when min and max are set.
161
- * If we use this in the core, we can add the hook to hasExtremes to the methods directly.
2208
+ /*
2209
+ * Returns true if the current browser supports webgl
162
2210
  */
163
- wrap(Series.prototype, 'getExtremes', function(proceed) {
164
- if (!this.hasExtremes()) {
165
- proceed.apply(this, Array.prototype.slice.call(arguments, 1));
2211
+ function hasWebGLSupport() {
2212
+ var i = 0,
2213
+ canvas,
2214
+ contexts = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'],
2215
+ context = false;
2216
+
2217
+ if (typeof win.WebGLRenderingContext !== 'undefined') {
2218
+ canvas = doc.createElement('canvas');
2219
+
2220
+ for (; i < contexts.length; i++) {
2221
+ try {
2222
+ context = canvas.getContext(contexts[i]);
2223
+ if (typeof context !== 'undefined' && context !== null) {
2224
+ return true;
2225
+ }
2226
+ } catch (e) {
2227
+
2228
+ }
2229
+ }
166
2230
  }
167
- });
168
- wrap(Series.prototype, 'setData', function(proceed) {
169
- if (!this.hasExtremes(true)) {
170
- proceed.apply(this, Array.prototype.slice.call(arguments, 1));
2231
+
2232
+ return false;
2233
+ }
2234
+
2235
+ ////////////////////////////////////////////////////////////////////////////////
2236
+ // We're wrapped in a closure, so just return if there's no webgl support
2237
+
2238
+ if (!hasWebGLSupport()) {
2239
+ if (typeof H.initCanvasBoost !== 'undefined') {
2240
+ // Fallback to canvas boost
2241
+ H.initCanvasBoost();
2242
+ } else {
2243
+ H.error(26);
171
2244
  }
172
- });
2245
+ //eslint-disable
2246
+ return;
2247
+ //eslint-enable
2248
+ }
2249
+
2250
+ ////////////////////////////////////////////////////////////////////////////////
2251
+ // GL-SPECIFIC WRAPPINGS FOLLOWS
2252
+
2253
+ /** If the series is a heatmap or treemap, or if the series is not boosting
2254
+ * do the default behaviour. Otherwise, process if the series has no
2255
+ * extremes.
2256
+ */
173
2257
  wrap(Series.prototype, 'processData', function(proceed) {
174
- if (!this.hasExtremes(true)) {
2258
+ // If this is a heatmap, do default behaviour
2259
+ if (!isSeriesBoosting(this) ||
2260
+ this.type === 'heatmap' ||
2261
+ this.type === 'treemap') {
175
2262
  proceed.apply(this, Array.prototype.slice.call(arguments, 1));
176
2263
  }
177
- });
178
2264
 
2265
+ if (!this.hasExtremes || !this.hasExtremes(true)) {
2266
+ proceed.apply(this, Array.prototype.slice.call(arguments, 1));
2267
+ }
2268
+ });
179
2269
 
180
2270
  H.extend(Series.prototype, {
181
2271
  pointRange: 0,
2272
+ directTouch: false,
182
2273
  allowDG: false, // No data grouping, let boost handle large data
183
2274
  hasExtremes: function(checkX) {
184
2275
  var options = this.options,
185
2276
  data = options.data,
186
2277
  xAxis = this.xAxis && this.xAxis.options,
187
2278
  yAxis = this.yAxis && this.yAxis.options;
188
- return data.length > (options.boostThreshold || Number.MAX_VALUE) && isNumber(yAxis.min) && isNumber(yAxis.max) &&
2279
+
2280
+ return data.length > (options.boostThreshold || Number.MAX_VALUE) &&
2281
+ isNumber(yAxis.min) && isNumber(yAxis.max) &&
189
2282
  (!checkX || (isNumber(xAxis.min) && isNumber(xAxis.max)));
190
2283
  },
191
2284
 
192
2285
  /**
193
- * If implemented in the core, parts of this can probably be shared with other similar
194
- * methods in Highcharts.
2286
+ * If implemented in the core, parts of this can probably be
2287
+ * shared with other similar methods in Highcharts.
195
2288
  */
196
2289
  destroyGraphics: function() {
197
2290
  var series = this,
@@ -215,65 +2308,18 @@
215
2308
  });
216
2309
  },
217
2310
 
218
- /**
219
- * Create a hidden canvas to draw the graph on. The contents is later copied over
220
- * to an SVG image element.
221
- */
222
- getContext: function() {
223
- var chart = this.chart,
224
- width = chart.plotWidth,
225
- height = chart.plotHeight,
226
- ctx = this.ctx,
227
- swapXY = function(proceed, x, y, a, b, c, d) {
228
- proceed.call(this, y, x, a, b, c, d);
229
- };
230
-
231
- if (!this.canvas) {
232
- this.canvas = doc.createElement('canvas');
233
- this.image = chart.renderer.image('', 0, 0, width, height).add(this.group);
234
- this.ctx = ctx = this.canvas.getContext('2d');
235
- if (chart.inverted) {
236
- each(['moveTo', 'lineTo', 'rect', 'arc'], function(fn) {
237
- wrap(ctx, fn, swapXY);
238
- });
239
- }
240
- } else {
241
- ctx.clearRect(0, 0, width, height);
242
- }
243
-
244
- this.canvas.width = width;
245
- this.canvas.height = height;
246
- this.image.attr({
247
- width: width,
248
- height: height
249
- });
250
-
251
- return ctx;
252
- },
253
-
254
- /**
255
- * Draw the canvas image inside an SVG image
256
- */
257
- canvasToSVG: function() {
258
- this.image.attr({
259
- href: this.canvas.toDataURL('image/png')
260
- });
261
- },
262
-
263
- cvsLineTo: function(ctx, clientX, plotY) {
264
- ctx.lineTo(clientX, plotY);
265
- },
266
-
267
2311
  renderCanvas: function() {
268
2312
  var series = this,
269
- options = series.options,
2313
+ options = series.options || {},
2314
+ renderer = false,
270
2315
  chart = series.chart,
271
2316
  xAxis = this.xAxis,
272
2317
  yAxis = this.yAxis,
273
- ctx,
274
- c = 0,
275
- xData = series.processedXData,
276
- yData = series.processedYData,
2318
+ //ctx,
2319
+ //c = 0,
2320
+ xData = options.xData || series.processedXData,
2321
+ yData = options.yData || series.processedYData,
2322
+
277
2323
  rawData = options.data,
278
2324
  xExtremes = xAxis.getExtremes(),
279
2325
  xMin = xExtremes.min,
@@ -285,24 +2331,21 @@
285
2331
  lastClientX,
286
2332
  sampling = !!series.sampling,
287
2333
  points,
288
- r = options.marker && options.marker.radius,
289
- cvsDrawPoint = this.cvsDrawPoint,
290
- cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
291
- cvsMarker = r && r <= 1 ?
292
- this.cvsMarkerSquare :
293
- this.cvsMarkerCircle,
294
- strokeBatch = this.cvsStrokeBatch || 1000,
2334
+ // r = options.marker && options.marker.radius,
2335
+ // cvsDrawPoint = this.cvsDrawPoint,
2336
+ // cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
2337
+ // cvsMarker = r <= 1 ? this.cvsMarkerSquare : this.cvsMarkerCircle,
295
2338
  enableMouseTracking = options.enableMouseTracking !== false,
296
- lastPoint,
2339
+ // lastPoint,
297
2340
  threshold = options.threshold,
298
2341
  yBottom = yAxis.getThreshold(threshold),
299
2342
  hasThreshold = isNumber(threshold),
300
- translatedThreshold = yBottom,
301
- doFill = this.fill,
302
- isRange = series.pointArrayMap && series.pointArrayMap.join(',') === 'low,high',
2343
+ // translatedThreshold = yBottom,
2344
+ // doFill = this.fill,
2345
+ isRange = series.pointArrayMap &&
2346
+ series.pointArrayMap.join(',') === 'low,high',
303
2347
  isStacked = !!options.stacking,
304
2348
  cropStart = series.cropStart || 0,
305
- loadingOptions = chart.options.loading,
306
2349
  requireSorting = series.requireSorting,
307
2350
  wasNull,
308
2351
  connectNulls = options.connectNulls,
@@ -311,62 +2354,20 @@
311
2354
  maxVal,
312
2355
  minI,
313
2356
  maxI,
314
- fillColor = series.fillOpacity ?
315
- new Color(series.color).setOpacity(pick(options.fillOpacity, 0.75)).get() :
316
- series.color,
317
- stroke = function() {
318
- if (doFill) {
319
- ctx.fillStyle = fillColor;
320
- ctx.fill();
321
- } else {
322
- ctx.strokeStyle = series.color;
323
- ctx.lineWidth = options.lineWidth;
324
- ctx.stroke();
325
- }
326
- },
327
- drawPoint = function(clientX, plotY, yBottom, i) {
328
- if (c === 0) {
329
- ctx.beginPath();
330
-
331
- if (cvsLineTo) {
332
- ctx.lineJoin = 'round';
333
- }
334
- }
335
-
336
- if (wasNull) {
337
- ctx.moveTo(clientX, plotY);
338
- } else {
339
- if (cvsDrawPoint) {
340
- cvsDrawPoint(ctx, clientX, plotY, yBottom, lastPoint);
341
- } else if (cvsLineTo) {
342
- cvsLineTo(ctx, clientX, plotY);
343
- } else if (cvsMarker) {
344
- cvsMarker.call(series, ctx, clientX, plotY, r, i);
345
- }
346
- }
347
-
348
- // We need to stroke the line for every 1000 pixels. It will crash the browser
349
- // memory use if we stroke too infrequently.
350
- c = c + 1;
351
- if (c === strokeBatch) {
352
- stroke();
353
- c = 0;
354
- }
355
-
356
- // Area charts need to keep track of the last point
357
- lastPoint = {
358
- clientX: clientX,
359
- plotY: plotY,
360
- yBottom: yBottom
361
- };
362
- },
2357
+ // fillColor = series.fillOpacity ?
2358
+ // new Color(series.color).setOpacity(
2359
+ // pick(options.fillOpacity, 0.75)
2360
+ // ).get() : series.color,
363
2361
 
364
2362
  addKDPoint = function(clientX, plotY, i) {
2363
+ //Shaves off about 60ms compared to repeated concatination
2364
+ index = clientX + ',' + plotY;
365
2365
 
366
- // The k-d tree requires series points. Reduce the amount of points, since the time to build the
2366
+ // The k-d tree requires series points.
2367
+ // Reduce the amount of points, since the time to build the
367
2368
  // tree increases exponentially.
368
- if (enableMouseTracking && !pointTaken[clientX + ',' + plotY]) {
369
- pointTaken[clientX + ',' + plotY] = true;
2369
+ if (enableMouseTracking && !pointTaken[index]) {
2370
+ pointTaken[index] = true;
370
2371
 
371
2372
  if (chart.inverted) {
372
2373
  clientX = xAxis.len - clientX;
@@ -382,49 +2383,54 @@
382
2383
  }
383
2384
  };
384
2385
 
2386
+ // Get or create the renderer
2387
+ renderer = createAndAttachRenderer(chart, series);
2388
+
2389
+ if (!this.visible) {
2390
+ if (!isChartSeriesBoosting(chart) && renderer) {
2391
+ renderer.clear();
2392
+ this.image.attr({
2393
+ href: ''
2394
+ });
2395
+ }
2396
+ return;
2397
+ }
2398
+
385
2399
  // If we are zooming out from SVG mode, destroy the graphics
386
2400
  if (this.points || this.graph) {
387
2401
  this.destroyGraphics();
388
2402
  }
389
2403
 
390
- // The group
391
- series.plotGroup(
392
- 'group',
393
- 'series',
394
- series.visible ? 'visible' : 'hidden',
395
- options.zIndex,
396
- chart.seriesGroup
397
- );
398
-
399
- series.markerGroup = series.group;
400
- addEvent(series, 'destroy', function() {
401
- series.markerGroup = null;
402
- });
2404
+ // If we're rendering per. series we should create the marker groups
2405
+ // as usual.
2406
+ if (!isChartSeriesBoosting(chart)) {
2407
+ this.markerGroup = series.plotGroup(
2408
+ 'markerGroup',
2409
+ 'markers',
2410
+ true,
2411
+ 1,
2412
+ chart.seriesGroup
2413
+ );
2414
+ } else {
2415
+ //Use a single group for the markers
2416
+ this.markerGroup = chart.markerGroup;
2417
+ }
403
2418
 
404
2419
  points = this.points = [];
405
- ctx = this.getContext();
406
- series.buildKDTree = noop; // Do not start building while drawing
407
-
408
- // Display a loading indicator
409
- if (rawData.length > 99999) {
410
- chart.options.loading = merge(loadingOptions, {
411
- labelStyle: {
412
- backgroundColor: H.color('#ffffff').setOpacity(0.75).get(),
413
- padding: '1em',
414
- borderRadius: '0.5em'
415
- },
416
- style: {
417
- backgroundColor: 'none',
418
- opacity: 1
419
- }
420
- });
421
- clearTimeout(destroyLoadingDiv);
422
- chart.showLoading('Drawing...');
423
- chart.options.loading = loadingOptions; // reset
2420
+
2421
+ // Do not start building while drawing
2422
+ series.buildKDTree = noop;
2423
+
2424
+ if (renderer) {
2425
+ allocateIfNotSeriesBoosting(renderer, this);
2426
+ renderer.pushSeries(series);
2427
+ // Perform the actual renderer if we're on series level
2428
+ renderIfNotSeriesBoosting(renderer, this, chart);
2429
+ //console.log(series, chart);
424
2430
  }
425
2431
 
426
- // Loop over the points
427
- eachAsync(isStacked ? series.data : (xData || rawData), function(d, i) {
2432
+ /* This builds the KD-tree */
2433
+ function processPoint(d, i) {
428
2434
  var x,
429
2435
  y,
430
2436
  clientX,
@@ -465,7 +2471,9 @@
465
2471
 
466
2472
  if (!isNull && x >= xMin && x <= xMax && isYInside) {
467
2473
 
468
- clientX = Math.round(xAxis.toPixels(x, true));
2474
+ // We use ceil to allow the KD tree to work with sub pixels,
2475
+ // which can be used in boost to space pixels
2476
+ clientX = Math.ceil(xAxis.toPixels(x, true));
469
2477
 
470
2478
  if (sampling) {
471
2479
  if (minI === undefined || clientX === lastClientX) {
@@ -486,61 +2494,29 @@
486
2494
  if (minI !== undefined) { // then maxI is also a number
487
2495
  plotY = yAxis.toPixels(maxVal, true);
488
2496
  yBottom = yAxis.toPixels(minVal, true);
489
- drawPoint(
490
- clientX,
491
- hasThreshold ? Math.min(plotY, translatedThreshold) : plotY,
492
- hasThreshold ? Math.max(yBottom, translatedThreshold) : yBottom,
493
- i
494
- );
2497
+
495
2498
  addKDPoint(clientX, plotY, maxI);
496
2499
  if (yBottom !== plotY) {
497
2500
  addKDPoint(clientX, yBottom, minI);
498
2501
  }
499
2502
  }
500
2503
 
501
-
502
2504
  minI = maxI = undefined;
503
2505
  lastClientX = clientX;
504
2506
  }
505
2507
  } else {
506
- plotY = Math.round(yAxis.toPixels(y, true));
507
- drawPoint(clientX, plotY, yBottom, i);
2508
+ plotY = Math.ceil(yAxis.toPixels(y, true));
508
2509
  addKDPoint(clientX, plotY, i);
509
2510
  }
510
2511
  }
511
2512
  wasNull = isNull && !connectNulls;
512
-
513
- if (i % CHUNK_SIZE === 0) {
514
- series.canvasToSVG();
515
- }
516
2513
  }
517
2514
 
518
2515
  return !chartDestroyed;
519
- }, function() {
520
- var loadingDiv = chart.loadingDiv,
521
- loadingShown = chart.loadingShown;
522
- stroke();
523
- series.canvasToSVG();
2516
+ }
524
2517
 
2518
+ function doneProcessing() {
525
2519
  fireEvent(series, 'renderedCanvas');
526
-
527
- // Do not use chart.hideLoading, as it runs JS animation and will be blocked by buildKDTree.
528
- // CSS animation looks good, but then it must be deleted in timeout. If we add the module to core,
529
- // change hideLoading so we can skip this block.
530
- if (loadingShown) {
531
- extend(loadingDiv.style, {
532
- transition: 'opacity 250ms',
533
- opacity: 0
534
- });
535
- chart.loadingShown = false;
536
- destroyLoadingDiv = setTimeout(function() {
537
- if (loadingDiv.parentNode) { // In exporting it is falsy
538
- loadingDiv.parentNode.removeChild(loadingDiv);
539
- }
540
- chart.loadingDiv = chart.loadingSpan = null;
541
- }, 250);
542
- }
543
-
544
2520
  // Pass tests in Pointer.
545
2521
  // Replace this with a single property, and replace when zooming in
546
2522
  // below boostThreshold.
@@ -549,109 +2525,112 @@
549
2525
 
550
2526
  delete series.buildKDTree; // Go back to prototype, ready to build
551
2527
  series.buildKDTree();
2528
+ }
552
2529
 
553
- // Don't do async on export, the exportChart, getSVGForExport and getSVG methods are not chained for it.
554
- }, chart.renderer.forExport ? Number.MAX_VALUE : undefined);
2530
+ // Loop over the points to build the k-d tree
2531
+ eachAsync(
2532
+ isStacked ? series.data : (xData || rawData),
2533
+ processPoint,
2534
+ doneProcessing,
2535
+ chart.renderer.forExport ? Number.MAX_VALUE : undefined
2536
+ );
555
2537
  }
556
2538
  });
557
2539
 
558
- seriesTypes.scatter.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r) {
559
- ctx.moveTo(clientX, plotY);
560
- ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
561
- };
2540
+ /* Used for treemap|heatmap.drawPoints */
2541
+ function pointDrawHandler(proceed) {
2542
+ if (!isSeriesBoosting(this)) {
2543
+ return proceed.call(this);
2544
+ }
562
2545
 
563
- // Rect is twice as fast as arc, should be used for small markers
564
- seriesTypes.scatter.prototype.cvsMarkerSquare = function(ctx, clientX, plotY, r) {
565
- ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
566
- };
567
- seriesTypes.scatter.prototype.fill = true;
2546
+ //Make sure we have a valid OGL context
2547
+ var renderer = createAndAttachRenderer(this.chart, this);
2548
+
2549
+ if (renderer) {
2550
+ allocateIfNotSeriesBoosting(renderer, this);
2551
+ renderer.pushSeries(this);
2552
+ }
2553
+
2554
+ renderIfNotSeriesBoosting(renderer, this);
2555
+ }
2556
+
2557
+ /*
2558
+ * We need to handle heatmaps separatly, since we can't perform the size/color
2559
+ * calculations in the shader easily.
2560
+ *
2561
+ * This likely needs future optimization.
2562
+ *
2563
+ */
2564
+ each(['heatmap', 'treemap'],
2565
+ function(t) {
2566
+ if (seriesTypes[t]) {
2567
+ wrap(seriesTypes[t].prototype, 'drawPoints', pointDrawHandler);
2568
+ seriesTypes[t].prototype.directTouch = false; // Use k-d-tree
2569
+ }
2570
+ }
2571
+ );
568
2572
 
569
2573
  if (seriesTypes.bubble) {
570
- seriesTypes.bubble.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r, i) {
571
- ctx.moveTo(clientX, plotY);
572
- ctx.arc(clientX, plotY, this.radii && this.radii[i], 0, 2 * Math.PI, false);
573
- };
574
- seriesTypes.bubble.prototype.cvsStrokeBatch = 1;
2574
+ // By default, the bubble series does not use the KD-tree, so force it to.
2575
+ delete seriesTypes.bubble.prototype.buildKDTree;
2576
+ seriesTypes.bubble.prototype.directTouch = false;
2577
+
2578
+ // Needed for markers to work correctly
2579
+ wrap(seriesTypes.bubble.prototype, 'markerAttribs', function(proceed) {
2580
+ if (isSeriesBoosting(this)) {
2581
+ return false;
2582
+ }
2583
+ return proceed.apply(this, [].slice.call(arguments, 1));
2584
+ });
575
2585
  }
576
2586
 
2587
+ seriesTypes.scatter.prototype.fill = true;
577
2588
 
578
2589
  extend(seriesTypes.area.prototype, {
579
- cvsDrawPoint: function(ctx, clientX, plotY, yBottom, lastPoint) {
580
- if (lastPoint && clientX !== lastPoint.clientX) {
581
- ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
582
- ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
583
- ctx.lineTo(clientX, plotY);
584
- ctx.lineTo(clientX, yBottom);
585
- }
586
- },
587
2590
  fill: true,
588
2591
  fillOpacity: true,
589
2592
  sampling: true
590
2593
  });
591
2594
 
592
2595
  extend(seriesTypes.column.prototype, {
593
- cvsDrawPoint: function(ctx, clientX, plotY, yBottom) {
594
- ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
595
- },
596
2596
  fill: true,
597
2597
  sampling: true
598
2598
  });
599
2599
 
600
- /**
601
- * Return a full Point object based on the index. The boost module uses stripped point objects
602
- * for performance reasons.
603
- * @param {Number} boostPoint A stripped-down point object
604
- * @returns {Object} A Point object as per http://api.highcharts.com/highcharts#Point
605
- */
606
- Series.prototype.getPoint = function(boostPoint) {
607
- var point = boostPoint,
608
- xData = this.xData || this.options.xData || this.processedXData || false;
609
-
610
- if (boostPoint && !(boostPoint instanceof this.pointClass)) {
611
- point = (new this.pointClass()).init( // eslint-disable-line new-cap
612
- this,
613
- this.options.data[boostPoint.i],
614
- xData ? xData[boostPoint.i] : undefined
615
- );
616
-
617
- point.category = point.x;
618
-
619
- point.dist = boostPoint.dist;
620
- point.distX = boostPoint.distX;
621
- point.plotX = boostPoint.plotX;
622
- point.plotY = boostPoint.plotY;
2600
+ wrap(Series.prototype, 'setVisible', function(proceed, vis) {
2601
+ proceed.call(this, vis, false);
2602
+ if (this.visible === false && this.ogl && this.canvas && this.image) {
2603
+ this.ogl.clear();
2604
+ this.image.attr({
2605
+ href: ''
2606
+ });
2607
+ } else {
2608
+ this.chart.redraw();
623
2609
  }
624
-
625
- return point;
626
- };
2610
+ });
627
2611
 
628
2612
  /**
629
- * Extend series.destroy to also remove the fake k-d-tree points (#5137). Normally
630
- * this is handled by Series.destroy that calls Point.destroy, but the fake
631
- * search points are not registered like that.
2613
+ * Take care of the canvas blitting
632
2614
  */
633
- wrap(Series.prototype, 'destroy', function(proceed) {
634
- var series = this,
635
- chart = series.chart;
636
- if (chart.hoverPoints) {
637
- chart.hoverPoints = grep(chart.hoverPoints, function(point) {
638
- return point.series === series;
639
- });
2615
+ H.Chart.prototype.callbacks.push(function(chart) {
2616
+
2617
+ /* Convert chart-level canvas to image */
2618
+ function canvasToSVG() {
2619
+ if (chart.ogl && isChartSeriesBoosting(chart)) {
2620
+ chart.ogl.render(chart);
2621
+ }
640
2622
  }
641
2623
 
642
- if (chart.hoverPoint && chart.hoverPoint.series === series) {
643
- chart.hoverPoint = null;
2624
+ /* Clear chart-level canvas */
2625
+ function preRender() {
2626
+ if (chart.canvas && chart.ogl && isChartSeriesBoosting(chart)) {
2627
+ // Allocate
2628
+ chart.ogl.allocateBuffer(chart);
2629
+ }
644
2630
  }
645
- proceed.call(this);
646
- });
647
2631
 
648
- /**
649
- * Return a point instance from the k-d-tree
650
- */
651
- wrap(Series.prototype, 'searchPoint', function(proceed) {
652
- return this.getPoint(
653
- proceed.apply(this, [].slice.call(arguments, 1))
654
- );
2632
+ addEvent(chart, 'predraw', preRender);
2633
+ addEvent(chart, 'render', canvasToSVG);
655
2634
  });
656
2635
 
657
2636
  }(Highcharts));