chore: version inicial del codigo fuente del LMS Estafeta
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import butils from './BoostUtils.js';
|
||||
import init from './BoostInit.js';
|
||||
import './BoostOverrides.js';
|
||||
import './NamedColors.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var error = U.error;
|
||||
// These need to be fixed when we support named imports
|
||||
var hasWebGLSupport = butils.hasWebGLSupport;
|
||||
if (!hasWebGLSupport()) {
|
||||
if (typeof H.initCanvasBoost !== 'undefined') {
|
||||
// Fallback to canvas boost
|
||||
H.initCanvasBoost();
|
||||
}
|
||||
else {
|
||||
error(26);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// WebGL support is alright, and we're good to go.
|
||||
init();
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import GLRenderer from './WGLRenderer.js';
|
||||
import H from '../../Core/Globals.js';
|
||||
var doc = H.doc;
|
||||
import U from '../../Core/Utilities.js';
|
||||
var error = U.error;
|
||||
import '../../Core/Series/Series.js';
|
||||
var mainCanvas = doc.createElement('canvas');
|
||||
/**
|
||||
* Create a canvas + context and attach it to the target
|
||||
*
|
||||
* @private
|
||||
* @function createAndAttachRenderer
|
||||
*
|
||||
* @param {Highcharts.Chart} chart
|
||||
* the chart
|
||||
*
|
||||
* @param {Highcharts.Series} series
|
||||
* the series
|
||||
*
|
||||
* @return {Highcharts.BoostGLRenderer}
|
||||
* the canvas renderer
|
||||
*/
|
||||
function createAndAttachRenderer(chart, series) {
|
||||
var width = chart.chartWidth, height = chart.chartHeight, target = chart, targetGroup = chart.seriesGroup || series.group, alpha = 1, foSupported = doc.implementation.hasFeature('www.http://w3.org/TR/SVG11/feature#Extensibility', '1.1');
|
||||
if (chart.isChartSeriesBoosting()) {
|
||||
target = chart;
|
||||
}
|
||||
else {
|
||||
target = series;
|
||||
}
|
||||
// Support for foreignObject is flimsy as best.
|
||||
// IE does not support it, and Chrome has a bug which messes up
|
||||
// the canvas draw order.
|
||||
// As such, we force the Image fallback for now, but leaving the
|
||||
// actual Canvas path in-place in case this changes in the future.
|
||||
foSupported = false;
|
||||
if (!target.renderTarget) {
|
||||
target.canvas = mainCanvas;
|
||||
// Fall back to image tag if foreignObject isn't supported,
|
||||
// or if we're exporting.
|
||||
if (chart.renderer.forExport || !foSupported) {
|
||||
target.renderTarget = chart.renderer.image('', 0, 0, width, height)
|
||||
.addClass('highcharts-boost-canvas')
|
||||
.add(targetGroup);
|
||||
target.boostClear = function () {
|
||||
target.renderTarget.attr({ href: '' });
|
||||
};
|
||||
target.boostCopy = function () {
|
||||
target.boostResizeTarget();
|
||||
target.renderTarget.attr({
|
||||
href: target.canvas.toDataURL('image/png')
|
||||
});
|
||||
};
|
||||
}
|
||||
else {
|
||||
target.renderTargetFo = chart.renderer
|
||||
.createElement('foreignObject')
|
||||
.add(targetGroup);
|
||||
target.renderTarget = doc.createElement('canvas');
|
||||
target.renderTargetCtx =
|
||||
target.renderTarget.getContext('2d');
|
||||
target.renderTargetFo.element.appendChild(target.renderTarget);
|
||||
target.boostClear = function () {
|
||||
target.renderTarget.width =
|
||||
target.canvas.width;
|
||||
target.renderTarget.height =
|
||||
target.canvas.height;
|
||||
};
|
||||
target.boostCopy = function () {
|
||||
target.renderTarget.width =
|
||||
target.canvas.width;
|
||||
target.renderTarget.height =
|
||||
target.canvas.height;
|
||||
target.renderTargetCtx
|
||||
.drawImage(target.canvas, 0, 0);
|
||||
};
|
||||
}
|
||||
target.boostResizeTarget = function () {
|
||||
width = chart.chartWidth;
|
||||
height = chart.chartHeight;
|
||||
(target.renderTargetFo || target.renderTarget)
|
||||
.attr({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height: height
|
||||
})
|
||||
.css({
|
||||
pointerEvents: 'none',
|
||||
mixedBlendMode: 'normal',
|
||||
opacity: alpha
|
||||
});
|
||||
if (target instanceof H.Chart) {
|
||||
target.markerGroup.translate(chart.plotLeft, chart.plotTop);
|
||||
}
|
||||
};
|
||||
target.boostClipRect = chart.renderer.clipRect();
|
||||
(target.renderTargetFo || target.renderTarget)
|
||||
.clip(target.boostClipRect);
|
||||
if (target instanceof H.Chart) {
|
||||
target.markerGroup = target.renderer.g().add(targetGroup);
|
||||
target.markerGroup.translate(series.xAxis.pos, series.yAxis.pos);
|
||||
}
|
||||
}
|
||||
target.canvas.width = width;
|
||||
target.canvas.height = height;
|
||||
target.boostClipRect.attr(chart.getBoostClipRect(target));
|
||||
target.boostResizeTarget();
|
||||
target.boostClear();
|
||||
if (!target.ogl) {
|
||||
target.ogl = GLRenderer(function () {
|
||||
if (target.ogl.settings.debug.timeBufferCopy) {
|
||||
console.time('buffer copy'); // eslint-disable-line no-console
|
||||
}
|
||||
target.boostCopy();
|
||||
if (target.ogl.settings.debug.timeBufferCopy) {
|
||||
console.timeEnd('buffer copy'); // eslint-disable-line no-console
|
||||
}
|
||||
});
|
||||
if (!target.ogl.init(target.canvas)) {
|
||||
// The OGL renderer couldn't be inited.
|
||||
// This likely means a shader error as we wouldn't get to this point
|
||||
// if there was no WebGL support.
|
||||
error('[highcharts boost] - unable to init WebGL renderer');
|
||||
}
|
||||
// target.ogl.clear();
|
||||
target.ogl.setOptions(chart.options.boost || {});
|
||||
if (target instanceof H.Chart) {
|
||||
target.ogl.allocateBuffer(chart);
|
||||
}
|
||||
}
|
||||
target.ogl.setSize(width, height);
|
||||
return target.ogl;
|
||||
}
|
||||
export default createAndAttachRenderer;
|
||||
@@ -0,0 +1,293 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from '../../Core/Chart/Chart.js';
|
||||
import H from '../../Core/Globals.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, extend = U.extend, fireEvent = U.fireEvent, wrap = U.wrap;
|
||||
import '../../Core/Series/Series.js';
|
||||
import butils from './BoostUtils.js';
|
||||
import createAndAttachRenderer from './BoostAttach.js';
|
||||
var Series = H.Series, seriesTypes = H.seriesTypes, noop = function () { }, eachAsync = butils.eachAsync, pointDrawHandler = butils.pointDrawHandler, allocateIfNotSeriesBoosting = butils.allocateIfNotSeriesBoosting, renderIfNotSeriesBoosting = butils.renderIfNotSeriesBoosting, shouldForceChartSeriesBoosting = butils.shouldForceChartSeriesBoosting, index;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Initialize the boot module.
|
||||
*
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
function init() {
|
||||
extend(Series.prototype, {
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Series#renderCanvas
|
||||
*/
|
||||
renderCanvas: function () {
|
||||
var series = this, options = series.options || {}, renderer = false, chart = series.chart, xAxis = this.xAxis, yAxis = this.yAxis, xData = options.xData || series.processedXData, yData = options.yData || series.processedYData, rawData = options.data, xExtremes = xAxis.getExtremes(), xMin = xExtremes.min, xMax = xExtremes.max, yExtremes = yAxis.getExtremes(), yMin = yExtremes.min, yMax = yExtremes.max, pointTaken = {}, lastClientX, sampling = !!series.sampling, points, enableMouseTracking = options.enableMouseTracking !== false, threshold = options.threshold, yBottom = yAxis.getThreshold(threshold), isRange = series.pointArrayMap &&
|
||||
series.pointArrayMap.join(',') === 'low,high', isStacked = !!options.stacking, cropStart = series.cropStart || 0, requireSorting = series.requireSorting, useRaw = !xData, minVal, maxVal, minI, maxI, boostOptions, compareX = options.findNearestPointBy === 'x', xDataFull = (this.xData ||
|
||||
this.options.xData ||
|
||||
this.processedXData ||
|
||||
false), addKDPoint = function (clientX, plotY, i) {
|
||||
// We need to do ceil on the clientX to make things
|
||||
// snap to pixel values. The renderer will frequently
|
||||
// draw stuff on "sub-pixels".
|
||||
clientX = Math.ceil(clientX);
|
||||
// Shaves off about 60ms compared to repeated concatenation
|
||||
index = compareX ? clientX : clientX + ',' + plotY;
|
||||
// The k-d tree requires series points.
|
||||
// Reduce the amount of points, since the time to build the
|
||||
// tree increases exponentially.
|
||||
if (enableMouseTracking && !pointTaken[index]) {
|
||||
pointTaken[index] = true;
|
||||
if (chart.inverted) {
|
||||
clientX = xAxis.len - clientX;
|
||||
plotY = yAxis.len - plotY;
|
||||
}
|
||||
points.push({
|
||||
x: xDataFull ? xDataFull[cropStart + i] : false,
|
||||
clientX: clientX,
|
||||
plotX: clientX,
|
||||
plotY: plotY,
|
||||
i: cropStart + i
|
||||
});
|
||||
}
|
||||
};
|
||||
// Get or create the renderer
|
||||
renderer = createAndAttachRenderer(chart, series);
|
||||
chart.isBoosting = true;
|
||||
boostOptions = renderer.settings;
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
// If we are zooming out from SVG mode, destroy the graphics
|
||||
if (this.points || this.graph) {
|
||||
this.destroyGraphics();
|
||||
}
|
||||
// If we're rendering per. series we should create the marker groups
|
||||
// as usual.
|
||||
if (!chart.isChartSeriesBoosting()) {
|
||||
// If all series were boosting, but are not anymore
|
||||
// restore private markerGroup
|
||||
if (this.markerGroup === chart.markerGroup) {
|
||||
this.markerGroup = void 0;
|
||||
}
|
||||
this.markerGroup = series.plotGroup('markerGroup', 'markers', true, 1, chart.seriesGroup);
|
||||
}
|
||||
else {
|
||||
// If series has a private markeGroup, remove that
|
||||
// and use common markerGroup
|
||||
if (this.markerGroup &&
|
||||
this.markerGroup !== chart.markerGroup) {
|
||||
this.markerGroup.destroy();
|
||||
}
|
||||
// Use a single group for the markers
|
||||
this.markerGroup = chart.markerGroup;
|
||||
// When switching from chart boosting mode, destroy redundant
|
||||
// series boosting targets
|
||||
if (this.renderTarget) {
|
||||
this.renderTarget = this.renderTarget.destroy();
|
||||
}
|
||||
}
|
||||
points = this.points = [];
|
||||
// Do not start building while drawing
|
||||
series.buildKDTree = noop;
|
||||
if (renderer) {
|
||||
allocateIfNotSeriesBoosting(renderer, this);
|
||||
renderer.pushSeries(series);
|
||||
// Perform the actual renderer if we're on series level
|
||||
renderIfNotSeriesBoosting(renderer, this, chart);
|
||||
}
|
||||
/**
|
||||
* This builds the KD-tree
|
||||
* @private
|
||||
*/
|
||||
function processPoint(d, i) {
|
||||
var x, y, clientX, plotY, isNull, low = false, chartDestroyed = typeof chart.index === 'undefined', isYInside = true;
|
||||
if (!chartDestroyed) {
|
||||
if (useRaw) {
|
||||
x = d[0];
|
||||
y = d[1];
|
||||
}
|
||||
else {
|
||||
x = d;
|
||||
y = yData[i];
|
||||
}
|
||||
// Resolve low and high for range series
|
||||
if (isRange) {
|
||||
if (useRaw) {
|
||||
y = d.slice(1, 3);
|
||||
}
|
||||
low = y[0];
|
||||
y = y[1];
|
||||
}
|
||||
else if (isStacked) {
|
||||
x = d.x;
|
||||
y = d.stackY;
|
||||
low = y - d.y;
|
||||
}
|
||||
isNull = y === null;
|
||||
// Optimize for scatter zooming
|
||||
if (!requireSorting) {
|
||||
isYInside = y >= yMin && y <= yMax;
|
||||
}
|
||||
if (!isNull && x >= xMin && x <= xMax && isYInside) {
|
||||
clientX = xAxis.toPixels(x, true);
|
||||
if (sampling) {
|
||||
if (typeof minI === 'undefined' ||
|
||||
clientX === lastClientX) {
|
||||
if (!isRange) {
|
||||
low = y;
|
||||
}
|
||||
if (typeof maxI === 'undefined' ||
|
||||
y > maxVal) {
|
||||
maxVal = y;
|
||||
maxI = i;
|
||||
}
|
||||
if (typeof minI === 'undefined' ||
|
||||
low < minVal) {
|
||||
minVal = low;
|
||||
minI = i;
|
||||
}
|
||||
}
|
||||
// Add points and reset
|
||||
if (clientX !== lastClientX) {
|
||||
// maxI is number too:
|
||||
if (typeof minI !== 'undefined') {
|
||||
plotY =
|
||||
yAxis.toPixels(maxVal, true);
|
||||
yBottom =
|
||||
yAxis.toPixels(minVal, true);
|
||||
addKDPoint(clientX, plotY, maxI);
|
||||
if (yBottom !== plotY) {
|
||||
addKDPoint(clientX, yBottom, minI);
|
||||
}
|
||||
}
|
||||
minI = maxI = void 0;
|
||||
lastClientX = clientX;
|
||||
}
|
||||
}
|
||||
else {
|
||||
plotY = Math.ceil(yAxis.toPixels(y, true));
|
||||
addKDPoint(clientX, plotY, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return !chartDestroyed;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function doneProcessing() {
|
||||
fireEvent(series, 'renderedCanvas');
|
||||
// Go back to prototype, ready to build
|
||||
delete series.buildKDTree;
|
||||
series.buildKDTree();
|
||||
if (boostOptions.debug.timeKDTree) {
|
||||
console.timeEnd('kd tree building'); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
// Loop over the points to build the k-d tree - skip this if
|
||||
// exporting
|
||||
if (!chart.renderer.forExport) {
|
||||
if (boostOptions.debug.timeKDTree) {
|
||||
console.time('kd tree building'); // eslint-disable-line no-console
|
||||
}
|
||||
eachAsync(isStacked ? series.data : (xData || rawData), processPoint, doneProcessing);
|
||||
}
|
||||
}
|
||||
});
|
||||
/*
|
||||
* We need to handle heatmaps separatly, since we can't perform the
|
||||
* size/color calculations in the shader easily.
|
||||
*
|
||||
* This likely needs future optimization.
|
||||
*/
|
||||
['heatmap', 'treemap'].forEach(function (t) {
|
||||
if (seriesTypes[t]) {
|
||||
wrap(seriesTypes[t].prototype, 'drawPoints', pointDrawHandler);
|
||||
}
|
||||
});
|
||||
/* eslint-disable no-invalid-this */
|
||||
if (seriesTypes.bubble) {
|
||||
// By default, the bubble series does not use the KD-tree, so force it
|
||||
// to.
|
||||
delete seriesTypes.bubble.prototype.buildKDTree;
|
||||
// seriesTypes.bubble.prototype.directTouch = false;
|
||||
// Needed for markers to work correctly
|
||||
wrap(seriesTypes.bubble.prototype, 'markerAttribs', function (proceed) {
|
||||
if (this.isSeriesBoosting) {
|
||||
return false;
|
||||
}
|
||||
return proceed.apply(this, [].slice.call(arguments, 1));
|
||||
});
|
||||
}
|
||||
seriesTypes.scatter.prototype.fill = true;
|
||||
extend(seriesTypes.area.prototype, {
|
||||
fill: true,
|
||||
fillOpacity: true,
|
||||
sampling: true
|
||||
});
|
||||
extend(seriesTypes.column.prototype, {
|
||||
fill: true,
|
||||
sampling: true
|
||||
});
|
||||
// Take care of the canvas blitting
|
||||
Chart.prototype.callbacks.push(function (chart) {
|
||||
/**
|
||||
* Convert chart-level canvas to image.
|
||||
* @private
|
||||
*/
|
||||
function canvasToSVG() {
|
||||
if (chart.ogl && chart.isChartSeriesBoosting()) {
|
||||
chart.ogl.render(chart);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clear chart-level canvas.
|
||||
* @private
|
||||
*/
|
||||
function preRender() {
|
||||
// Reset force state
|
||||
chart.boostForceChartBoost = void 0;
|
||||
chart.boostForceChartBoost = shouldForceChartSeriesBoosting(chart);
|
||||
chart.isBoosting = false;
|
||||
if (!chart.isChartSeriesBoosting() && chart.didBoost) {
|
||||
chart.didBoost = false;
|
||||
}
|
||||
// Clear the canvas
|
||||
if (chart.boostClear) {
|
||||
chart.boostClear();
|
||||
}
|
||||
if (chart.canvas && chart.ogl && chart.isChartSeriesBoosting()) {
|
||||
chart.didBoost = true;
|
||||
// Allocate
|
||||
chart.ogl.allocateBuffer(chart);
|
||||
}
|
||||
// see #6518 + #6739
|
||||
if (chart.markerGroup &&
|
||||
chart.xAxis &&
|
||||
chart.xAxis.length > 0 &&
|
||||
chart.yAxis &&
|
||||
chart.yAxis.length > 0) {
|
||||
chart.markerGroup.translate(chart.xAxis[0].pos, chart.yAxis[0].pos);
|
||||
}
|
||||
}
|
||||
addEvent(chart, 'predraw', preRender);
|
||||
addEvent(chart, 'render', canvasToSVG);
|
||||
// addEvent(chart, 'zoom', function () {
|
||||
// chart.boostForceChartBoost =
|
||||
// shouldForceChartSeriesBoosting(chart);
|
||||
// });
|
||||
});
|
||||
/* eslint-enable no-invalid-this */
|
||||
}
|
||||
export default init;
|
||||
@@ -0,0 +1,178 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import '../../Core/Globals.js';
|
||||
/**
|
||||
* @requires modules/boost
|
||||
* @apioption boost
|
||||
*/
|
||||
/**
|
||||
* Set the series threshold for when the boost should kick in globally.
|
||||
*
|
||||
* Setting to e.g. 20 will cause the whole chart to enter boost mode
|
||||
* if there are 20 or more series active. When the chart is in boost mode,
|
||||
* every series in it will be rendered to a common canvas. This offers
|
||||
* a significant speed improvment in charts with a very high
|
||||
* amount of series.
|
||||
*
|
||||
* @type {number|null}
|
||||
* @default null
|
||||
* @apioption boost.seriesThreshold
|
||||
*/
|
||||
/**
|
||||
* Enable or disable boost on a chart.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @apioption boost.enabled
|
||||
*/
|
||||
/**
|
||||
* Debugging options for boost.
|
||||
* Useful for benchmarking, and general timing.
|
||||
*
|
||||
* @apioption boost.debug
|
||||
*/
|
||||
/**
|
||||
* Time the series rendering.
|
||||
*
|
||||
* This outputs the time spent on actual rendering in the console when
|
||||
* set to true.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.debug.timeRendering
|
||||
*/
|
||||
/**
|
||||
* Time the series processing.
|
||||
*
|
||||
* This outputs the time spent on transforming the series data to
|
||||
* vertex buffers when set to true.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.debug.timeSeriesProcessing
|
||||
*/
|
||||
/**
|
||||
* Time the the WebGL setup.
|
||||
*
|
||||
* This outputs the time spent on setting up the WebGL context,
|
||||
* creating shaders, and textures.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.debug.timeSetup
|
||||
*/
|
||||
/**
|
||||
* Time the building of the k-d tree.
|
||||
*
|
||||
* This outputs the time spent building the k-d tree used for
|
||||
* markers etc.
|
||||
*
|
||||
* Note that the k-d tree is built async, and runs post-rendering.
|
||||
* Following, it does not affect the performance of the rendering itself.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.debug.timeKDTree
|
||||
*/
|
||||
/**
|
||||
* Show the number of points skipped through culling.
|
||||
*
|
||||
* When set to true, the number of points skipped in series processing
|
||||
* is outputted. Points are skipped if they are closer than 1 pixel from
|
||||
* each other.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.debug.showSkipSummary
|
||||
*/
|
||||
/**
|
||||
* Time the WebGL to SVG buffer copy
|
||||
*
|
||||
* After rendering, the result is copied to an image which is injected
|
||||
* into the SVG.
|
||||
*
|
||||
* If this property is set to true, the time it takes for the buffer copy
|
||||
* to complete is outputted.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.debug.timeBufferCopy
|
||||
*/
|
||||
/**
|
||||
* Enable or disable GPU translations. GPU translations are faster than doing
|
||||
* the translation in JavaScript.
|
||||
*
|
||||
* This option may cause rendering issues with certain datasets.
|
||||
* Namely, if your dataset has large numbers with small increments (such as
|
||||
* timestamps), it won't work correctly. This is due to floating point
|
||||
* precission.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.useGPUTranslations
|
||||
*/
|
||||
/**
|
||||
* Enable or disable pre-allocation of vertex buffers.
|
||||
*
|
||||
* Enabling this will make it so that the binary data arrays required for
|
||||
* storing the series data will be allocated prior to transforming the data
|
||||
* to a WebGL-compatible format.
|
||||
*
|
||||
* This saves a copy operation on the order of O(n) and so is significantly more
|
||||
* performant. However, this is currently an experimental option, and may cause
|
||||
* visual artifacts with some datasets.
|
||||
*
|
||||
* As such, care should be taken when using this setting to make sure that
|
||||
* it doesn't cause any rendering glitches with the given use-case.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @apioption boost.usePreallocated
|
||||
*/
|
||||
/**
|
||||
* Set the point threshold for when a series should enter boost mode.
|
||||
*
|
||||
* Setting it to e.g. 2000 will cause the series to enter boost mode when there
|
||||
* are 2000 or more points in the series.
|
||||
*
|
||||
* To disable boosting on the series, set the `boostThreshold` to 0. Setting it
|
||||
* to 1 will force boosting.
|
||||
*
|
||||
* Note that the [cropThreshold](plotOptions.series.cropThreshold) also affects
|
||||
* this setting. When zooming in on a series that has fewer points than the
|
||||
* `cropThreshold`, all points are rendered although outside the visible plot
|
||||
* area, and the `boostThreshold` won't take effect.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
* @requires modules/boost
|
||||
* @apioption plotOptions.series.boostThreshold
|
||||
*/
|
||||
/**
|
||||
* If set to true, the whole chart will be boosted if one of the series
|
||||
* crosses its threshold, and all the series can be boosted.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @apioption boost.allowForce
|
||||
*/
|
||||
/**
|
||||
* Sets the color blending in the boost module.
|
||||
*
|
||||
* @type {string}
|
||||
* @default undefined
|
||||
* @validvalue ["add", "multiply", "darken"]
|
||||
* @requires modules/boost
|
||||
* @apioption plotOptions.series.boostBlending
|
||||
*/
|
||||
''; // adds doclets above to transpiled file
|
||||
@@ -0,0 +1,368 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from '../../Core/Chart/Chart.js';
|
||||
import H from '../../Core/Globals.js';
|
||||
import Point from '../../Core/Series/Point.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, error = U.error, getOptions = U.getOptions, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick, wrap = U.wrap;
|
||||
import '../../Core/Series/Series.js';
|
||||
import '../../Core/Options.js';
|
||||
import '../../Core/Interaction.js';
|
||||
import butils from './BoostUtils.js';
|
||||
import boostable from './Boostables.js';
|
||||
import boostableMap from './BoostableMap.js';
|
||||
var boostEnabled = butils.boostEnabled, shouldForceChartSeriesBoosting = butils.shouldForceChartSeriesBoosting, Series = H.Series, seriesTypes = H.seriesTypes, plotOptions = getOptions().plotOptions;
|
||||
/**
|
||||
* Returns true if the chart is in series boost mode.
|
||||
*
|
||||
* @function Highcharts.Chart#isChartSeriesBoosting
|
||||
*
|
||||
* @param {Highcharts.Chart} chart
|
||||
* the chart to check
|
||||
*
|
||||
* @return {boolean}
|
||||
* true if the chart is in series boost mode
|
||||
*/
|
||||
Chart.prototype.isChartSeriesBoosting = function () {
|
||||
var isSeriesBoosting, threshold = pick(this.options.boost && this.options.boost.seriesThreshold, 50);
|
||||
isSeriesBoosting = threshold <= this.series.length ||
|
||||
shouldForceChartSeriesBoosting(this);
|
||||
return isSeriesBoosting;
|
||||
};
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Get the clip rectangle for a target, either a series or the chart. For the
|
||||
* chart, we need to consider the maximum extent of its Y axes, in case of
|
||||
* Highstock panes and navigator.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#getBoostClipRect
|
||||
*
|
||||
* @param {Highcharts.Chart} target
|
||||
*
|
||||
* @return {Highcharts.BBoxObject}
|
||||
*/
|
||||
Chart.prototype.getBoostClipRect = function (target) {
|
||||
var clipBox = {
|
||||
x: this.plotLeft,
|
||||
y: this.plotTop,
|
||||
width: this.plotWidth,
|
||||
height: this.plotHeight
|
||||
};
|
||||
if (target === this) {
|
||||
this.yAxis.forEach(function (yAxis) {
|
||||
clipBox.y = Math.min(yAxis.pos, clipBox.y);
|
||||
clipBox.height = Math.max(yAxis.pos - this.plotTop + yAxis.len, clipBox.height);
|
||||
}, this);
|
||||
}
|
||||
return clipBox;
|
||||
};
|
||||
/**
|
||||
* Return a full Point object based on the index.
|
||||
* The boost module uses stripped point objects for performance reasons.
|
||||
*
|
||||
* @function Highcharts.Series#getPoint
|
||||
*
|
||||
* @param {object|Highcharts.Point} boostPoint
|
||||
* A stripped-down point object
|
||||
*
|
||||
* @return {Highcharts.Point}
|
||||
* A Point object as per https://api.highcharts.com/highcharts#Point
|
||||
*/
|
||||
Series.prototype.getPoint = function (boostPoint) {
|
||||
var point = boostPoint, xData = (this.xData || this.options.xData || this.processedXData ||
|
||||
false);
|
||||
if (boostPoint && !(boostPoint instanceof this.pointClass)) {
|
||||
point = (new this.pointClass()).init(// eslint-disable-line new-cap
|
||||
this, this.options.data[boostPoint.i], xData ? xData[boostPoint.i] : void 0);
|
||||
point.category = pick(this.xAxis.categories ?
|
||||
this.xAxis.categories[point.x] :
|
||||
point.x, // @todo simplify
|
||||
point.x);
|
||||
point.dist = boostPoint.dist;
|
||||
point.distX = boostPoint.distX;
|
||||
point.plotX = boostPoint.plotX;
|
||||
point.plotY = boostPoint.plotY;
|
||||
point.index = boostPoint.i;
|
||||
point.isInside = this.isPointInside(boostPoint);
|
||||
}
|
||||
return point;
|
||||
};
|
||||
/* eslint-disable no-invalid-this */
|
||||
// Return a point instance from the k-d-tree
|
||||
wrap(Series.prototype, 'searchPoint', function (proceed) {
|
||||
return this.getPoint(proceed.apply(this, [].slice.call(arguments, 1)));
|
||||
});
|
||||
// For inverted series, we need to swap X-Y values before running base methods
|
||||
wrap(Point.prototype, 'haloPath', function (proceed) {
|
||||
var halo, point = this, series = point.series, chart = series.chart, plotX = point.plotX, plotY = point.plotY, inverted = chart.inverted;
|
||||
if (series.isSeriesBoosting && inverted) {
|
||||
point.plotX = series.yAxis.len - plotY;
|
||||
point.plotY = series.xAxis.len - plotX;
|
||||
}
|
||||
halo = proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
if (series.isSeriesBoosting && inverted) {
|
||||
point.plotX = plotX;
|
||||
point.plotY = plotY;
|
||||
}
|
||||
return halo;
|
||||
});
|
||||
wrap(Series.prototype, 'markerAttribs', function (proceed, point) {
|
||||
var attribs, series = this, chart = series.chart, plotX = point.plotX, plotY = point.plotY, inverted = chart.inverted;
|
||||
if (series.isSeriesBoosting && inverted) {
|
||||
point.plotX = series.yAxis.len - plotY;
|
||||
point.plotY = series.xAxis.len - plotX;
|
||||
}
|
||||
attribs = proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
if (series.isSeriesBoosting && inverted) {
|
||||
point.plotX = plotX;
|
||||
point.plotY = plotY;
|
||||
}
|
||||
return attribs;
|
||||
});
|
||||
/*
|
||||
* Extend series.destroy to also remove the fake k-d-tree points (#5137).
|
||||
* Normally this is handled by Series.destroy that calls Point.destroy,
|
||||
* but the fake search points are not registered like that.
|
||||
*/
|
||||
addEvent(Series, 'destroy', function () {
|
||||
var series = this, chart = series.chart;
|
||||
if (chart.markerGroup === series.markerGroup) {
|
||||
series.markerGroup = null;
|
||||
}
|
||||
if (chart.hoverPoints) {
|
||||
chart.hoverPoints = chart.hoverPoints.filter(function (point) {
|
||||
return point.series === series;
|
||||
});
|
||||
}
|
||||
if (chart.hoverPoint && chart.hoverPoint.series === series) {
|
||||
chart.hoverPoint = null;
|
||||
}
|
||||
});
|
||||
/*
|
||||
* Do not compute extremes when min and max are set.
|
||||
* If we use this in the core, we can add the hook
|
||||
* to hasExtremes to the methods directly.
|
||||
*/
|
||||
wrap(Series.prototype, 'getExtremes', function (proceed) {
|
||||
if (!this.isSeriesBoosting || (!this.hasExtremes || !this.hasExtremes())) {
|
||||
return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
return {};
|
||||
});
|
||||
/*
|
||||
* Override a bunch of methods the same way. If the number of points is
|
||||
* below the threshold, run the original method. If not, check for a
|
||||
* canvas version or do nothing.
|
||||
*
|
||||
* Note that we're not overriding any of these for heatmaps.
|
||||
*/
|
||||
[
|
||||
'translate',
|
||||
'generatePoints',
|
||||
'drawTracker',
|
||||
'drawPoints',
|
||||
'render'
|
||||
].forEach(function (method) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function branch(proceed) {
|
||||
var letItPass = this.options.stacking &&
|
||||
(method === 'translate' || method === 'generatePoints');
|
||||
if (!this.isSeriesBoosting ||
|
||||
letItPass ||
|
||||
!boostEnabled(this.chart) ||
|
||||
this.type === 'heatmap' ||
|
||||
this.type === 'treemap' ||
|
||||
!boostableMap[this.type] ||
|
||||
this.options.boostThreshold === 0) {
|
||||
proceed.call(this);
|
||||
// If a canvas version of the method exists, like renderCanvas(), run
|
||||
}
|
||||
else if (this[method + 'Canvas']) {
|
||||
this[method + 'Canvas']();
|
||||
}
|
||||
}
|
||||
wrap(Series.prototype, method, branch);
|
||||
// A special case for some types - their translate method is already wrapped
|
||||
if (method === 'translate') {
|
||||
[
|
||||
'column',
|
||||
'bar',
|
||||
'arearange',
|
||||
'columnrange',
|
||||
'heatmap',
|
||||
'treemap'
|
||||
].forEach(function (type) {
|
||||
if (seriesTypes[type]) {
|
||||
wrap(seriesTypes[type].prototype, method, branch);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// If the series is a heatmap or treemap, or if the series is not boosting
|
||||
// do the default behaviour. Otherwise, process if the series has no extremes.
|
||||
wrap(Series.prototype, 'processData', function (proceed) {
|
||||
var series = this, dataToMeasure = this.options.data, firstPoint;
|
||||
/**
|
||||
* Used twice in this function, first on this.options.data, the second
|
||||
* time it runs the check again after processedXData is built.
|
||||
* @private
|
||||
* @todo Check what happens with data grouping
|
||||
*/
|
||||
function getSeriesBoosting(data) {
|
||||
return series.chart.isChartSeriesBoosting() || ((data ? data.length : 0) >=
|
||||
(series.options.boostThreshold || Number.MAX_VALUE));
|
||||
}
|
||||
if (boostEnabled(this.chart) && boostableMap[this.type]) {
|
||||
// If there are no extremes given in the options, we also need to
|
||||
// process the data to read the data extremes. If this is a heatmap, do
|
||||
// default behaviour.
|
||||
if (!getSeriesBoosting(dataToMeasure) || // First pass with options.data
|
||||
this.type === 'heatmap' ||
|
||||
this.type === 'treemap' ||
|
||||
this.options.stacking || // processedYData for the stack (#7481)
|
||||
!this.hasExtremes ||
|
||||
!this.hasExtremes(true)) {
|
||||
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
dataToMeasure = this.processedXData;
|
||||
}
|
||||
// Set the isBoosting flag, second pass with processedXData to see if we
|
||||
// have zoomed.
|
||||
this.isSeriesBoosting = getSeriesBoosting(dataToMeasure);
|
||||
// Enter or exit boost mode
|
||||
if (this.isSeriesBoosting) {
|
||||
// Force turbo-mode:
|
||||
firstPoint = this.getFirstValidPoint(this.options.data);
|
||||
if (!isNumber(firstPoint) && !isArray(firstPoint)) {
|
||||
error(12, false, this.chart);
|
||||
}
|
||||
this.enterBoost();
|
||||
}
|
||||
else if (this.exitBoost) {
|
||||
this.exitBoost();
|
||||
}
|
||||
// The series type is not boostable
|
||||
}
|
||||
else {
|
||||
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
});
|
||||
addEvent(Series, 'hide', function () {
|
||||
if (this.canvas && this.renderTarget) {
|
||||
if (this.ogl) {
|
||||
this.ogl.clear();
|
||||
}
|
||||
this.boostClear();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Enter boost mode and apply boost-specific properties.
|
||||
*
|
||||
* @function Highcharts.Series#enterBoost
|
||||
*/
|
||||
Series.prototype.enterBoost = function () {
|
||||
this.alteredByBoost = [];
|
||||
// Save the original values, including whether it was an own property or
|
||||
// inherited from the prototype.
|
||||
['allowDG', 'directTouch', 'stickyTracking'].forEach(function (prop) {
|
||||
this.alteredByBoost.push({
|
||||
prop: prop,
|
||||
val: this[prop],
|
||||
own: Object.hasOwnProperty.call(this, prop)
|
||||
});
|
||||
}, this);
|
||||
this.allowDG = false;
|
||||
this.directTouch = false;
|
||||
this.stickyTracking = true;
|
||||
// Prevent animation when zooming in on boosted series(#13421).
|
||||
this.finishedAnimating = true;
|
||||
// Hide series label if any
|
||||
if (this.labelBySeries) {
|
||||
this.labelBySeries = this.labelBySeries.destroy();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Exit from boost mode and restore non-boost properties.
|
||||
*
|
||||
* @function Highcharts.Series#exitBoost
|
||||
*/
|
||||
Series.prototype.exitBoost = function () {
|
||||
// Reset instance properties and/or delete instance properties and go back
|
||||
// to prototype
|
||||
(this.alteredByBoost || []).forEach(function (setting) {
|
||||
if (setting.own) {
|
||||
this[setting.prop] = setting.val;
|
||||
}
|
||||
else {
|
||||
// Revert to prototype
|
||||
delete this[setting.prop];
|
||||
}
|
||||
}, this);
|
||||
// Clear previous run
|
||||
if (this.boostClear) {
|
||||
this.boostClear();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Series#hasExtremes
|
||||
*
|
||||
* @param {boolean} checkX
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
Series.prototype.hasExtremes = function (checkX) {
|
||||
var options = this.options, data = options.data, xAxis = this.xAxis && this.xAxis.options, yAxis = this.yAxis && this.yAxis.options, colorAxis = this.colorAxis && this.colorAxis.options;
|
||||
return data.length > (options.boostThreshold || Number.MAX_VALUE) &&
|
||||
// Defined yAxis extremes
|
||||
isNumber(yAxis.min) &&
|
||||
isNumber(yAxis.max) &&
|
||||
// Defined (and required) xAxis extremes
|
||||
(!checkX ||
|
||||
(isNumber(xAxis.min) && isNumber(xAxis.max))) &&
|
||||
// Defined (e.g. heatmap) colorAxis extremes
|
||||
(!colorAxis ||
|
||||
(isNumber(colorAxis.min) && isNumber(colorAxis.max)));
|
||||
};
|
||||
/**
|
||||
* If implemented in the core, parts of this can probably be
|
||||
* shared with other similar methods in Highcharts.
|
||||
*
|
||||
* @function Highcharts.Series#destroyGraphics
|
||||
*/
|
||||
Series.prototype.destroyGraphics = function () {
|
||||
var series = this, points = this.points, point, i;
|
||||
if (points) {
|
||||
for (i = 0; i < points.length; i = i + 1) {
|
||||
point = points[i];
|
||||
if (point && point.destroyElements) {
|
||||
point.destroyElements(); // #7557
|
||||
}
|
||||
}
|
||||
}
|
||||
['graph', 'area', 'tracker'].forEach(function (prop) {
|
||||
if (series[prop]) {
|
||||
series[prop] = series[prop].destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
// Set default options
|
||||
boostable.forEach(function (type) {
|
||||
if (plotOptions[type]) {
|
||||
plotOptions[type].boostThreshold = 5000;
|
||||
plotOptions[type].boostData = [];
|
||||
seriesTypes[type].prototype.fillOpacity = true;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,262 @@
|
||||
/* *
|
||||
*
|
||||
* 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 '../../Core/Globals.js';
|
||||
var win = H.win, doc = H.doc;
|
||||
import '../../Core/Series/Series.js';
|
||||
import boostableMap from './BoostableMap.js';
|
||||
import createAndAttachRenderer from './BoostAttach.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var pick = U.pick;
|
||||
// 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;
|
||||
@@ -0,0 +1,18 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
import boostables from './Boostables.js';
|
||||
// These are the series we allow boosting for.
|
||||
var boostableMap = {};
|
||||
boostables.forEach(function (item) {
|
||||
boostableMap[item] = 1;
|
||||
});
|
||||
export default boostableMap;
|
||||
@@ -0,0 +1,25 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
// These are the series we allow boosting for.
|
||||
var boostables = [
|
||||
'area',
|
||||
'arearange',
|
||||
'column',
|
||||
'columnrange',
|
||||
'bar',
|
||||
'line',
|
||||
'scatter',
|
||||
'heatmap',
|
||||
'bubble',
|
||||
'treemap'
|
||||
];
|
||||
export default boostables;
|
||||
@@ -0,0 +1,162 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Color from '../../Core/Color.js';
|
||||
// Register color names since GL can't render those directly.
|
||||
// TODO: When supporting modern syntax, make this a const and a named export
|
||||
var defaultHTMLColorMap = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
feldspar: '#d19275',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslateblue: '#8470ff',
|
||||
lightslategray: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370d8',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#d87093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
violetred: '#d02090',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32'
|
||||
};
|
||||
Color.names = defaultHTMLColorMap;
|
||||
export default defaultHTMLColorMap;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,469 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var clamp = U.clamp, error = U.error, pick = U.pick;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* A static shader mimicing axis translation functions found in Core/Axis
|
||||
*
|
||||
* @private
|
||||
* @function GLShader
|
||||
*
|
||||
* @param {WebGLContext} gl
|
||||
* the context in which the shader is active
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
function GLShader(gl) {
|
||||
var vertShade = [
|
||||
/* eslint-disable max-len, @typescript-eslint/indent */
|
||||
'#version 100',
|
||||
'#define LN10 2.302585092994046',
|
||||
'precision highp float;',
|
||||
'attribute vec4 aVertexPosition;',
|
||||
'attribute vec4 aColor;',
|
||||
'varying highp vec2 position;',
|
||||
'varying highp vec4 vColor;',
|
||||
'uniform mat4 uPMatrix;',
|
||||
'uniform float pSize;',
|
||||
'uniform float translatedThreshold;',
|
||||
'uniform bool hasThreshold;',
|
||||
'uniform bool skipTranslation;',
|
||||
'uniform float xAxisTrans;',
|
||||
'uniform float xAxisMin;',
|
||||
'uniform float xAxisMinPad;',
|
||||
'uniform float xAxisPointRange;',
|
||||
'uniform float xAxisLen;',
|
||||
'uniform bool xAxisPostTranslate;',
|
||||
'uniform float xAxisOrdinalSlope;',
|
||||
'uniform float xAxisOrdinalOffset;',
|
||||
'uniform float xAxisPos;',
|
||||
'uniform bool xAxisCVSCoord;',
|
||||
'uniform bool xAxisIsLog;',
|
||||
'uniform bool xAxisReversed;',
|
||||
'uniform float yAxisTrans;',
|
||||
'uniform float yAxisMin;',
|
||||
'uniform float yAxisMinPad;',
|
||||
'uniform float yAxisPointRange;',
|
||||
'uniform float yAxisLen;',
|
||||
'uniform bool yAxisPostTranslate;',
|
||||
'uniform float yAxisOrdinalSlope;',
|
||||
'uniform float yAxisOrdinalOffset;',
|
||||
'uniform float yAxisPos;',
|
||||
'uniform bool yAxisCVSCoord;',
|
||||
'uniform bool yAxisIsLog;',
|
||||
'uniform bool yAxisReversed;',
|
||||
'uniform bool isBubble;',
|
||||
'uniform bool bubbleSizeByArea;',
|
||||
'uniform float bubbleZMin;',
|
||||
'uniform float bubbleZMax;',
|
||||
'uniform float bubbleZThreshold;',
|
||||
'uniform float bubbleMinSize;',
|
||||
'uniform float bubbleMaxSize;',
|
||||
'uniform bool bubbleSizeAbs;',
|
||||
'uniform bool isInverted;',
|
||||
'float bubbleRadius(){',
|
||||
'float value = aVertexPosition.w;',
|
||||
'float zMax = bubbleZMax;',
|
||||
'float zMin = bubbleZMin;',
|
||||
'float radius = 0.0;',
|
||||
'float pos = 0.0;',
|
||||
'float zRange = zMax - zMin;',
|
||||
'if (bubbleSizeAbs){',
|
||||
'value = value - bubbleZThreshold;',
|
||||
'zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);',
|
||||
'zMin = 0.0;',
|
||||
'}',
|
||||
'if (value < zMin){',
|
||||
'radius = bubbleZMin / 2.0 - 1.0;',
|
||||
'} else {',
|
||||
'pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;',
|
||||
'if (bubbleSizeByArea && pos > 0.0){',
|
||||
'pos = sqrt(pos);',
|
||||
'}',
|
||||
'radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;',
|
||||
'}',
|
||||
'return radius * 2.0;',
|
||||
'}',
|
||||
'float translate(float val,',
|
||||
'float pointPlacement,',
|
||||
'float localA,',
|
||||
'float localMin,',
|
||||
'float minPixelPadding,',
|
||||
'float pointRange,',
|
||||
'float len,',
|
||||
'bool cvsCoord,',
|
||||
'bool isLog,',
|
||||
'bool reversed',
|
||||
'){',
|
||||
'float sign = 1.0;',
|
||||
'float cvsOffset = 0.0;',
|
||||
'if (cvsCoord) {',
|
||||
'sign *= -1.0;',
|
||||
'cvsOffset = len;',
|
||||
'}',
|
||||
'if (isLog) {',
|
||||
'val = log(val) / LN10;',
|
||||
'}',
|
||||
'if (reversed) {',
|
||||
'sign *= -1.0;',
|
||||
'cvsOffset -= sign * len;',
|
||||
'}',
|
||||
'return sign * (val - localMin) * localA + cvsOffset + ',
|
||||
'(sign * minPixelPadding);',
|
||||
'}',
|
||||
'float xToPixels(float value) {',
|
||||
'if (skipTranslation){',
|
||||
'return value;// + xAxisPos;',
|
||||
'}',
|
||||
'return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord, xAxisIsLog, xAxisReversed);// + xAxisPos;',
|
||||
'}',
|
||||
'float yToPixels(float value, float checkTreshold) {',
|
||||
'float v;',
|
||||
'if (skipTranslation){',
|
||||
'v = value;// + yAxisPos;',
|
||||
'} else {',
|
||||
'v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord, yAxisIsLog, yAxisReversed);// + yAxisPos;',
|
||||
'if (v > yAxisLen) {',
|
||||
'v = yAxisLen;',
|
||||
'}',
|
||||
'}',
|
||||
'if (checkTreshold > 0.0 && hasThreshold) {',
|
||||
'v = min(v, translatedThreshold);',
|
||||
'}',
|
||||
'return v;',
|
||||
'}',
|
||||
'void main(void) {',
|
||||
'if (isBubble){',
|
||||
'gl_PointSize = bubbleRadius();',
|
||||
'} else {',
|
||||
'gl_PointSize = pSize;',
|
||||
'}',
|
||||
// 'gl_PointSize = 10.0;',
|
||||
'vColor = aColor;',
|
||||
'if (skipTranslation && isInverted) {',
|
||||
// If we get translated values from JS, just swap them (x, y)
|
||||
'gl_Position = uPMatrix * vec4(aVertexPosition.y + yAxisPos, aVertexPosition.x + xAxisPos, 0.0, 1.0);',
|
||||
'} else if (isInverted) {',
|
||||
// But when calculating pixel positions directly,
|
||||
// swap axes and values (x, y)
|
||||
'gl_Position = uPMatrix * vec4(yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, xToPixels(aVertexPosition.x) + xAxisPos, 0.0, 1.0);',
|
||||
'} else {',
|
||||
'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);',
|
||||
'}',
|
||||
// 'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
|
||||
'}'
|
||||
/* eslint-enable max-len, @typescript-eslint/indent */
|
||||
].join('\n'),
|
||||
// Fragment shader source
|
||||
fragShade = [
|
||||
/* eslint-disable max-len, @typescript-eslint/indent */
|
||||
'precision highp float;',
|
||||
'uniform vec4 fillColor;',
|
||||
'varying highp vec2 position;',
|
||||
'varying highp vec4 vColor;',
|
||||
'uniform sampler2D uSampler;',
|
||||
'uniform bool isCircle;',
|
||||
'uniform bool hasColor;',
|
||||
// 'vec4 toColor(float value, vec2 point) {',
|
||||
// 'return vec4(0.0, 0.0, 0.0, 0.0);',
|
||||
// '}',
|
||||
'void main(void) {',
|
||||
'vec4 col = fillColor;',
|
||||
'vec4 tcol;',
|
||||
'if (hasColor) {',
|
||||
'col = vColor;',
|
||||
'}',
|
||||
'if (isCircle) {',
|
||||
'tcol = texture2D(uSampler, gl_PointCoord.st);',
|
||||
'col *= tcol;',
|
||||
'if (tcol.r < 0.0) {',
|
||||
'discard;',
|
||||
'} else {',
|
||||
'gl_FragColor = col;',
|
||||
'}',
|
||||
'} else {',
|
||||
'gl_FragColor = col;',
|
||||
'}',
|
||||
'}'
|
||||
/* eslint-enable max-len, @typescript-eslint/indent */
|
||||
].join('\n'), uLocations = {},
|
||||
// The shader program
|
||||
shaderProgram,
|
||||
// Uniform handle to the perspective matrix
|
||||
pUniform,
|
||||
// Uniform for point size
|
||||
psUniform,
|
||||
// Uniform for fill color
|
||||
fillColorUniform,
|
||||
// Uniform for isBubble
|
||||
isBubbleUniform,
|
||||
// Uniform for bubble abs sizing
|
||||
bubbleSizeAbsUniform, bubbleSizeAreaUniform,
|
||||
// Skip translation uniform
|
||||
skipTranslationUniform,
|
||||
// Set to 1 if circle
|
||||
isCircleUniform,
|
||||
// Uniform for invertion
|
||||
isInverted,
|
||||
// Error stack
|
||||
errors = [],
|
||||
// Texture uniform
|
||||
uSamplerUniform;
|
||||
/**
|
||||
* Handle errors accumulated in errors stack
|
||||
* @private
|
||||
*/
|
||||
function handleErrors() {
|
||||
if (errors.length) {
|
||||
error('[highcharts boost] shader error - ' + errors.join('\n'));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* String to shader program
|
||||
* @private
|
||||
* @param {string} str - the program source
|
||||
* @param {string} type - the program type: either `vertex` or `fragment`
|
||||
* @returns {bool|shader}
|
||||
*/
|
||||
function stringToProgram(str, type) {
|
||||
var t = type === 'vertex' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER, shader = gl.createShader(t);
|
||||
gl.shaderSource(shader, str);
|
||||
gl.compileShader(shader);
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
errors.push('when compiling ' +
|
||||
type +
|
||||
' shader:\n' +
|
||||
gl.getShaderInfoLog(shader));
|
||||
return false;
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
/**
|
||||
* Create the shader.
|
||||
* Loads the shader program statically defined above
|
||||
* @private
|
||||
*/
|
||||
function createShader() {
|
||||
var v = stringToProgram(vertShade, 'vertex'), f = stringToProgram(fragShade, 'fragment');
|
||||
if (!v || !f) {
|
||||
shaderProgram = false;
|
||||
handleErrors();
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function uloc(n) {
|
||||
return gl.getUniformLocation(shaderProgram, n);
|
||||
}
|
||||
shaderProgram = gl.createProgram();
|
||||
gl.attachShader(shaderProgram, v);
|
||||
gl.attachShader(shaderProgram, f);
|
||||
gl.linkProgram(shaderProgram);
|
||||
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||
errors.push(gl.getProgramInfoLog(shaderProgram));
|
||||
handleErrors();
|
||||
shaderProgram = false;
|
||||
return false;
|
||||
}
|
||||
gl.useProgram(shaderProgram);
|
||||
gl.bindAttribLocation(shaderProgram, 0, 'aVertexPosition');
|
||||
pUniform = uloc('uPMatrix');
|
||||
psUniform = uloc('pSize');
|
||||
fillColorUniform = uloc('fillColor');
|
||||
isBubbleUniform = uloc('isBubble');
|
||||
bubbleSizeAbsUniform = uloc('bubbleSizeAbs');
|
||||
bubbleSizeAreaUniform = uloc('bubbleSizeByArea');
|
||||
uSamplerUniform = uloc('uSampler');
|
||||
skipTranslationUniform = uloc('skipTranslation');
|
||||
isCircleUniform = uloc('isCircle');
|
||||
isInverted = uloc('isInverted');
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Destroy the shader
|
||||
* @private
|
||||
*/
|
||||
function destroy() {
|
||||
if (gl && shaderProgram) {
|
||||
gl.deleteProgram(shaderProgram);
|
||||
shaderProgram = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Bind the shader.
|
||||
* This makes the shader the active one until another one is bound,
|
||||
* or until 0 is bound.
|
||||
* @private
|
||||
*/
|
||||
function bind() {
|
||||
if (gl && shaderProgram) {
|
||||
gl.useProgram(shaderProgram);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set a uniform value.
|
||||
* This uses a hash map to cache uniform locations.
|
||||
* @private
|
||||
* @param name {string} - the name of the uniform to set
|
||||
* @param val {float} - the value to set
|
||||
*/
|
||||
function setUniform(name, val) {
|
||||
if (gl && shaderProgram) {
|
||||
var u = uLocations[name] = (uLocations[name] ||
|
||||
gl.getUniformLocation(shaderProgram, name));
|
||||
gl.uniform1f(u, val);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the active texture
|
||||
* @private
|
||||
* @param texture - the texture
|
||||
*/
|
||||
function setTexture(texture) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform1i(uSamplerUniform, texture);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set if inversion state
|
||||
* @private
|
||||
* @flag is the state
|
||||
*/
|
||||
function setInverted(flag) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform1i(isInverted, flag);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Enable/disable circle drawing
|
||||
* @private
|
||||
*/
|
||||
function setDrawAsCircle(flag) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform1i(isCircleUniform, flag ? 1 : 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Flush
|
||||
* @private
|
||||
*/
|
||||
function reset() {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform1i(isBubbleUniform, 0);
|
||||
gl.uniform1i(isCircleUniform, 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set bubble uniforms
|
||||
* @private
|
||||
* @param series {Highcharts.Series} - the series to use
|
||||
*/
|
||||
function setBubbleUniforms(series, zCalcMin, zCalcMax) {
|
||||
var seriesOptions = series.options, zMin = Number.MAX_VALUE, zMax = -Number.MAX_VALUE;
|
||||
if (gl && shaderProgram && series.type === 'bubble') {
|
||||
zMin = pick(seriesOptions.zMin, clamp(zCalcMin, seriesOptions.displayNegative === false ?
|
||||
seriesOptions.zThreshold : -Number.MAX_VALUE, zMin));
|
||||
zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
|
||||
gl.uniform1i(isBubbleUniform, 1);
|
||||
gl.uniform1i(isCircleUniform, 1);
|
||||
gl.uniform1i(bubbleSizeAreaUniform, (series.options.sizeBy !== 'width'));
|
||||
gl.uniform1i(bubbleSizeAbsUniform, series.options
|
||||
.sizeByAbsoluteValue);
|
||||
setUniform('bubbleZMin', zMin);
|
||||
setUniform('bubbleZMax', zMax);
|
||||
setUniform('bubbleZThreshold', series.options.zThreshold);
|
||||
setUniform('bubbleMinSize', series.minPxSize);
|
||||
setUniform('bubbleMaxSize', series.maxPxSize);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the Color uniform.
|
||||
* @private
|
||||
* @param color {Array<float>} - an array with RGBA values
|
||||
*/
|
||||
function setColor(color) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform4f(fillColorUniform, color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set skip translation
|
||||
* @private
|
||||
*/
|
||||
function setSkipTranslation(flag) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the perspective matrix
|
||||
* @private
|
||||
* @param m {Matrix4x4} - the matrix
|
||||
*/
|
||||
function setPMatrix(m) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniformMatrix4fv(pUniform, false, m);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the point size.
|
||||
* @private
|
||||
* @param p {float} - point size
|
||||
*/
|
||||
function setPointSize(p) {
|
||||
if (gl && shaderProgram) {
|
||||
gl.uniform1f(psUniform, p);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the shader program handle
|
||||
* @private
|
||||
* @return {GLInt} - the handle for the program
|
||||
*/
|
||||
function getProgram() {
|
||||
return shaderProgram;
|
||||
}
|
||||
if (gl) {
|
||||
if (!createShader()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return {
|
||||
psUniform: function () {
|
||||
return psUniform;
|
||||
},
|
||||
pUniform: function () {
|
||||
return pUniform;
|
||||
},
|
||||
fillColorUniform: function () {
|
||||
return fillColorUniform;
|
||||
},
|
||||
setBubbleUniforms: setBubbleUniforms,
|
||||
bind: bind,
|
||||
program: getProgram,
|
||||
create: createShader,
|
||||
setUniform: setUniform,
|
||||
setPMatrix: setPMatrix,
|
||||
setColor: setColor,
|
||||
setPointSize: setPointSize,
|
||||
setSkipTranslation: setSkipTranslation,
|
||||
setTexture: setTexture,
|
||||
setDrawAsCircle: setDrawAsCircle,
|
||||
reset: reset,
|
||||
setInverted: setInverted,
|
||||
destroy: destroy
|
||||
};
|
||||
}
|
||||
export default GLShader;
|
||||
@@ -0,0 +1,153 @@
|
||||
/* *
|
||||
*
|
||||
* Copyright (c) 2019-2020 Highsoft AS
|
||||
*
|
||||
* Boost module: stripped-down renderer for higher performance
|
||||
*
|
||||
* License: highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Vertex Buffer abstraction.
|
||||
* A vertex buffer is a set of vertices which are passed to the GPU
|
||||
* in a single call.
|
||||
*
|
||||
* @private
|
||||
* @function GLVertexBuffer
|
||||
*
|
||||
* @param {WebGLContext} gl
|
||||
* the context in which to create the buffer
|
||||
*
|
||||
* @param {GLShader} shader
|
||||
* the shader to use
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
function GLVertexBuffer(gl, shader, dataComponents
|
||||
/* , type */
|
||||
) {
|
||||
var buffer = false, vertAttribute = false, components = dataComponents || 2, preAllocated = false, iterator = 0,
|
||||
// farray = false,
|
||||
data;
|
||||
// type = type || 'float';
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function destroy() {
|
||||
if (buffer) {
|
||||
gl.deleteBuffer(buffer);
|
||||
buffer = false;
|
||||
vertAttribute = false;
|
||||
}
|
||||
iterator = 0;
|
||||
components = dataComponents || 2;
|
||||
data = [];
|
||||
}
|
||||
/**
|
||||
* Build the buffer
|
||||
* @private
|
||||
* @param dataIn {Array<float>} - a 0 padded array of indices
|
||||
* @param attrib {String} - the name of the Attribute to bind the buffer to
|
||||
* @param dataComponents {Integer} - the number of components per. indice
|
||||
*/
|
||||
function build(dataIn, attrib, dataComponents) {
|
||||
var farray;
|
||||
data = dataIn || [];
|
||||
if ((!data || data.length === 0) && !preAllocated) {
|
||||
// console.error('trying to render empty vbuffer');
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
components = dataComponents || components;
|
||||
if (buffer) {
|
||||
gl.deleteBuffer(buffer);
|
||||
}
|
||||
if (!preAllocated) {
|
||||
farray = new Float32Array(data);
|
||||
}
|
||||
buffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, preAllocated || farray, gl.STATIC_DRAW);
|
||||
// gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
|
||||
vertAttribute = gl.getAttribLocation(shader.program(), attrib);
|
||||
gl.enableVertexAttribArray(vertAttribute);
|
||||
// Trigger cleanup
|
||||
farray = false;
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Bind the buffer
|
||||
* @private
|
||||
*/
|
||||
function bind() {
|
||||
if (!buffer) {
|
||||
return false;
|
||||
}
|
||||
// gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
|
||||
// gl.enableVertexAttribArray(vertAttribute);
|
||||
// gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.vertexAttribPointer(vertAttribute, components, gl.FLOAT, false, 0, 0);
|
||||
// gl.enableVertexAttribArray(vertAttribute);
|
||||
}
|
||||
/**
|
||||
* Render the buffer
|
||||
* @private
|
||||
* @param from {Integer} - the start indice
|
||||
* @param to {Integer} - the end indice
|
||||
* @param drawMode {String} - the draw mode
|
||||
*/
|
||||
function render(from, to, drawMode) {
|
||||
var length = preAllocated ? preAllocated.length : data.length;
|
||||
if (!buffer) {
|
||||
return false;
|
||||
}
|
||||
if (!length) {
|
||||
return false;
|
||||
}
|
||||
if (!from || from > length || from < 0) {
|
||||
from = 0;
|
||||
}
|
||||
if (!to || to > length) {
|
||||
to = length;
|
||||
}
|
||||
drawMode = drawMode || 'points';
|
||||
gl.drawArrays(gl[drawMode.toUpperCase()], from / components, (to - from) / components);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function push(x, y, a, b) {
|
||||
if (preAllocated) { // && iterator <= preAllocated.length - 4) {
|
||||
preAllocated[++iterator] = x;
|
||||
preAllocated[++iterator] = y;
|
||||
preAllocated[++iterator] = a;
|
||||
preAllocated[++iterator] = b;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Note about pre-allocated buffers:
|
||||
* - This is slower for charts with many series
|
||||
* @private
|
||||
*/
|
||||
function allocate(size) {
|
||||
size *= 4;
|
||||
iterator = -1;
|
||||
preAllocated = new Float32Array(size);
|
||||
}
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
return {
|
||||
destroy: destroy,
|
||||
bind: bind,
|
||||
data: data,
|
||||
build: build,
|
||||
render: render,
|
||||
allocate: allocate,
|
||||
push: push
|
||||
};
|
||||
}
|
||||
export default GLVertexBuffer;
|
||||
Reference in New Issue
Block a user