| 776 |
lars |
1 |
/**
|
|
|
2 |
* This is an experimental Highcharts module that draws long data series on a canvas
|
|
|
3 |
* in order to increase performance of the initial load time and tooltip responsiveness.
|
|
|
4 |
*
|
|
|
5 |
* Compatible with HTML5 canvas compatible browsers (not IE < 9).
|
|
|
6 |
*
|
|
|
7 |
* Author: Torstein Honsi
|
|
|
8 |
*
|
|
|
9 |
*
|
|
|
10 |
* Development plan
|
|
|
11 |
* - Column range.
|
|
|
12 |
* - Heatmap.
|
|
|
13 |
* - Treemap.
|
|
|
14 |
* - Check how it works with Highstock and data grouping.
|
|
|
15 |
* - Check inverted charts.
|
|
|
16 |
* - Check reversed axes.
|
|
|
17 |
* - Chart callback should be async after last series is drawn. (But not necessarily, we don't do
|
|
|
18 |
that with initial series animation).
|
|
|
19 |
* - Cache full-size image so we don't have to redraw on hide/show and zoom up. But k-d-tree still
|
|
|
20 |
* needs to be built.
|
|
|
21 |
* - Test IE9 and IE10.
|
|
|
22 |
* - Stacking is not perhaps not correct since it doesn't use the translation given in
|
|
|
23 |
* the translate method. If this gets to complicated, a possible way out would be to
|
|
|
24 |
* have a simplified renderCanvas method that simply draws the areaPath on a canvas.
|
|
|
25 |
*
|
|
|
26 |
* If this module is taken in as part of the core
|
|
|
27 |
* - All the loading logic should be merged with core. Update styles in the core.
|
|
|
28 |
* - Most of the method wraps should probably be added directly in parent methods.
|
|
|
29 |
*
|
|
|
30 |
* Notes for boost mode
|
|
|
31 |
* - Area lines are not drawn
|
|
|
32 |
* - Point markers are not drawn
|
|
|
33 |
* - Zones and negativeColor don't work
|
|
|
34 |
* - Columns are always one pixel wide. Don't set the threshold too low.
|
|
|
35 |
*
|
|
|
36 |
* Optimizing tips for users
|
|
|
37 |
* - For scatter plots, use a marker.radius of 1 or less. It results in a rectangle being drawn, which is
|
|
|
38 |
* considerably faster than a circle.
|
|
|
39 |
* - Set extremes (min, max) explicitly on the axes in order for Highcharts to avoid computing extremes.
|
|
|
40 |
* - Set enableMouseTracking to false on the series to improve total rendering time.
|
|
|
41 |
* - The default threshold is set based on one series. If you have multiple, dense series, the combined
|
|
|
42 |
* number of points drawn gets higher, and you may want to set the threshold lower in order to
|
|
|
43 |
* use optimizations.
|
|
|
44 |
*/
|
|
|
45 |
/*global document, Highcharts, HighchartsAdapter, setTimeout */
|
|
|
46 |
(function (H, HA) {
|
|
|
47 |
|
|
|
48 |
'use strict';
|
|
|
49 |
|
|
|
50 |
var noop = function () { return undefined; },
|
|
|
51 |
Color = H.Color,
|
|
|
52 |
Series = H.Series,
|
|
|
53 |
seriesTypes = H.seriesTypes,
|
|
|
54 |
each = H.each,
|
|
|
55 |
extend = H.extend,
|
|
|
56 |
addEvent = HA.addEvent,
|
|
|
57 |
fireEvent = HA.fireEvent,
|
|
|
58 |
merge = H.merge,
|
|
|
59 |
pick = H.pick,
|
|
|
60 |
wrap = H.wrap,
|
|
|
61 |
plotOptions = H.getOptions().plotOptions,
|
|
|
62 |
CHUNK_SIZE = 50000;
|
|
|
63 |
|
|
|
64 |
function eachAsync(arr, fn, callback, chunkSize, i) {
|
|
|
65 |
i = i || 0;
|
|
|
66 |
chunkSize = chunkSize || CHUNK_SIZE;
|
|
|
67 |
each(arr.slice(i, i + chunkSize), fn);
|
|
|
68 |
if (i + chunkSize < arr.length) {
|
|
|
69 |
setTimeout(function () {
|
|
|
70 |
eachAsync(arr, fn, callback, chunkSize, i + chunkSize);
|
|
|
71 |
});
|
|
|
72 |
} else if (callback) {
|
|
|
73 |
callback();
|
|
|
74 |
}
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
// Set default options
|
|
|
78 |
each(['area', 'arearange', 'column', 'line', 'scatter'], function (type) {
|
|
|
79 |
if (plotOptions[type]) {
|
|
|
80 |
plotOptions[type].boostThreshold = 5000;
|
|
|
81 |
}
|
|
|
82 |
});
|
|
|
83 |
|
|
|
84 |
/**
|
|
|
85 |
* Override a bunch of methods the same way. If the number of points is below the threshold,
|
|
|
86 |
* run the original method. If not, check for a canvas version or do nothing.
|
|
|
87 |
*/
|
|
|
88 |
each(['translate', 'generatePoints', 'drawTracker', 'drawPoints', 'render'], function (method) {
|
|
|
89 |
function branch(proceed) {
|
|
|
90 |
var letItPass = this.options.stacking && (method === 'translate' || method === 'generatePoints');
|
|
|
91 |
if ((this.processedXData || this.options.data).length < (this.options.boostThreshold || Number.MAX_VALUE) ||
|
|
|
92 |
letItPass) {
|
|
|
93 |
|
|
|
94 |
// Clear image
|
|
|
95 |
if (method === 'render' && this.image) {
|
|
|
96 |
this.image.attr({ href: '' });
|
|
|
97 |
this.animate = null; // We're zooming in, don't run animation
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
proceed.call(this);
|
|
|
101 |
|
|
|
102 |
// If a canvas version of the method exists, like renderCanvas(), run
|
|
|
103 |
} else if (this[method + 'Canvas']) {
|
|
|
104 |
|
|
|
105 |
this[method + 'Canvas']();
|
|
|
106 |
}
|
|
|
107 |
}
|
|
|
108 |
wrap(Series.prototype, method, branch);
|
|
|
109 |
|
|
|
110 |
// A special case for some types - its translate method is already wrapped
|
|
|
111 |
if (method === 'translate') {
|
|
|
112 |
if (seriesTypes.column) {
|
|
|
113 |
wrap(seriesTypes.column.prototype, method, branch);
|
|
|
114 |
}
|
|
|
115 |
if (seriesTypes.arearange) {
|
|
|
116 |
wrap(seriesTypes.arearange.prototype, method, branch);
|
|
|
117 |
}
|
|
|
118 |
}
|
|
|
119 |
});
|
|
|
120 |
|
|
|
121 |
/**
|
|
|
122 |
* Do not compute extremes when min and max are set.
|
|
|
123 |
* If we use this in the core, we can add the hook to hasExtremes to the methods directly.
|
|
|
124 |
*/
|
|
|
125 |
wrap(Series.prototype, 'getExtremes', function (proceed) {
|
|
|
126 |
if (!this.hasExtremes()) {
|
|
|
127 |
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
|
128 |
}
|
|
|
129 |
});
|
|
|
130 |
wrap(Series.prototype, 'setData', function (proceed) {
|
|
|
131 |
if (!this.hasExtremes(true)) {
|
|
|
132 |
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
|
133 |
}
|
|
|
134 |
});
|
|
|
135 |
wrap(Series.prototype, 'processData', function (proceed) {
|
|
|
136 |
if (!this.hasExtremes(true)) {
|
|
|
137 |
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
|
138 |
}
|
|
|
139 |
});
|
|
|
140 |
|
|
|
141 |
|
|
|
142 |
H.extend(Series.prototype, {
|
|
|
143 |
pointRange: 0,
|
|
|
144 |
|
|
|
145 |
hasExtremes: function (checkX) {
|
|
|
146 |
var options = this.options,
|
|
|
147 |
data = options.data,
|
|
|
148 |
xAxis = this.xAxis && this.xAxis.options,
|
|
|
149 |
yAxis = this.yAxis && this.yAxis.options;
|
|
|
150 |
return data.length > (options.boostThreshold || Number.MAX_VALUE) && typeof yAxis.min === 'number' && typeof yAxis.max === 'number' &&
|
|
|
151 |
(!checkX || (typeof xAxis.min === 'number' && typeof xAxis.max === 'number'));
|
|
|
152 |
},
|
|
|
153 |
|
|
|
154 |
/**
|
|
|
155 |
* If implemented in the core, parts of this can probably be shared with other similar
|
|
|
156 |
* methods in Highcharts.
|
|
|
157 |
*/
|
|
|
158 |
destroyGraphics: function () {
|
|
|
159 |
var series = this,
|
|
|
160 |
points = this.points,
|
|
|
161 |
point,
|
|
|
162 |
i;
|
|
|
163 |
|
|
|
164 |
for (i = 0; i < points.length; i = i + 1) {
|
|
|
165 |
point = points[i];
|
|
|
166 |
if (point && point.graphic) {
|
|
|
167 |
point.graphic = point.graphic.destroy();
|
|
|
168 |
}
|
|
|
169 |
}
|
|
|
170 |
|
|
|
171 |
each(['graph', 'area'], function (prop) {
|
|
|
172 |
if (series[prop]) {
|
|
|
173 |
series[prop] = series[prop].destroy();
|
|
|
174 |
}
|
|
|
175 |
});
|
|
|
176 |
},
|
|
|
177 |
|
|
|
178 |
/**
|
|
|
179 |
* Create a hidden canvas to draw the graph on. The contents is later copied over
|
|
|
180 |
* to an SVG image element.
|
|
|
181 |
*/
|
|
|
182 |
getContext: function () {
|
|
|
183 |
var width = this.chart.plotWidth,
|
|
|
184 |
height = this.chart.plotHeight;
|
|
|
185 |
|
|
|
186 |
if (!this.canvas) {
|
|
|
187 |
this.canvas = document.createElement('canvas');
|
|
|
188 |
this.image = this.chart.renderer.image('', 0, 0, width, height).add(this.group);
|
|
|
189 |
this.ctx = this.canvas.getContext('2d');
|
|
|
190 |
} else {
|
|
|
191 |
this.ctx.clearRect(0, 0, width, height);
|
|
|
192 |
}
|
|
|
193 |
|
|
|
194 |
this.canvas.setAttribute('width', width);
|
|
|
195 |
this.canvas.setAttribute('height', height);
|
|
|
196 |
this.image.attr({
|
|
|
197 |
width: width,
|
|
|
198 |
height: height
|
|
|
199 |
});
|
|
|
200 |
|
|
|
201 |
return this.ctx;
|
|
|
202 |
},
|
|
|
203 |
|
|
|
204 |
/**
|
|
|
205 |
* Draw the canvas image inside an SVG image
|
|
|
206 |
*/
|
|
|
207 |
canvasToSVG: function () {
|
|
|
208 |
this.image.attr({ href: this.canvas.toDataURL('image/png') });
|
|
|
209 |
},
|
|
|
210 |
|
|
|
211 |
cvsLineTo: function (ctx, clientX, plotY) {
|
|
|
212 |
ctx.lineTo(clientX, plotY);
|
|
|
213 |
},
|
|
|
214 |
|
|
|
215 |
renderCanvas: function () {
|
|
|
216 |
var series = this,
|
|
|
217 |
options = series.options,
|
|
|
218 |
chart = series.chart,
|
|
|
219 |
xAxis = this.xAxis,
|
|
|
220 |
yAxis = this.yAxis,
|
|
|
221 |
ctx,
|
|
|
222 |
i,
|
|
|
223 |
c = 0,
|
|
|
224 |
xData = series.processedXData,
|
|
|
225 |
yData = series.processedYData,
|
|
|
226 |
rawData = options.data,
|
|
|
227 |
xExtremes = xAxis.getExtremes(),
|
|
|
228 |
xMin = xExtremes.min,
|
|
|
229 |
xMax = xExtremes.max,
|
|
|
230 |
yExtremes = yAxis.getExtremes(),
|
|
|
231 |
yMin = yExtremes.min,
|
|
|
232 |
yMax = yExtremes.max,
|
|
|
233 |
pointTaken = {},
|
|
|
234 |
lastClientX,
|
|
|
235 |
sampling = !!series.sampling,
|
|
|
236 |
points,
|
|
|
237 |
r = options.marker && options.marker.radius,
|
|
|
238 |
cvsDrawPoint = this.cvsDrawPoint,
|
|
|
239 |
cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
|
|
|
240 |
cvsMarker = r <= 1 ? this.cvsMarkerSquare : this.cvsMarkerCircle,
|
|
|
241 |
enableMouseTracking = options.enableMouseTracking !== false,
|
|
|
242 |
lastPoint,
|
|
|
243 |
threshold = options.threshold,
|
|
|
244 |
yBottom = yAxis.getThreshold(threshold),
|
|
|
245 |
hasThreshold = typeof threshold === 'number',
|
|
|
246 |
translatedThreshold = yBottom,
|
|
|
247 |
doFill = this.fill,
|
|
|
248 |
isRange = series.pointArrayMap && series.pointArrayMap.join(',') === 'low,high',
|
|
|
249 |
isStacked = !!options.stacking,
|
|
|
250 |
cropStart = series.cropStart || 0,
|
|
|
251 |
loadingOptions = chart.options.loading,
|
|
|
252 |
requireSorting = series.requireSorting,
|
|
|
253 |
wasNull,
|
|
|
254 |
connectNulls = options.connectNulls,
|
|
|
255 |
useRaw = !xData,
|
|
|
256 |
minVal,
|
|
|
257 |
maxVal,
|
|
|
258 |
minI,
|
|
|
259 |
maxI,
|
|
|
260 |
fillColor = series.fillOpacity ?
|
|
|
261 |
new Color(series.color).setOpacity(pick(options.fillOpacity, 0.75)).get() :
|
|
|
262 |
series.color,
|
|
|
263 |
stroke = function () {
|
|
|
264 |
if (doFill) {
|
|
|
265 |
ctx.fillStyle = fillColor;
|
|
|
266 |
ctx.fill();
|
|
|
267 |
} else {
|
|
|
268 |
ctx.strokeStyle = series.color;
|
|
|
269 |
ctx.lineWidth = options.lineWidth;
|
|
|
270 |
ctx.stroke();
|
|
|
271 |
}
|
|
|
272 |
},
|
|
|
273 |
drawPoint = function (clientX, plotY, yBottom) {
|
|
|
274 |
if (c === 0) {
|
|
|
275 |
ctx.beginPath();
|
|
|
276 |
}
|
|
|
277 |
|
|
|
278 |
if (wasNull) {
|
|
|
279 |
ctx.moveTo(clientX, plotY);
|
|
|
280 |
} else {
|
|
|
281 |
if (cvsDrawPoint) {
|
|
|
282 |
cvsDrawPoint(ctx, clientX, plotY, yBottom, lastPoint);
|
|
|
283 |
} else if (cvsLineTo) {
|
|
|
284 |
cvsLineTo(ctx, clientX, plotY);
|
|
|
285 |
} else if (cvsMarker) {
|
|
|
286 |
cvsMarker(ctx, clientX, plotY, r);
|
|
|
287 |
}
|
|
|
288 |
}
|
|
|
289 |
|
|
|
290 |
// We need to stroke the line for every 1000 pixels. It will crash the browser
|
|
|
291 |
// memory use if we stroke too infrequently.
|
|
|
292 |
c = c + 1;
|
|
|
293 |
if (c === 1000) {
|
|
|
294 |
stroke();
|
|
|
295 |
c = 0;
|
|
|
296 |
}
|
|
|
297 |
|
|
|
298 |
// Area charts need to keep track of the last point
|
|
|
299 |
lastPoint = {
|
|
|
300 |
clientX: clientX,
|
|
|
301 |
plotY: plotY,
|
|
|
302 |
yBottom: yBottom
|
|
|
303 |
};
|
|
|
304 |
},
|
|
|
305 |
|
|
|
306 |
addKDPoint = function (clientX, plotY, i) {
|
|
|
307 |
|
|
|
308 |
// The k-d tree requires series points. Reduce the amount of points, since the time to build the
|
|
|
309 |
// tree increases exponentially.
|
|
|
310 |
if (enableMouseTracking && !pointTaken[clientX + ',' + plotY]) {
|
|
|
311 |
points.push({
|
|
|
312 |
clientX: clientX,
|
|
|
313 |
plotX: clientX,
|
|
|
314 |
plotY: plotY,
|
|
|
315 |
i: cropStart + i
|
|
|
316 |
});
|
|
|
317 |
pointTaken[clientX + ',' + plotY] = true;
|
|
|
318 |
}
|
|
|
319 |
};
|
|
|
320 |
|
|
|
321 |
// If we are zooming out from SVG mode, destroy the graphics
|
|
|
322 |
if (this.points) {
|
|
|
323 |
this.destroyGraphics();
|
|
|
324 |
}
|
|
|
325 |
|
|
|
326 |
// The group
|
|
|
327 |
series.plotGroup(
|
|
|
328 |
'group',
|
|
|
329 |
'series',
|
|
|
330 |
series.visible ? 'visible' : 'hidden',
|
|
|
331 |
options.zIndex,
|
|
|
332 |
chart.seriesGroup
|
|
|
333 |
);
|
|
|
334 |
|
|
|
335 |
series.getAttribs();
|
|
|
336 |
series.markerGroup = series.group;
|
|
|
337 |
addEvent(series, 'destroy', function () {
|
|
|
338 |
series.markerGroup = null;
|
|
|
339 |
});
|
|
|
340 |
|
|
|
341 |
points = this.points = [];
|
|
|
342 |
ctx = this.getContext();
|
|
|
343 |
series.buildKDTree = noop; // Do not start building while drawing
|
|
|
344 |
|
|
|
345 |
// Display a loading indicator
|
|
|
346 |
if (rawData.length > 99999) {
|
|
|
347 |
chart.options.loading = merge(loadingOptions, {
|
|
|
348 |
labelStyle: {
|
|
|
349 |
backgroundColor: 'rgba(255,255,255,0.75)',
|
|
|
350 |
padding: '1em',
|
|
|
351 |
borderRadius: '0.5em'
|
|
|
352 |
},
|
|
|
353 |
style: {
|
|
|
354 |
backgroundColor: 'none',
|
|
|
355 |
opacity: 1
|
|
|
356 |
}
|
|
|
357 |
});
|
|
|
358 |
chart.showLoading('Drawing...');
|
|
|
359 |
chart.options.loading = loadingOptions; // reset
|
|
|
360 |
if (chart.loadingShown === true) {
|
|
|
361 |
chart.loadingShown = 1;
|
|
|
362 |
} else {
|
|
|
363 |
chart.loadingShown = chart.loadingShown + 1;
|
|
|
364 |
}
|
|
|
365 |
}
|
|
|
366 |
|
|
|
367 |
// Loop over the points
|
|
|
368 |
i = 0;
|
|
|
369 |
eachAsync(isStacked ? series.data : (xData || rawData), function (d) {
|
|
|
370 |
|
|
|
371 |
var x,
|
|
|
372 |
y,
|
|
|
373 |
clientX,
|
|
|
374 |
plotY,
|
|
|
375 |
isNull,
|
|
|
376 |
low,
|
|
|
377 |
isYInside = true;
|
|
|
378 |
|
|
|
379 |
if (useRaw) {
|
|
|
380 |
x = d[0];
|
|
|
381 |
y = d[1];
|
|
|
382 |
} else {
|
|
|
383 |
x = d;
|
|
|
384 |
y = yData[i];
|
|
|
385 |
}
|
|
|
386 |
|
|
|
387 |
// Resolve low and high for range series
|
|
|
388 |
if (isRange) {
|
|
|
389 |
if (useRaw) {
|
|
|
390 |
y = d.slice(1, 3);
|
|
|
391 |
}
|
|
|
392 |
low = y[0];
|
|
|
393 |
y = y[1];
|
|
|
394 |
} else if (isStacked) {
|
|
|
395 |
x = d.x;
|
|
|
396 |
y = d.stackY;
|
|
|
397 |
low = y - d.y;
|
|
|
398 |
}
|
|
|
399 |
|
|
|
400 |
isNull = y === null;
|
|
|
401 |
|
|
|
402 |
// Optimize for scatter zooming
|
|
|
403 |
if (!requireSorting) {
|
|
|
404 |
isYInside = y >= yMin && y <= yMax;
|
|
|
405 |
}
|
|
|
406 |
|
|
|
407 |
if (!isNull && x >= xMin && x <= xMax && isYInside) {
|
|
|
408 |
|
|
|
409 |
clientX = Math.round(xAxis.toPixels(x, true));
|
|
|
410 |
|
|
|
411 |
if (sampling) {
|
|
|
412 |
if (minI === undefined || clientX === lastClientX) {
|
|
|
413 |
if (!isRange) {
|
|
|
414 |
low = y;
|
|
|
415 |
}
|
|
|
416 |
if (maxI === undefined || y > maxVal) {
|
|
|
417 |
maxVal = y;
|
|
|
418 |
maxI = i;
|
|
|
419 |
}
|
|
|
420 |
if (minI === undefined || low < minVal) {
|
|
|
421 |
minVal = low;
|
|
|
422 |
minI = i;
|
|
|
423 |
}
|
|
|
424 |
|
|
|
425 |
}
|
|
|
426 |
if (clientX !== lastClientX) { // Add points and reset
|
|
|
427 |
if (minI !== undefined) { // then maxI is also a number
|
|
|
428 |
plotY = yAxis.toPixels(maxVal, true);
|
|
|
429 |
yBottom = yAxis.toPixels(minVal, true);
|
|
|
430 |
drawPoint(
|
|
|
431 |
clientX,
|
|
|
432 |
hasThreshold ? Math.min(plotY, translatedThreshold) : plotY,
|
|
|
433 |
hasThreshold ? Math.max(yBottom, translatedThreshold) : yBottom
|
|
|
434 |
);
|
|
|
435 |
addKDPoint(clientX, plotY, maxI);
|
|
|
436 |
if (yBottom !== plotY) {
|
|
|
437 |
addKDPoint(clientX, yBottom, minI);
|
|
|
438 |
}
|
|
|
439 |
}
|
|
|
440 |
|
|
|
441 |
|
|
|
442 |
minI = maxI = undefined;
|
|
|
443 |
lastClientX = clientX;
|
|
|
444 |
}
|
|
|
445 |
} else {
|
|
|
446 |
plotY = Math.round(yAxis.toPixels(y, true));
|
|
|
447 |
drawPoint(clientX, plotY, yBottom);
|
|
|
448 |
addKDPoint(clientX, plotY, i);
|
|
|
449 |
}
|
|
|
450 |
}
|
|
|
451 |
wasNull = isNull && !connectNulls;
|
|
|
452 |
|
|
|
453 |
i = i + 1;
|
|
|
454 |
|
|
|
455 |
if (i % CHUNK_SIZE === 0) {
|
|
|
456 |
series.canvasToSVG();
|
|
|
457 |
}
|
|
|
458 |
|
|
|
459 |
}, function () {
|
|
|
460 |
|
|
|
461 |
var loadingDiv = chart.loadingDiv,
|
|
|
462 |
loadingShown = +chart.loadingShown;
|
|
|
463 |
|
|
|
464 |
stroke();
|
|
|
465 |
series.canvasToSVG();
|
|
|
466 |
|
|
|
467 |
fireEvent(series, 'renderedCanvas');
|
|
|
468 |
|
|
|
469 |
// Do not use chart.hideLoading, as it runs JS animation and will be blocked by buildKDTree.
|
|
|
470 |
// CSS animation looks good, but then it must be deleted in timeout. If we add the module to core,
|
|
|
471 |
// change hideLoading so we can skip this block.
|
|
|
472 |
if (loadingShown === 1) {
|
|
|
473 |
extend(loadingDiv.style, {
|
|
|
474 |
transition: 'opacity 250ms',
|
|
|
475 |
opacity: 0
|
|
|
476 |
});
|
|
|
477 |
|
|
|
478 |
chart.loadingShown = false;
|
|
|
479 |
setTimeout(function () {
|
|
|
480 |
if (loadingDiv.parentNode) { // In exporting it is falsy
|
|
|
481 |
loadingDiv.parentNode.removeChild(loadingDiv);
|
|
|
482 |
}
|
|
|
483 |
chart.loadingDiv = chart.loadingSpan = null;
|
|
|
484 |
}, 250);
|
|
|
485 |
}
|
|
|
486 |
if (loadingShown) {
|
|
|
487 |
chart.loadingShown = loadingShown - 1;
|
|
|
488 |
}
|
|
|
489 |
|
|
|
490 |
// Pass tests in Pointer.
|
|
|
491 |
// TODO: Replace this with a single property, and replace when zooming in
|
|
|
492 |
// below boostThreshold.
|
|
|
493 |
series.directTouch = false;
|
|
|
494 |
series.options.stickyTracking = true;
|
|
|
495 |
|
|
|
496 |
delete series.buildKDTree; // Go back to prototype, ready to build
|
|
|
497 |
series.buildKDTree();
|
|
|
498 |
|
|
|
499 |
// Don't do async on export, the exportChart, getSVGForExport and getSVG methods are not chained for it.
|
|
|
500 |
}, chart.renderer.forExport ? Number.MAX_VALUE : undefined);
|
|
|
501 |
}
|
|
|
502 |
});
|
|
|
503 |
|
|
|
504 |
seriesTypes.scatter.prototype.cvsMarkerCircle = function (ctx, clientX, plotY, r) {
|
|
|
505 |
ctx.moveTo(clientX, plotY);
|
|
|
506 |
ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
|
|
|
507 |
};
|
|
|
508 |
|
|
|
509 |
// Rect is twice as fast as arc, should be used for small markers
|
|
|
510 |
seriesTypes.scatter.prototype.cvsMarkerSquare = function (ctx, clientX, plotY, r) {
|
|
|
511 |
ctx.moveTo(clientX, plotY);
|
|
|
512 |
ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
|
|
|
513 |
};
|
|
|
514 |
seriesTypes.scatter.prototype.fill = true;
|
|
|
515 |
|
|
|
516 |
extend(seriesTypes.area.prototype, {
|
|
|
517 |
cvsDrawPoint: function (ctx, clientX, plotY, yBottom, lastPoint) {
|
|
|
518 |
if (lastPoint && clientX !== lastPoint.clientX) {
|
|
|
519 |
ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
|
|
|
520 |
ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
|
|
|
521 |
ctx.lineTo(clientX, plotY);
|
|
|
522 |
ctx.lineTo(clientX, yBottom);
|
|
|
523 |
}
|
|
|
524 |
},
|
|
|
525 |
fill: true,
|
|
|
526 |
fillOpacity: true,
|
|
|
527 |
sampling: true
|
|
|
528 |
});
|
|
|
529 |
|
|
|
530 |
extend(seriesTypes.column.prototype, {
|
|
|
531 |
cvsDrawPoint: function (ctx, clientX, plotY, yBottom) {
|
|
|
532 |
ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
|
|
|
533 |
},
|
|
|
534 |
fill: true,
|
|
|
535 |
sampling: true
|
|
|
536 |
});
|
|
|
537 |
|
|
|
538 |
/**
|
|
|
539 |
* Return a point instance from the k-d-tree
|
|
|
540 |
*/
|
|
|
541 |
wrap(Series.prototype, 'searchPoint', function (proceed) {
|
|
|
542 |
var point = proceed.apply(this, [].slice.call(arguments, 1)),
|
|
|
543 |
ret = point;
|
|
|
544 |
|
|
|
545 |
if (point && !(point instanceof this.pointClass)) {
|
|
|
546 |
ret = (new this.pointClass()).init(this, this.options.data[point.i]);
|
|
|
547 |
ret.dist = point.dist;
|
|
|
548 |
ret.category = ret.x;
|
|
|
549 |
ret.plotX = point.plotX;
|
|
|
550 |
ret.plotY = point.plotY;
|
|
|
551 |
}
|
|
|
552 |
return ret;
|
|
|
553 |
});
|
|
|
554 |
}(Highcharts, HighchartsAdapter));
|