corrade-nucleus-nucleons – Blame information for rev 20

Subversion Repositories:
Rev:
Rev Author Line No. Line
20 office 1 /**
2 * @license Highcharts JS v5.0.12 (2017-05-24)
3 * Boost module
4 *
5 * (c) 2010-2017 Highsoft AS
6 * Author: Torstein Honsi
7 *
8 * License: www.highcharts.com/license
9 */
10 'use strict';
11 (function(factory) {
12 if (typeof module === 'object' && module.exports) {
13 module.exports = factory;
14 } else {
15 factory(Highcharts);
16 }
17 }(function(Highcharts) {
18 (function(H) {
19 /**
20 * License: www.highcharts.com/license
21 * Author: Torstein Honsi, Christer Vasseng
22 *
23 * This is an experimental Highcharts module that draws long data series on a canvas
24 * in order to increase performance of the initial load time and tooltip responsiveness.
25 *
26 * Compatible with HTML5 canvas compatible browsers (not IE < 9).
27 *
28 *
29 *
30 * Development plan
31 * - Column range.
32 * - Heatmap. Modify the heatmap-canvas demo so that it uses this module.
33 * - Treemap.
34 * - Check how it works with Highstock and data grouping. Currently it only works when navigator.adaptToUpdatedData
35 * is false. It is also recommended to set scrollbar.liveRedraw to false.
36 * - Check inverted charts.
37 * - Check reversed axes.
38 * - Chart callback should be async after last series is drawn. (But not necessarily, we don't do
39 that with initial series animation).
40 * - Cache full-size image so we don't have to redraw on hide/show and zoom up. But k-d-tree still
41 * needs to be built.
42 * - Test IE9 and IE10.
43 * - Stacking is not perhaps not correct since it doesn't use the translation given in
44 * the translate method. If this gets to complicated, a possible way out would be to
45 * have a simplified renderCanvas method that simply draws the areaPath on a canvas.
46 *
47 * If this module is taken in as part of the core
48 * - All the loading logic should be merged with core. Update styles in the core.
49 * - Most of the method wraps should probably be added directly in parent methods.
50 *
51 * Notes for boost mode
52 * - Area lines are not drawn
53 * - Point markers are not drawn on line-type series
54 * - Lines are not drawn on scatter charts
55 * - Zones and negativeColor don't work
56 * - Initial point colors aren't rendered
57 * - Columns are always one pixel wide. Don't set the threshold too low.
58 *
59 * Optimizing tips for users
60 * - For scatter plots, use a marker.radius of 1 or less. It results in a rectangle being drawn, which is
61 * considerably faster than a circle.
62 * - Set extremes (min, max) explicitly on the axes in order for Highcharts to avoid computing extremes.
63 * - Set enableMouseTracking to false on the series to improve total rendering time.
64 * - The default threshold is set based on one series. If you have multiple, dense series, the combined
65 * number of points drawn gets higher, and you may want to set the threshold lower in order to
66 * use optimizations.
67 */
68  
69  
70 var win = H.win,
71 doc = win.document,
72 noop = function() {},
73 Color = H.Color,
74 Series = H.Series,
75 seriesTypes = H.seriesTypes,
76 each = H.each,
77 extend = H.extend,
78 addEvent = H.addEvent,
79 fireEvent = H.fireEvent,
80 isNumber = H.isNumber,
81 merge = H.merge,
82 pick = H.pick,
83 wrap = H.wrap,
84 CHUNK_SIZE = 50000,
85 destroyLoadingDiv;
86  
87 function eachAsync(arr, fn, finalFunc, chunkSize, i) {
88 i = i || 0;
89 chunkSize = chunkSize || CHUNK_SIZE;
90  
91 var threshold = i + chunkSize,
92 proceed = true;
93  
94 while (proceed && i < threshold && i < arr.length) {
95 proceed = fn(arr[i], i);
96 i = i + 1;
97 }
98 if (proceed) {
99 if (i < arr.length) {
100 setTimeout(function() {
101 eachAsync(arr, fn, finalFunc, chunkSize, i);
102 });
103 } else if (finalFunc) {
104 finalFunc();
105 }
106 }
107 }
108  
109 /*
110 * Returns true if the chart is in series boost mode
111 * @param chart {Highchart.Chart} - the chart to check
112 * @returns {Boolean} - true if the chart is in series boost mode
113 */
114 function isChartSeriesBoosting(chart) {
115 var threshold = (chart.options.boost ? chart.options.boost.seriesThreshold : 0) ||
116 chart.options.chart.seriesBoostThreshold ||
117 10;
118  
119 return chart.series.length >= threshold;
120 }
121  
122 H.initCanvasBoost = function() {
123  
124 if (H.seriesTypes.heatmap) {
125 H.wrap(H.seriesTypes.heatmap.prototype, 'drawPoints', function() {
126 var ctx = this.getContext();
127 if (ctx) {
128  
129 // draw the columns
130 each(this.points, function(point) {
131 var plotY = point.plotY,
132 shapeArgs,
133 pointAttr;
134  
135 if (plotY !== undefined && !isNaN(plotY) && point.y !== null) {
136 shapeArgs = point.shapeArgs;
137  
138  
139 pointAttr = point.series.pointAttribs(point);
140  
141  
142 ctx.fillStyle = pointAttr.fill;
143 ctx.fillRect(shapeArgs.x, shapeArgs.y, shapeArgs.width, shapeArgs.height);
144 }
145 });
146  
147 this.canvasToSVG();
148  
149 } else {
150 this.chart.showLoading('Your browser doesn\'t support HTML5 canvas, <br>please use a modern browser');
151  
152 // Uncomment this to provide low-level (slow) support in oldIE. It will cause script errors on
153 // charts with more than a few thousand points.
154 // arguments[0].call(this);
155 }
156 });
157 }
158  
159  
160 /**
161 * Override a bunch of methods the same way. If the number of points is below the threshold,
162 * run the original method. If not, check for a canvas version or do nothing.
163 */
164 // each(['translate', 'generatePoints', 'drawTracker', 'drawPoints', 'render'], function (method) {
165 // function branch(proceed) {
166 // var letItPass = this.options.stacking && (method === 'translate' || method === 'generatePoints');
167 // if (((this.processedXData || this.options.data).length < (this.options.boostThreshold || Number.MAX_VALUE) ||
168 // letItPass) || !isChartSeriesBoosting(this.chart)) {
169  
170 // // Clear image
171 // if (method === 'render' && this.image) {
172 // this.image.attr({ href: '' });
173 // this.animate = null; // We're zooming in, don't run animation
174 // }
175  
176 // proceed.call(this);
177  
178 // // If a canvas version of the method exists, like renderCanvas(), run
179 // } else if (this[method + 'Canvas']) {
180  
181 // this[method + 'Canvas']();
182 // }
183 // }
184 // wrap(Series.prototype, method, branch);
185  
186 // // A special case for some types - its translate method is already wrapped
187 // if (method === 'translate') {
188 // each(['arearange', 'bubble', 'column'], function (type) {
189 // if (seriesTypes[type]) {
190 // wrap(seriesTypes[type].prototype, method, branch);
191 // }
192 // });
193 // }
194 // });
195  
196 H.extend(Series.prototype, {
197 directTouch: false,
198 pointRange: 0,
199 allowDG: false, // No data grouping, let boost handle large data
200 hasExtremes: function(checkX) {
201 var options = this.options,
202 data = options.data,
203 xAxis = this.xAxis && this.xAxis.options,
204 yAxis = this.yAxis && this.yAxis.options;
205 return data.length > (options.boostThreshold || Number.MAX_VALUE) && isNumber(yAxis.min) && isNumber(yAxis.max) &&
206 (!checkX || (isNumber(xAxis.min) && isNumber(xAxis.max)));
207 },
208  
209 /**
210 * If implemented in the core, parts of this can probably be shared with other similar
211 * methods in Highcharts.
212 */
213 destroyGraphics: function() {
214 var series = this,
215 points = this.points,
216 point,
217 i;
218  
219 if (points) {
220 for (i = 0; i < points.length; i = i + 1) {
221 point = points[i];
222 if (point && point.graphic) {
223 point.graphic = point.graphic.destroy();
224 }
225 }
226 }
227  
228 each(['graph', 'area', 'tracker'], function(prop) {
229 if (series[prop]) {
230 series[prop] = series[prop].destroy();
231 }
232 });
233 },
234  
235 /**
236 * Create a hidden canvas to draw the graph on. The contents is later copied over
237 * to an SVG image element.
238 */
239 getContext: function() {
240 var chart = this.chart,
241 width = chart.chartWidth,
242 height = chart.chartHeight,
243 targetGroup = this.group,
244 target = this,
245 ctx,
246 swapXY = function(proceed, x, y, a, b, c, d) {
247 proceed.call(this, y, x, a, b, c, d);
248 };
249  
250 if (isChartSeriesBoosting(chart)) {
251 target = chart;
252 targetGroup = chart.seriesGroup;
253 }
254  
255 ctx = target.ctx;
256  
257 if (!target.canvas) {
258 target.canvas = doc.createElement('canvas');
259  
260 target.image = chart.renderer.image(
261 '',
262 0,
263 0,
264 width,
265 height
266 ).add(targetGroup);
267  
268 target.ctx = ctx = target.canvas.getContext('2d');
269  
270 if (chart.inverted) {
271 each(['moveTo', 'lineTo', 'rect', 'arc'], function(fn) {
272 wrap(ctx, fn, swapXY);
273 });
274 }
275  
276 target.boostClipRect = chart.renderer.clipRect(
277 chart.plotLeft,
278 chart.plotTop,
279 chart.plotWidth,
280 chart.chartHeight
281 );
282  
283 target.image.clip(target.boostClipRect);
284  
285 } else if (!(target instanceof H.Chart)) {
286 //ctx.clearRect(0, 0, width, height);
287 }
288  
289 if (target.canvas.width !== width) {
290 target.canvas.width = width;
291 }
292  
293 if (target.canvas.height !== height) {
294 target.canvas.height = height;
295 }
296  
297 target.image.attr({
298 x: 0,
299 y: 0,
300 width: width,
301 height: height,
302 style: 'pointer-events: none'
303 });
304  
305 target.boostClipRect.attr({
306 x: 0,
307 y: 0,
308 width: chart.plotWidth,
309 height: chart.chartHeight
310 });
311  
312 return ctx;
313 },
314  
315 /**
316 * Draw the canvas image inside an SVG image
317 */
318 canvasToSVG: function() {
319 if (!isChartSeriesBoosting(this.chart)) {
320 this.image.attr({
321 href: this.canvas.toDataURL('image/png')
322 });
323 } else if (this.image) {
324 this.image.attr({
325 href: ''
326 });
327 }
328 },
329  
330 cvsLineTo: function(ctx, clientX, plotY) {
331 ctx.lineTo(clientX, plotY);
332 },
333  
334 renderCanvas: function() {
335 var series = this,
336 options = series.options,
337 chart = series.chart,
338 xAxis = this.xAxis,
339 yAxis = this.yAxis,
340 activeBoostSettings = chart.options.boost || {},
341 boostSettings = {
342 timeRendering: activeBoostSettings.timeRendering || false,
343 timeSeriesProcessing: activeBoostSettings.timeSeriesProcessing || false,
344 timeSetup: activeBoostSettings.timeSetup || false
345 },
346 ctx,
347 c = 0,
348 xData = series.processedXData,
349 yData = series.processedYData,
350 rawData = options.data,
351 xExtremes = xAxis.getExtremes(),
352 xMin = xExtremes.min,
353 xMax = xExtremes.max,
354 yExtremes = yAxis.getExtremes(),
355 yMin = yExtremes.min,
356 yMax = yExtremes.max,
357 pointTaken = {},
358 lastClientX,
359 sampling = !!series.sampling,
360 points,
361 r = options.marker && options.marker.radius,
362 cvsDrawPoint = this.cvsDrawPoint,
363 cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
364 cvsMarker = r && r <= 1 ?
365 this.cvsMarkerSquare :
366 this.cvsMarkerCircle,
367 strokeBatch = this.cvsStrokeBatch || 1000,
368 enableMouseTracking = options.enableMouseTracking !== false,
369 lastPoint,
370 threshold = options.threshold,
371 yBottom = yAxis.getThreshold(threshold),
372 hasThreshold = isNumber(threshold),
373 translatedThreshold = yBottom,
374 doFill = this.fill,
375 isRange = series.pointArrayMap && series.pointArrayMap.join(',') === 'low,high',
376 isStacked = !!options.stacking,
377 cropStart = series.cropStart || 0,
378 loadingOptions = chart.options.loading,
379 requireSorting = series.requireSorting,
380 wasNull,
381 connectNulls = options.connectNulls,
382 useRaw = !xData,
383 minVal,
384 maxVal,
385 minI,
386 maxI,
387 kdIndex,
388 sdata = isStacked ? series.data : (xData || rawData),
389 fillColor = series.fillOpacity ?
390 new Color(series.color).setOpacity(pick(options.fillOpacity, 0.75)).get() :
391 series.color,
392  
393 stroke = function() {
394 if (doFill) {
395 ctx.fillStyle = fillColor;
396 ctx.fill();
397 } else {
398 ctx.strokeStyle = series.color;
399 ctx.lineWidth = options.lineWidth;
400 ctx.stroke();
401 }
402 },
403  
404 drawPoint = function(clientX, plotY, yBottom, i) {
405 if (c === 0) {
406 ctx.beginPath();
407  
408 if (cvsLineTo) {
409 ctx.lineJoin = 'round';
410 }
411 }
412  
413 if (chart.scroller && series.options.className === 'highcharts-navigator-series') {
414 plotY += chart.scroller.top;
415 if (yBottom) {
416 yBottom += chart.scroller.top;
417 }
418 } else {
419 plotY += chart.plotTop;
420 }
421  
422 clientX += chart.plotLeft;
423  
424 if (wasNull) {
425 ctx.moveTo(clientX, plotY);
426 } else {
427 if (cvsDrawPoint) {
428 cvsDrawPoint(ctx, clientX, plotY, yBottom, lastPoint);
429 } else if (cvsLineTo) {
430 cvsLineTo(ctx, clientX, plotY);
431 } else if (cvsMarker) {
432 cvsMarker.call(series, ctx, clientX, plotY, r, i);
433 }
434 }
435  
436 // We need to stroke the line for every 1000 pixels. It will crash the browser
437 // memory use if we stroke too infrequently.
438 c = c + 1;
439 if (c === strokeBatch) {
440 stroke();
441 c = 0;
442 }
443  
444 // Area charts need to keep track of the last point
445 lastPoint = {
446 clientX: clientX,
447 plotY: plotY,
448 yBottom: yBottom
449 };
450 },
451  
452 addKDPoint = function(clientX, plotY, i) {
453 // Avoid more string concatination than required
454 kdIndex = clientX + ',' + plotY;
455  
456 // The k-d tree requires series points. Reduce the amount of points, since the time to build the
457 // tree increases exponentially.
458 if (enableMouseTracking && !pointTaken[kdIndex]) {
459 pointTaken[kdIndex] = true;
460  
461 if (chart.inverted) {
462 clientX = xAxis.len - clientX;
463 plotY = yAxis.len - plotY;
464 }
465  
466 points.push({
467 clientX: clientX,
468 plotX: clientX,
469 plotY: plotY,
470 i: cropStart + i
471 });
472 }
473 };
474  
475 // If we are zooming out from SVG mode, destroy the graphics
476 if (this.points || this.graph) {
477 this.destroyGraphics();
478 }
479  
480 // The group
481 series.plotGroup(
482 'group',
483 'series',
484 series.visible ? 'visible' : 'hidden',
485 options.zIndex,
486 chart.seriesGroup
487 );
488  
489 series.markerGroup = series.group;
490 // addEvent(series, 'destroy', function () {
491 // series.markerGroup = null;
492 // });
493  
494 points = this.points = [];
495 ctx = this.getContext();
496 series.buildKDTree = noop; // Do not start building while drawing
497  
498 // Display a loading indicator
499 if (rawData.length > 99999) {
500 chart.options.loading = merge(loadingOptions, {
501 labelStyle: {
502 backgroundColor: H.color('#ffffff').setOpacity(0.75).get(),
503 padding: '1em',
504 borderRadius: '0.5em'
505 },
506 style: {
507 backgroundColor: 'none',
508 opacity: 1
509 }
510 });
511 clearTimeout(destroyLoadingDiv);
512 chart.showLoading('Drawing...');
513 chart.options.loading = loadingOptions; // reset
514 }
515  
516 if (boostSettings.timeRendering) {
517 console.time('canvas rendering'); // eslint-disable-line no-console
518 }
519  
520 // Loop over the points
521 eachAsync(sdata, function(d, i) {
522 var x,
523 y,
524 clientX,
525 plotY,
526 isNull,
527 low,
528 isNextInside = false,
529 isPrevInside = false,
530 nx = false,
531 px = false,
532 chartDestroyed = typeof chart.index === 'undefined',
533 isYInside = true;
534  
535 if (!chartDestroyed) {
536 if (useRaw) {
537 x = d[0];
538 y = d[1];
539  
540 if (sdata[i + 1]) {
541 nx = sdata[i + 1][0];
542 }
543  
544 if (sdata[i - 1]) {
545 px = sdata[i - 1][0];
546 }
547 } else {
548 x = d;
549 y = yData[i];
550  
551 if (sdata[i + 1]) {
552 nx = sdata[i + 1];
553 }
554  
555 if (sdata[i - 1]) {
556 px = sdata[i - 1];
557 }
558 }
559  
560 if (nx && nx >= xMin && nx <= xMax) {
561 isNextInside = true;
562 }
563  
564 if (px && px >= xMin && px <= xMax) {
565 isPrevInside = true;
566 }
567  
568 // Resolve low and high for range series
569 if (isRange) {
570 if (useRaw) {
571 y = d.slice(1, 3);
572 }
573 low = y[0];
574 y = y[1];
575 } else if (isStacked) {
576 x = d.x;
577 y = d.stackY;
578 low = y - d.y;
579 }
580  
581 isNull = y === null;
582  
583 // Optimize for scatter zooming
584 if (!requireSorting) {
585 isYInside = y >= yMin && y <= yMax;
586 }
587  
588 if (!isNull &&
589 (
590 (x >= xMin && x <= xMax && isYInside) ||
591 (isNextInside || isPrevInside)
592 )) {
593  
594  
595 clientX = Math.round(xAxis.toPixels(x, true));
596  
597 if (sampling) {
598 if (minI === undefined || clientX === lastClientX) {
599 if (!isRange) {
600 low = y;
601 }
602 if (maxI === undefined || y > maxVal) {
603 maxVal = y;
604 maxI = i;
605 }
606 if (minI === undefined || low < minVal) {
607 minVal = low;
608 minI = i;
609 }
610  
611 }
612 if (clientX !== lastClientX) { // Add points and reset
613 if (minI !== undefined) { // then maxI is also a number
614 plotY = yAxis.toPixels(maxVal, true);
615 yBottom = yAxis.toPixels(minVal, true);
616 drawPoint(
617 clientX,
618 hasThreshold ? Math.min(plotY, translatedThreshold) : plotY,
619 hasThreshold ? Math.max(yBottom, translatedThreshold) : yBottom,
620 i
621 );
622 addKDPoint(clientX, plotY, maxI);
623 if (yBottom !== plotY) {
624 addKDPoint(clientX, yBottom, minI);
625 }
626 }
627  
628 minI = maxI = undefined;
629 lastClientX = clientX;
630 }
631 } else {
632 plotY = Math.round(yAxis.toPixels(y, true));
633 drawPoint(clientX, plotY, yBottom, i);
634 addKDPoint(clientX, plotY, i);
635 }
636 }
637 wasNull = isNull && !connectNulls;
638  
639 if (i % CHUNK_SIZE === 0) {
640 series.canvasToSVG();
641 }
642 }
643  
644 return !chartDestroyed;
645 }, function() {
646 var loadingDiv = chart.loadingDiv,
647 loadingShown = chart.loadingShown;
648 stroke();
649 series.canvasToSVG();
650  
651 if (boostSettings.timeRendering) {
652 console.timeEnd('canvas rendering'); // eslint-disable-line no-console
653 }
654  
655 fireEvent(series, 'renderedCanvas');
656  
657 // Do not use chart.hideLoading, as it runs JS animation and will be blocked by buildKDTree.
658 // CSS animation looks good, but then it must be deleted in timeout. If we add the module to core,
659 // change hideLoading so we can skip this block.
660 if (loadingShown) {
661 extend(loadingDiv.style, {
662 transition: 'opacity 250ms',
663 opacity: 0
664 });
665 chart.loadingShown = false;
666 destroyLoadingDiv = setTimeout(function() {
667 if (loadingDiv.parentNode) { // In exporting it is falsy
668 loadingDiv.parentNode.removeChild(loadingDiv);
669 }
670 chart.loadingDiv = chart.loadingSpan = null;
671 }, 250);
672 }
673  
674 // Pass tests in Pointer.
675 // Replace this with a single property, and replace when zooming in
676 // below boostThreshold.
677 series.directTouch = false;
678 series.options.stickyTracking = true;
679  
680 delete series.buildKDTree; // Go back to prototype, ready to build
681 series.buildKDTree();
682  
683 // Don't do async on export, the exportChart, getSVGForExport and getSVG methods are not chained for it.
684 }, chart.renderer.forExport ? Number.MAX_VALUE : undefined);
685 }
686 });
687  
688 wrap(Series.prototype, 'setData', function(proceed) {
689 if (!this.hasExtremes || !this.hasExtremes(true) || this.type === 'heatmap') {
690 proceed.apply(this, Array.prototype.slice.call(arguments, 1));
691 }
692 });
693  
694 wrap(Series.prototype, 'processData', function(proceed) {
695 if (!this.hasExtremes || !this.hasExtremes(true) || this.type === 'heatmap') {
696 proceed.apply(this, Array.prototype.slice.call(arguments, 1));
697 }
698 });
699  
700 seriesTypes.scatter.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r) {
701 ctx.moveTo(clientX, plotY);
702 ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
703 };
704  
705 // Rect is twice as fast as arc, should be used for small markers
706 seriesTypes.scatter.prototype.cvsMarkerSquare = function(ctx, clientX, plotY, r) {
707 ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
708 };
709 seriesTypes.scatter.prototype.fill = true;
710  
711 if (seriesTypes.bubble) {
712 seriesTypes.bubble.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r, i) {
713 ctx.moveTo(clientX, plotY);
714 ctx.arc(clientX, plotY, this.radii && this.radii[i], 0, 2 * Math.PI, false);
715 };
716 seriesTypes.bubble.prototype.cvsStrokeBatch = 1;
717 }
718  
719 extend(seriesTypes.area.prototype, {
720 cvsDrawPoint: function(ctx, clientX, plotY, yBottom, lastPoint) {
721 if (lastPoint && clientX !== lastPoint.clientX) {
722 ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
723 ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
724 ctx.lineTo(clientX, plotY);
725 ctx.lineTo(clientX, yBottom);
726 }
727 },
728 fill: true,
729 fillOpacity: true,
730 sampling: true
731 });
732  
733 extend(seriesTypes.column.prototype, {
734 cvsDrawPoint: function(ctx, clientX, plotY, yBottom) {
735 ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
736 },
737 fill: true,
738 sampling: true
739 });
740  
741 H.Chart.prototype.callbacks.push(function(chart) {
742 function canvasToSVG() {
743 if (chart.image && chart.canvas) {
744 chart.image.attr({
745 href: chart.canvas.toDataURL('image/png')
746 });
747 }
748 }
749  
750 function clear() {
751 if (chart.image) {
752 chart.image.attr({
753 href: ''
754 });
755 }
756  
757 if (chart.canvas) {
758 chart.canvas.getContext('2d').clearRect(
759 0,
760 0,
761 chart.canvas.width,
762 chart.canvas.height
763 );
764 }
765 }
766  
767 addEvent(chart, 'predraw', clear);
768 addEvent(chart, 'render', canvasToSVG);
769 });
770 };
771  
772 }(Highcharts));
773 }));