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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +41 -0
- data/README.markdown +3 -6
- data/app/assets/javascripts/highcharts.js +758 -455
- data/app/assets/javascripts/highcharts/highcharts-3d.js +58 -50
- data/app/assets/javascripts/highcharts/highcharts-more.js +21 -35
- data/app/assets/javascripts/highcharts/modules/accessibility.js +69 -39
- data/app/assets/javascripts/highcharts/modules/annotations.js +2 -2
- data/app/assets/javascripts/highcharts/modules/boost.js +2316 -337
- data/app/assets/javascripts/highcharts/modules/broken-axis.js +17 -3
- data/app/assets/javascripts/highcharts/modules/data.js +2 -2
- data/app/assets/javascripts/highcharts/modules/drilldown.js +6 -6
- data/app/assets/javascripts/highcharts/modules/exporting.js +4 -4
- data/app/assets/javascripts/highcharts/modules/funnel.js +4 -4
- data/app/assets/javascripts/highcharts/modules/grid-axis.js +6 -6
- data/app/assets/javascripts/highcharts/modules/heatmap.js +10 -7
- data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +2 -2
- data/app/assets/javascripts/highcharts/modules/offline-exporting.js +3 -3
- data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +3 -3
- data/app/assets/javascripts/highcharts/modules/series-label.js +2 -2
- data/app/assets/javascripts/highcharts/modules/solid-gauge.js +40 -4
- data/app/assets/javascripts/highcharts/modules/stock.js +100 -81
- data/app/assets/javascripts/highcharts/modules/treemap.js +36 -9
- data/app/assets/javascripts/highcharts/modules/xrange-series.js +2 -2
- data/lib/highcharts/version.rb +1 -1
- metadata +2 -2
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license Highcharts JS v5.0.
|
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
|
-
|
76
|
-
|
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
|
-
|
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
|
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
|
-
|
190
|
-
|
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 + ', ' +
|
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
|
-
|
351
|
+
// Show the tooltip
|
352
|
+
if (chart.tooltip) {
|
353
|
+
chart.tooltip.refresh(chart.tooltip.shared ? [this] : this);
|
354
|
+
}
|
345
355
|
} else {
|
346
|
-
chart.tooltip
|
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 (
|
374
|
-
for (var i = 0; i <
|
375
|
-
if (
|
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
|
-
//
|
399
|
+
// Grab next/prev point & series
|
383
400
|
newSeries = series[curPoint.series.index + (next ? 1 : -1)];
|
384
|
-
newPoint =
|
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 (
|
873
|
-
chart.
|
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('
|
916
|
+
tableShortcut = doc.createElement('h4'),
|
892
917
|
tableShortcutAnchor = doc.createElement('a'),
|
893
|
-
chartHeading = doc.createElement('
|
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
|
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
|
937
|
+
'<div>Use regions/landmarks to skip ahead to chart' +
|
912
938
|
(series.length > 1 ? ' and navigate between data series' : '') +
|
913
|
-
'.</div><h3>
|
939
|
+
'.</div><h3>' + (options.title.text ? htmlencode(options.title.text) : 'Chart') +
|
914
940
|
(options.subtitle && options.subtitle.text ? '. ' + htmlencode(options.subtitle.text) : '') +
|
915
|
-
'</
|
916
|
-
'</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.
|
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.
|
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.
|
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
|
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
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
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
|
2028
|
+
++i;
|
99
2029
|
}
|
100
2030
|
if (proceed) {
|
101
2031
|
if (i < arr.length) {
|
102
|
-
|
103
|
-
|
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
|
-
|
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
|
123
|
-
* run the original method. If not, check for a
|
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([
|
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 &&
|
128
|
-
|
129
|
-
|
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 -
|
2192
|
+
// A special case for some types - their translate method is already wrapped
|
150
2193
|
if (method === 'translate') {
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
-
*
|
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
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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 &&
|
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(
|
316
|
-
|
317
|
-
|
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.
|
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[
|
369
|
-
pointTaken[
|
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
|
-
//
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
if (
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
-
|
427
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
}
|
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
|
-
|
554
|
-
|
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
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
2540
|
+
/* Used for treemap|heatmap.drawPoints */
|
2541
|
+
function pointDrawHandler(proceed) {
|
2542
|
+
if (!isSeriesBoosting(this)) {
|
2543
|
+
return proceed.call(this);
|
2544
|
+
}
|
562
2545
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
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
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
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
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
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
|
-
*
|
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
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
chart.
|
638
|
-
|
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
|
-
|
643
|
-
|
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
|
-
|
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));
|