corrade-nucleus-nucleons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /**
2 * @license Highcharts JS v5.0.10 (2017-03-31)
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 pointAttr = (point.pointAttr && point.pointAttr['']) || point.series.pointAttribs(point);
139  
140 ctx.fillStyle = pointAttr.fill;
141 ctx.fillRect(shapeArgs.x, shapeArgs.y, shapeArgs.width, shapeArgs.height);
142 }
143 });
144  
145 this.canvasToSVG();
146  
147 } else {
148 this.chart.showLoading('Your browser doesn\'t support HTML5 canvas, <br>please use a modern browser');
149  
150 // Uncomment this to provide low-level (slow) support in oldIE. It will cause script errors on
151 // charts with more than a few thousand points.
152 // arguments[0].call(this);
153 }
154 });
155 }
156  
157  
158 /**
159 * Override a bunch of methods the same way. If the number of points is below the threshold,
160 * run the original method. If not, check for a canvas version or do nothing.
161 */
162 // each(['translate', 'generatePoints', 'drawTracker', 'drawPoints', 'render'], function (method) {
163 // function branch(proceed) {
164 // var letItPass = this.options.stacking && (method === 'translate' || method === 'generatePoints');
165 // if (((this.processedXData || this.options.data).length < (this.options.boostThreshold || Number.MAX_VALUE) ||
166 // letItPass) || !isChartSeriesBoosting(this.chart)) {
167  
168 // // Clear image
169 // if (method === 'render' && this.image) {
170 // this.image.attr({ href: '' });
171 // this.animate = null; // We're zooming in, don't run animation
172 // }
173  
174 // proceed.call(this);
175  
176 // // If a canvas version of the method exists, like renderCanvas(), run
177 // } else if (this[method + 'Canvas']) {
178  
179 // this[method + 'Canvas']();
180 // }
181 // }
182 // wrap(Series.prototype, method, branch);
183  
184 // // A special case for some types - its translate method is already wrapped
185 // if (method === 'translate') {
186 // each(['arearange', 'bubble', 'column'], function (type) {
187 // if (seriesTypes[type]) {
188 // wrap(seriesTypes[type].prototype, method, branch);
189 // }
190 // });
191 // }
192 // });
193  
194 H.extend(Series.prototype, {
195 directTouch: false,
196 pointRange: 0,
197 allowDG: false, // No data grouping, let boost handle large data
198 hasExtremes: function(checkX) {
199 var options = this.options,
200 data = options.data,
201 xAxis = this.xAxis && this.xAxis.options,
202 yAxis = this.yAxis && this.yAxis.options;
203 return data.length > (options.boostThreshold || Number.MAX_VALUE) && isNumber(yAxis.min) && isNumber(yAxis.max) &&
204 (!checkX || (isNumber(xAxis.min) && isNumber(xAxis.max)));
205 },
206  
207 /**
208 * If implemented in the core, parts of this can probably be shared with other similar
209 * methods in Highcharts.
210 */
211 destroyGraphics: function() {
212 var series = this,
213 points = this.points,
214 point,
215 i;
216  
217 if (points) {
218 for (i = 0; i < points.length; i = i + 1) {
219 point = points[i];
220 if (point && point.graphic) {
221 point.graphic = point.graphic.destroy();
222 }
223 }
224 }
225  
226 each(['graph', 'area', 'tracker'], function(prop) {
227 if (series[prop]) {
228 series[prop] = series[prop].destroy();
229 }
230 });
231 },
232  
233 /**
234 * Create a hidden canvas to draw the graph on. The contents is later copied over
235 * to an SVG image element.
236 */
237 getContext: function() {
238 var chart = this.chart,
239 width = chart.chartWidth,
240 height = chart.chartHeight,
241 targetGroup = this.group,
242 target = this,
243 ctx,
244 swapXY = function(proceed, x, y, a, b, c, d) {
245 proceed.call(this, y, x, a, b, c, d);
246 };
247  
248 if (isChartSeriesBoosting(chart)) {
249 target = chart;
250 targetGroup = chart.seriesGroup;
251 }
252  
253 ctx = target.ctx;
254  
255 if (!target.canvas) {
256 target.canvas = doc.createElement('canvas');
257  
258 target.image = chart.renderer.image(
259 '',
260 0,
261 0,
262 width,
263 height
264 ).add(targetGroup);
265  
266 target.ctx = ctx = target.canvas.getContext('2d');
267  
268 if (chart.inverted) {
269 each(['moveTo', 'lineTo', 'rect', 'arc'], function(fn) {
270 wrap(ctx, fn, swapXY);
271 });
272 }
273  
274 target.boostClipRect = chart.renderer.clipRect(
275 chart.plotLeft,
276 chart.plotTop,
277 chart.plotWidth,
278 chart.chartHeight
279 );
280  
281 target.image.clip(target.boostClipRect);
282  
283 } else if (!(target instanceof H.Chart)) {
284 //ctx.clearRect(0, 0, width, height);
285 }
286  
287 if (target.canvas.width !== width) {
288 target.canvas.width = width;
289 }
290  
291 if (target.canvas.height !== height) {
292 target.canvas.height = height;
293 }
294  
295 target.image.attr({
296 x: 0,
297 y: 0,
298 width: width,
299 height: height,
300 style: 'pointer-events: none'
301 });
302  
303 target.boostClipRect.attr({
304 x: 0,
305 y: 0,
306 width: chart.plotWidth,
307 height: chart.chartHeight
308 });
309  
310 return ctx;
311 },
312  
313 /**
314 * Draw the canvas image inside an SVG image
315 */
316 canvasToSVG: function() {
317 if (!isChartSeriesBoosting(this.chart)) {
318 this.image.attr({
319 href: this.canvas.toDataURL('image/png')
320 });
321 } else if (this.image) {
322 this.image.attr({
323 href: ''
324 });
325 }
326 },
327  
328 cvsLineTo: function(ctx, clientX, plotY) {
329 ctx.lineTo(clientX, plotY);
330 },
331  
332 renderCanvas: function() {
333 var series = this,
334 options = series.options,
335 chart = series.chart,
336 xAxis = this.xAxis,
337 yAxis = this.yAxis,
338 activeBoostSettings = chart.options.boost || {},
339 boostSettings = {
340 timeRendering: activeBoostSettings.timeRendering || false,
341 timeSeriesProcessing: activeBoostSettings.timeSeriesProcessing || false,
342 timeSetup: activeBoostSettings.timeSetup || false
343 },
344 ctx,
345 c = 0,
346 xData = series.processedXData,
347 yData = series.processedYData,
348 rawData = options.data,
349 xExtremes = xAxis.getExtremes(),
350 xMin = xExtremes.min,
351 xMax = xExtremes.max,
352 yExtremes = yAxis.getExtremes(),
353 yMin = yExtremes.min,
354 yMax = yExtremes.max,
355 pointTaken = {},
356 lastClientX,
357 sampling = !!series.sampling,
358 points,
359 r = options.marker && options.marker.radius,
360 cvsDrawPoint = this.cvsDrawPoint,
361 cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
362 cvsMarker = r && r <= 1 ?
363 this.cvsMarkerSquare :
364 this.cvsMarkerCircle,
365 strokeBatch = this.cvsStrokeBatch || 1000,
366 enableMouseTracking = options.enableMouseTracking !== false,
367 lastPoint,
368 threshold = options.threshold,
369 yBottom = yAxis.getThreshold(threshold),
370 hasThreshold = isNumber(threshold),
371 translatedThreshold = yBottom,
372 doFill = this.fill,
373 isRange = series.pointArrayMap && series.pointArrayMap.join(',') === 'low,high',
374 isStacked = !!options.stacking,
375 cropStart = series.cropStart || 0,
376 loadingOptions = chart.options.loading,
377 requireSorting = series.requireSorting,
378 wasNull,
379 connectNulls = options.connectNulls,
380 useRaw = !xData,
381 minVal,
382 maxVal,
383 minI,
384 maxI,
385 kdIndex,
386 sdata = isStacked ? series.data : (xData || rawData),
387 fillColor = series.fillOpacity ?
388 new Color(series.color).setOpacity(pick(options.fillOpacity, 0.75)).get() :
389 series.color,
390  
391 stroke = function() {
392 if (doFill) {
393 ctx.fillStyle = fillColor;
394 ctx.fill();
395 } else {
396 ctx.strokeStyle = series.color;
397 ctx.lineWidth = options.lineWidth;
398 ctx.stroke();
399 }
400 },
401  
402 drawPoint = function(clientX, plotY, yBottom, i) {
403 if (c === 0) {
404 ctx.beginPath();
405  
406 if (cvsLineTo) {
407 ctx.lineJoin = 'round';
408 }
409 }
410  
411 if (chart.scroller && series.options.className === 'highcharts-navigator-series') {
412 plotY += chart.scroller.top;
413 if (yBottom) {
414 yBottom += chart.scroller.top;
415 }
416 } else {
417 plotY += chart.plotTop;
418 }
419  
420 clientX += chart.plotLeft;
421  
422 if (wasNull) {
423 ctx.moveTo(clientX, plotY);
424 } else {
425 if (cvsDrawPoint) {
426 cvsDrawPoint(ctx, clientX, plotY, yBottom, lastPoint);
427 } else if (cvsLineTo) {
428 cvsLineTo(ctx, clientX, plotY);
429 } else if (cvsMarker) {
430 cvsMarker.call(series, ctx, clientX, plotY, r, i);
431 }
432 }
433  
434 // We need to stroke the line for every 1000 pixels. It will crash the browser
435 // memory use if we stroke too infrequently.
436 c = c + 1;
437 if (c === strokeBatch) {
438 stroke();
439 c = 0;
440 }
441  
442 // Area charts need to keep track of the last point
443 lastPoint = {
444 clientX: clientX,
445 plotY: plotY,
446 yBottom: yBottom
447 };
448 },
449  
450 addKDPoint = function(clientX, plotY, i) {
451 // Avoid more string concatination than required
452 kdIndex = clientX + ',' + plotY;
453  
454 // The k-d tree requires series points. Reduce the amount of points, since the time to build the
455 // tree increases exponentially.
456 if (enableMouseTracking && !pointTaken[kdIndex]) {
457 pointTaken[kdIndex] = true;
458  
459 if (chart.inverted) {
460 clientX = xAxis.len - clientX;
461 plotY = yAxis.len - plotY;
462 }
463  
464 points.push({
465 clientX: clientX,
466 plotX: clientX,
467 plotY: plotY,
468 i: cropStart + i
469 });
470 }
471 };
472  
473 // If we are zooming out from SVG mode, destroy the graphics
474 if (this.points || this.graph) {
475 this.destroyGraphics();
476 }
477  
478 // The group
479 series.plotGroup(
480 'group',
481 'series',
482 series.visible ? 'visible' : 'hidden',
483 options.zIndex,
484 chart.seriesGroup
485 );
486  
487 series.markerGroup = series.group;
488 // addEvent(series, 'destroy', function () {
489 // series.markerGroup = null;
490 // });
491  
492 points = this.points = [];
493 ctx = this.getContext();
494 series.buildKDTree = noop; // Do not start building while drawing
495  
496 // Display a loading indicator
497 if (rawData.length > 99999) {
498 chart.options.loading = merge(loadingOptions, {
499 labelStyle: {
500 backgroundColor: H.color('#ffffff').setOpacity(0.75).get(),
501 padding: '1em',
502 borderRadius: '0.5em'
503 },
504 style: {
505 backgroundColor: 'none',
506 opacity: 1
507 }
508 });
509 clearTimeout(destroyLoadingDiv);
510 chart.showLoading('Drawing...');
511 chart.options.loading = loadingOptions; // reset
512 }
513  
514 if (boostSettings.timeRendering) {
515 console.time('canvas rendering'); // eslint-disable-line no-console
516 }
517  
518 // Loop over the points
519 eachAsync(sdata, function(d, i) {
520 var x,
521 y,
522 clientX,
523 plotY,
524 isNull,
525 low,
526 isNextInside = false,
527 isPrevInside = false,
528 nx = false,
529 px = false,
530 chartDestroyed = typeof chart.index === 'undefined',
531 isYInside = true;
532  
533 if (!chartDestroyed) {
534 if (useRaw) {
535 x = d[0];
536 y = d[1];
537  
538 if (sdata[i + 1]) {
539 nx = sdata[i + 1][0];
540 }
541  
542 if (sdata[i - 1]) {
543 px = sdata[i - 1][0];
544 }
545 } else {
546 x = d;
547 y = yData[i];
548  
549 if (sdata[i + 1]) {
550 nx = sdata[i + 1];
551 }
552  
553 if (sdata[i - 1]) {
554 px = sdata[i - 1];
555 }
556 }
557  
558 if (nx && nx >= xMin && nx <= xMax) {
559 isNextInside = true;
560 }
561  
562 if (px && px >= xMin && px <= xMax) {
563 isPrevInside = true;
564 }
565  
566 // Resolve low and high for range series
567 if (isRange) {
568 if (useRaw) {
569 y = d.slice(1, 3);
570 }
571 low = y[0];
572 y = y[1];
573 } else if (isStacked) {
574 x = d.x;
575 y = d.stackY;
576 low = y - d.y;
577 }
578  
579 isNull = y === null;
580  
581 // Optimize for scatter zooming
582 if (!requireSorting) {
583 isYInside = y >= yMin && y <= yMax;
584 }
585  
586 if (!isNull &&
587 (
588 (x >= xMin && x <= xMax && isYInside) ||
589 (isNextInside || isPrevInside)
590 )) {
591  
592  
593 clientX = Math.round(xAxis.toPixels(x, true));
594  
595 if (sampling) {
596 if (minI === undefined || clientX === lastClientX) {
597 if (!isRange) {
598 low = y;
599 }
600 if (maxI === undefined || y > maxVal) {
601 maxVal = y;
602 maxI = i;
603 }
604 if (minI === undefined || low < minVal) {
605 minVal = low;
606 minI = i;
607 }
608  
609 }
610 if (clientX !== lastClientX) { // Add points and reset
611 if (minI !== undefined) { // then maxI is also a number
612 plotY = yAxis.toPixels(maxVal, true);
613 yBottom = yAxis.toPixels(minVal, true);
614 drawPoint(
615 clientX,
616 hasThreshold ? Math.min(plotY, translatedThreshold) : plotY,
617 hasThreshold ? Math.max(yBottom, translatedThreshold) : yBottom,
618 i
619 );
620 addKDPoint(clientX, plotY, maxI);
621 if (yBottom !== plotY) {
622 addKDPoint(clientX, yBottom, minI);
623 }
624 }
625  
626 minI = maxI = undefined;
627 lastClientX = clientX;
628 }
629 } else {
630 plotY = Math.round(yAxis.toPixels(y, true));
631 drawPoint(clientX, plotY, yBottom, i);
632 addKDPoint(clientX, plotY, i);
633 }
634 }
635 wasNull = isNull && !connectNulls;
636  
637 if (i % CHUNK_SIZE === 0) {
638 series.canvasToSVG();
639 }
640 }
641  
642 return !chartDestroyed;
643 }, function() {
644 var loadingDiv = chart.loadingDiv,
645 loadingShown = chart.loadingShown;
646 stroke();
647 series.canvasToSVG();
648  
649 if (boostSettings.timeRendering) {
650 console.timeEnd('canvas rendering'); // eslint-disable-line no-console
651 }
652  
653 fireEvent(series, 'renderedCanvas');
654  
655 // Do not use chart.hideLoading, as it runs JS animation and will be blocked by buildKDTree.
656 // CSS animation looks good, but then it must be deleted in timeout. If we add the module to core,
657 // change hideLoading so we can skip this block.
658 if (loadingShown) {
659 extend(loadingDiv.style, {
660 transition: 'opacity 250ms',
661 opacity: 0
662 });
663 chart.loadingShown = false;
664 destroyLoadingDiv = setTimeout(function() {
665 if (loadingDiv.parentNode) { // In exporting it is falsy
666 loadingDiv.parentNode.removeChild(loadingDiv);
667 }
668 chart.loadingDiv = chart.loadingSpan = null;
669 }, 250);
670 }
671  
672 // Pass tests in Pointer.
673 // Replace this with a single property, and replace when zooming in
674 // below boostThreshold.
675 series.directTouch = false;
676 series.options.stickyTracking = true;
677  
678 delete series.buildKDTree; // Go back to prototype, ready to build
679 series.buildKDTree();
680  
681 // Don't do async on export, the exportChart, getSVGForExport and getSVG methods are not chained for it.
682 }, chart.renderer.forExport ? Number.MAX_VALUE : undefined);
683 }
684 });
685  
686 wrap(Series.prototype, 'setData', function(proceed) {
687 if (!this.hasExtremes || !this.hasExtremes(true) || this.type === 'heatmap') {
688 proceed.apply(this, Array.prototype.slice.call(arguments, 1));
689 }
690 });
691  
692 wrap(Series.prototype, 'processData', function(proceed) {
693 if (!this.hasExtremes || !this.hasExtremes(true) || this.type === 'heatmap') {
694 proceed.apply(this, Array.prototype.slice.call(arguments, 1));
695 }
696 });
697  
698 seriesTypes.scatter.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r) {
699 ctx.moveTo(clientX, plotY);
700 ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
701 };
702  
703 // Rect is twice as fast as arc, should be used for small markers
704 seriesTypes.scatter.prototype.cvsMarkerSquare = function(ctx, clientX, plotY, r) {
705 ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
706 };
707 seriesTypes.scatter.prototype.fill = true;
708  
709 if (seriesTypes.bubble) {
710 seriesTypes.bubble.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r, i) {
711 ctx.moveTo(clientX, plotY);
712 ctx.arc(clientX, plotY, this.radii && this.radii[i], 0, 2 * Math.PI, false);
713 };
714 seriesTypes.bubble.prototype.cvsStrokeBatch = 1;
715 }
716  
717 extend(seriesTypes.area.prototype, {
718 cvsDrawPoint: function(ctx, clientX, plotY, yBottom, lastPoint) {
719 if (lastPoint && clientX !== lastPoint.clientX) {
720 ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
721 ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
722 ctx.lineTo(clientX, plotY);
723 ctx.lineTo(clientX, yBottom);
724 }
725 },
726 fill: true,
727 fillOpacity: true,
728 sampling: true
729 });
730  
731 extend(seriesTypes.column.prototype, {
732 cvsDrawPoint: function(ctx, clientX, plotY, yBottom) {
733 ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
734 },
735 fill: true,
736 sampling: true
737 });
738  
739 H.Chart.prototype.callbacks.push(function(chart) {
740 function canvasToSVG() {
741 if (chart.image && chart.canvas) {
742 chart.image.attr({
743 href: chart.canvas.toDataURL('image/png')
744 });
745 }
746 }
747  
748 function clear() {
749 if (chart.image) {
750 chart.image.attr({
751 href: ''
752 });
753 }
754  
755 if (chart.canvas) {
756 chart.canvas.getContext('2d').clearRect(
757 0,
758 0,
759 chart.canvas.width,
760 chart.canvas.height
761 );
762 }
763 }
764  
765 addEvent(chart, 'predraw', clear);
766 addEvent(chart, 'render', canvasToSVG);
767 });
768 };
769  
770 }(Highcharts));
771 }));