/* *
|
*
|
* Copyright (c) 2019-2019 Highsoft AS
|
*
|
* Boost module: stripped-down renderer for higher performance
|
*
|
* License: highcharts.com/license
|
*
|
* */
|
|
'use strict';
|
|
Object.defineProperty(exports, "__esModule", {
|
value: true
|
});
|
|
var _Globals = require('../../parts/Globals.js');
|
|
var _Globals2 = _interopRequireDefault(_Globals);
|
|
require('../../parts/Series.js');
|
|
var _boostUtils = require('./boost-utils.js');
|
|
var _boostUtils2 = _interopRequireDefault(_boostUtils);
|
|
var _boostAttach = require('./boost-attach.js');
|
|
var _boostAttach2 = _interopRequireDefault(_boostAttach);
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
var addEvent = _Globals2.default.addEvent,
|
fireEvent = _Globals2.default.fireEvent,
|
extend = _Globals2.default.extend,
|
Series = _Globals2.default.Series,
|
seriesTypes = _Globals2.default.seriesTypes,
|
wrap = _Globals2.default.wrap,
|
noop = function noop() {},
|
eachAsync = _boostUtils2.default.eachAsync,
|
pointDrawHandler = _boostUtils2.default.pointDrawHandler,
|
allocateIfNotSeriesBoosting = _boostUtils2.default.allocateIfNotSeriesBoosting,
|
renderIfNotSeriesBoosting = _boostUtils2.default.renderIfNotSeriesBoosting,
|
shouldForceChartSeriesBoosting = _boostUtils2.default.shouldForceChartSeriesBoosting,
|
index;
|
|
/**
|
* Initialize the boot module.
|
*
|
* @private
|
*/
|
function init() {
|
_Globals2.default.extend(Series.prototype, {
|
/**
|
* @private
|
* @function Highcharts.Series#renderCanvas
|
*/
|
renderCanvas: function renderCanvas() {
|
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 addKDPoint(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 = (0, _boostAttach2.default)(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.animate = null;
|
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 = undefined;
|
}
|
|
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 */
|
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 (minI === undefined || clientX === lastClientX) {
|
if (!isRange) {
|
low = y;
|
}
|
if (maxI === undefined || y > maxVal) {
|
maxVal = y;
|
maxI = i;
|
}
|
if (minI === undefined || low < minVal) {
|
minVal = low;
|
minI = i;
|
}
|
}
|
// Add points and reset
|
if (clientX !== lastClientX) {
|
if (minI !== undefined) {
|
// maxI is number too
|
plotY = yAxis.toPixels(maxVal, true);
|
yBottom = yAxis.toPixels(minVal, true);
|
|
addKDPoint(clientX, plotY, maxI);
|
if (yBottom !== plotY) {
|
addKDPoint(clientX, yBottom, minI);
|
}
|
}
|
|
minI = maxI = undefined;
|
lastClientX = clientX;
|
}
|
} else {
|
plotY = Math.ceil(yAxis.toPixels(y, true));
|
addKDPoint(clientX, plotY, i);
|
}
|
}
|
}
|
|
return !chartDestroyed;
|
}
|
|
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);
|
}
|
});
|
|
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
|
_Globals2.default.Chart.prototype.callbacks.push(function (chart) {
|
|
/* Convert chart-level canvas to image */
|
function canvasToSVG() {
|
if (chart.ogl && chart.isChartSeriesBoosting()) {
|
chart.ogl.render(chart);
|
}
|
}
|
|
/* Clear chart-level canvas */
|
function preRender() {
|
// Reset force state
|
chart.boostForceChartBoost = undefined;
|
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);
|
// });
|
});
|
}
|
|
exports.default = init;
|