263 lines
7.7 KiB
JavaScript
263 lines
7.7 KiB
JavaScript
/* *
|
|
*
|
|
* Copyright (c) 2019-2020 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.
|
|
*
|
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
|
*
|
|
* */
|
|
'use strict';
|
|
import H from '../../parts/Globals.js';
|
|
import '../../parts/Series.js';
|
|
import boostableMap from './boostable-map.js';
|
|
import createAndAttachRenderer from './boost-attach.js';
|
|
import U from '../../parts/Utilities.js';
|
|
var pick = U.pick;
|
|
var win = H.win, doc = win.document;
|
|
// This should be a const.
|
|
var CHUNK_SIZE = 3000;
|
|
/**
|
|
* Tolerant max() function.
|
|
*
|
|
* @private
|
|
* @function patientMax
|
|
*
|
|
* @param {...Array<Array<unknown>>} args
|
|
* Max arguments
|
|
*
|
|
* @return {number}
|
|
* Max value
|
|
*/
|
|
function patientMax() {
|
|
var args = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
args[_i] = arguments[_i];
|
|
}
|
|
var 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}
|
|
* True, if boost is enabled.
|
|
*/
|
|
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}
|
|
* True, if boosting should be forced.
|
|
*/
|
|
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;
|
|
}
|
|
/* eslint-disable valid-jsdoc */
|
|
/**
|
|
* Performs the actual render if the renderer is
|
|
* attached to the series.
|
|
* @private
|
|
* @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);
|
|
}
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
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) {
|
|
// silent error
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/* eslint-disable no-invalid-this */
|
|
/**
|
|
* 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);
|
|
}
|
|
/* eslint-enable no-invalid-this, valid-jsdoc */
|
|
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;
|