| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- /* *
- *
- * 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;
|