/* * * * Copyright (c) 2019-2019 Highsoft AS * * Boost module: stripped-down renderer for higher performance * * License: highcharts.com/license * * This files contains generic utility functions used by the boost module. * * */ 'use strict'; import H from '../../parts/Globals.js'; import '../../parts/Series.js'; import boostableMap from './boostable-map.js'; import createAndAttachRenderer from './boost-attach.js'; var win = H.win, doc = win.document, pick = H.pick; // This should be a const. var CHUNK_SIZE = 3000; /** * Tolerant max() function. * * @private * @function patientMax * * @return {number} * max value */ function patientMax() { var args = Array.prototype.slice.call(arguments), r = -Number.MAX_VALUE; args.forEach(function (t) { if ( typeof t !== 'undefined' && t !== null && typeof t.length !== 'undefined' ) { // r = r < t.length ? t.length : r; if (t.length > 0) { r = t.length; return true; } } }); return r; } /** * Return true if ths boost.enabled option is true * * @private * @function boostEnabled * * @param {Highcharts.Chart} chart * The chart * * @return {boolean} */ function boostEnabled(chart) { return pick( ( chart && chart.options && chart.options.boost && chart.options.boost.enabled ), true ); } /** * Returns true if we should force boosting the chart * @private * @function shouldForceChartSeriesBoosting * * @param {Highcharts.Chart} chart * The chart to check for forcing on * * @return {boolean} */ function shouldForceChartSeriesBoosting(chart) { // If there are more than five series currently boosting, // we should boost the whole chart to avoid running out of webgl contexts. var sboostCount = 0, canBoostCount = 0, allowBoostForce = pick( chart.options.boost && chart.options.boost.allowForce, true ), series; if (typeof chart.boostForceChartBoost !== 'undefined') { return chart.boostForceChartBoost; } if (chart.series.length > 1) { for (var i = 0; i < chart.series.length; i++) { series = chart.series[i]; // Don't count series with boostThreshold set to 0 // See #8950 // Also don't count if the series is hidden. // See #9046 if (series.options.boostThreshold === 0 || series.visible === false) { continue; } // Don't count heatmap series as they are handled differently. // In the future we should make the heatmap/treemap path compatible // with forcing. See #9636. if (series.type === 'heatmap') { continue; } if (boostableMap[series.type]) { ++canBoostCount; } if (patientMax( series.processedXData, series.options.data, // series.xData, series.points ) >= (series.options.boostThreshold || Number.MAX_VALUE)) { ++sboostCount; } } } chart.boostForceChartBoost = allowBoostForce && ( ( canBoostCount === chart.series.length && sboostCount > 0 ) || sboostCount > 5 ); return chart.boostForceChartBoost; } /* * Performs the actual render if the renderer is * attached to the series. * @param renderer {OGLRenderer} - the renderer * @param series {Highcharts.Series} - the series */ function renderIfNotSeriesBoosting(renderer, series, chart) { if (renderer && series.renderTarget && series.canvas && !(chart || series.chart).isChartSeriesBoosting() ) { renderer.render(chart || series.chart); } } function allocateIfNotSeriesBoosting(renderer, series) { if (renderer && series.renderTarget && series.canvas && !series.chart.isChartSeriesBoosting() ) { renderer.allocateBufferForSingleSeries(series); } } /** * An "async" foreach loop. Uses a setTimeout to keep the loop from blocking the * UI thread. * * @private * * @param arr {Array} - the array to loop through * @param fn {Function} - the callback to call for each item * @param finalFunc {Function} - the callback to call when done * @param chunkSize {Number} - the number of iterations per timeout * @param i {Number} - the current index * @param noTimeout {Boolean} - set to true to skip timeouts */ function eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout) { i = i || 0; chunkSize = chunkSize || CHUNK_SIZE; var threshold = i + chunkSize, proceed = true; while (proceed && i < threshold && i < arr.length) { proceed = fn(arr[i], i); ++i; } if (proceed) { if (i < arr.length) { if (noTimeout) { eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout); } else if (win.requestAnimationFrame) { // If available, do requestAnimationFrame - shaves off a few ms win.requestAnimationFrame(function () { eachAsync(arr, fn, finalFunc, chunkSize, i); }); } else { setTimeout(function () { eachAsync(arr, fn, finalFunc, chunkSize, i); }); } } else if (finalFunc) { finalFunc(); } } } /** * Returns true if the current browser supports webgl * * @private * @function hasWebGLSupport * * @return {boolean} */ function hasWebGLSupport() { var i = 0, canvas, contexts = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'], context = false; if (typeof win.WebGLRenderingContext !== 'undefined') { canvas = doc.createElement('canvas'); for (; i < contexts.length; i++) { try { context = canvas.getContext(contexts[i]); if (typeof context !== 'undefined' && context !== null) { return true; } } catch (e) { } } } return false; } /** * Used for treemap|heatmap.drawPoints * * @private * @function pointDrawHandler * * @param {Function} proceed * * @return {*} */ function pointDrawHandler(proceed) { var enabled = true, renderer; if (this.chart.options && this.chart.options.boost) { enabled = typeof this.chart.options.boost.enabled === 'undefined' ? true : this.chart.options.boost.enabled; } if (!enabled || !this.isSeriesBoosting) { return proceed.call(this); } this.chart.isBoosting = true; // Make sure we have a valid OGL context renderer = createAndAttachRenderer(this.chart, this); if (renderer) { allocateIfNotSeriesBoosting(renderer, this); renderer.pushSeries(this); } renderIfNotSeriesBoosting(renderer, this); } var funs = { patientMax: patientMax, boostEnabled: boostEnabled, shouldForceChartSeriesBoosting: shouldForceChartSeriesBoosting, renderIfNotSeriesBoosting: renderIfNotSeriesBoosting, allocateIfNotSeriesBoosting: allocateIfNotSeriesBoosting, eachAsync: eachAsync, hasWebGLSupport: hasWebGLSupport, pointDrawHandler: pointDrawHandler }; // This needs to be fixed. H.hasWebGLSupport = hasWebGLSupport; export default funs;