BoostInit.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /* *
  2. *
  3. * Copyright (c) 2019-2021 Highsoft AS
  4. *
  5. * Boost module: stripped-down renderer for higher performance
  6. *
  7. * License: highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import Chart from '../../Core/Chart/Chart.js';
  14. import H from '../../Core/Globals.js';
  15. var noop = H.noop;
  16. import Series from '../../Core/Series/Series.js';
  17. import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
  18. var seriesTypes = SeriesRegistry.seriesTypes;
  19. import U from '../../Core/Utilities.js';
  20. var addEvent = U.addEvent, extend = U.extend, fireEvent = U.fireEvent, wrap = U.wrap;
  21. import butils from './BoostUtils.js';
  22. import createAndAttachRenderer from './BoostAttach.js';
  23. var eachAsync = butils.eachAsync, pointDrawHandler = butils.pointDrawHandler, allocateIfNotSeriesBoosting = butils.allocateIfNotSeriesBoosting, renderIfNotSeriesBoosting = butils.renderIfNotSeriesBoosting, shouldForceChartSeriesBoosting = butils.shouldForceChartSeriesBoosting, index;
  24. /* eslint-disable valid-jsdoc */
  25. /**
  26. * Initialize the boot module.
  27. *
  28. * @private
  29. * @return {void}
  30. */
  31. function init() {
  32. extend(Series.prototype, {
  33. /**
  34. * @private
  35. * @function Highcharts.Series#renderCanvas
  36. */
  37. renderCanvas: function () {
  38. 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 &&
  39. 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 ||
  40. this.options.xData ||
  41. this.processedXData ||
  42. false), addKDPoint = function (clientX, plotY, i) {
  43. // We need to do ceil on the clientX to make things
  44. // snap to pixel values. The renderer will frequently
  45. // draw stuff on "sub-pixels".
  46. clientX = Math.ceil(clientX);
  47. // Shaves off about 60ms compared to repeated concatenation
  48. index = compareX ? clientX : clientX + ',' + plotY;
  49. // The k-d tree requires series points.
  50. // Reduce the amount of points, since the time to build the
  51. // tree increases exponentially.
  52. if (enableMouseTracking && !pointTaken[index]) {
  53. pointTaken[index] = true;
  54. if (chart.inverted) {
  55. clientX = xAxis.len - clientX;
  56. plotY = yAxis.len - plotY;
  57. }
  58. points.push({
  59. x: xDataFull ? xDataFull[cropStart + i] : false,
  60. clientX: clientX,
  61. plotX: clientX,
  62. plotY: plotY,
  63. i: cropStart + i
  64. });
  65. }
  66. };
  67. // Get or create the renderer
  68. renderer = createAndAttachRenderer(chart, series);
  69. chart.isBoosting = true;
  70. boostOptions = renderer.settings;
  71. if (!this.visible) {
  72. return;
  73. }
  74. // If we are zooming out from SVG mode, destroy the graphics
  75. if (this.points || this.graph) {
  76. this.destroyGraphics();
  77. }
  78. // If we're rendering per. series we should create the marker groups
  79. // as usual.
  80. if (!chart.isChartSeriesBoosting()) {
  81. // If all series were boosting, but are not anymore
  82. // restore private markerGroup
  83. if (this.markerGroup === chart.markerGroup) {
  84. this.markerGroup = void 0;
  85. }
  86. this.markerGroup = series.plotGroup('markerGroup', 'markers', true, 1, chart.seriesGroup);
  87. }
  88. else {
  89. // If series has a private markeGroup, remove that
  90. // and use common markerGroup
  91. if (this.markerGroup &&
  92. this.markerGroup !== chart.markerGroup) {
  93. this.markerGroup.destroy();
  94. }
  95. // Use a single group for the markers
  96. this.markerGroup = chart.markerGroup;
  97. // When switching from chart boosting mode, destroy redundant
  98. // series boosting targets
  99. if (this.renderTarget) {
  100. this.renderTarget = this.renderTarget.destroy();
  101. }
  102. }
  103. points = this.points = [];
  104. // Do not start building while drawing
  105. series.buildKDTree = noop;
  106. if (renderer) {
  107. allocateIfNotSeriesBoosting(renderer, this);
  108. renderer.pushSeries(series);
  109. // Perform the actual renderer if we're on series level
  110. renderIfNotSeriesBoosting(renderer, this, chart);
  111. }
  112. /**
  113. * This builds the KD-tree
  114. * @private
  115. */
  116. function processPoint(d, i) {
  117. var x, y, clientX, plotY, isNull, low = false, chartDestroyed = typeof chart.index === 'undefined', isYInside = true;
  118. if (!chartDestroyed) {
  119. if (useRaw) {
  120. x = d[0];
  121. y = d[1];
  122. }
  123. else {
  124. x = d;
  125. y = yData[i];
  126. }
  127. // Resolve low and high for range series
  128. if (isRange) {
  129. if (useRaw) {
  130. y = d.slice(1, 3);
  131. }
  132. low = y[0];
  133. y = y[1];
  134. }
  135. else if (isStacked) {
  136. x = d.x;
  137. y = d.stackY;
  138. low = y - d.y;
  139. }
  140. isNull = y === null;
  141. // Optimize for scatter zooming
  142. if (!requireSorting) {
  143. isYInside = y >= yMin && y <= yMax;
  144. }
  145. if (!isNull && x >= xMin && x <= xMax && isYInside) {
  146. clientX = xAxis.toPixels(x, true);
  147. if (sampling) {
  148. if (typeof minI === 'undefined' ||
  149. clientX === lastClientX) {
  150. if (!isRange) {
  151. low = y;
  152. }
  153. if (typeof maxI === 'undefined' ||
  154. y > maxVal) {
  155. maxVal = y;
  156. maxI = i;
  157. }
  158. if (typeof minI === 'undefined' ||
  159. low < minVal) {
  160. minVal = low;
  161. minI = i;
  162. }
  163. }
  164. // Add points and reset
  165. if (clientX !== lastClientX) {
  166. // maxI is number too:
  167. if (typeof minI !== 'undefined') {
  168. plotY =
  169. yAxis.toPixels(maxVal, true);
  170. yBottom =
  171. yAxis.toPixels(minVal, true);
  172. addKDPoint(clientX, plotY, maxI);
  173. if (yBottom !== plotY) {
  174. addKDPoint(clientX, yBottom, minI);
  175. }
  176. }
  177. minI = maxI = void 0;
  178. lastClientX = clientX;
  179. }
  180. }
  181. else {
  182. plotY = Math.ceil(yAxis.toPixels(y, true));
  183. addKDPoint(clientX, plotY, i);
  184. }
  185. }
  186. }
  187. return !chartDestroyed;
  188. }
  189. /**
  190. * @private
  191. */
  192. function doneProcessing() {
  193. fireEvent(series, 'renderedCanvas');
  194. // Go back to prototype, ready to build
  195. delete series.buildKDTree;
  196. series.buildKDTree();
  197. if (boostOptions.debug.timeKDTree) {
  198. console.timeEnd('kd tree building'); // eslint-disable-line no-console
  199. }
  200. }
  201. // Loop over the points to build the k-d tree - skip this if
  202. // exporting
  203. if (!chart.renderer.forExport) {
  204. if (boostOptions.debug.timeKDTree) {
  205. console.time('kd tree building'); // eslint-disable-line no-console
  206. }
  207. eachAsync(isStacked ? series.data : (xData || rawData), processPoint, doneProcessing);
  208. }
  209. }
  210. });
  211. /*
  212. * We need to handle heatmaps separatly, since we can't perform the
  213. * size/color calculations in the shader easily.
  214. *
  215. * This likely needs future optimization.
  216. */
  217. ['heatmap', 'treemap'].forEach(function (t) {
  218. if (seriesTypes[t]) {
  219. wrap(seriesTypes[t].prototype, 'drawPoints', pointDrawHandler);
  220. }
  221. });
  222. /* eslint-disable no-invalid-this */
  223. if (seriesTypes.bubble) {
  224. // By default, the bubble series does not use the KD-tree, so force it
  225. // to.
  226. delete seriesTypes.bubble.prototype.buildKDTree;
  227. // seriesTypes.bubble.prototype.directTouch = false;
  228. // Needed for markers to work correctly
  229. wrap(seriesTypes.bubble.prototype, 'markerAttribs', function (proceed) {
  230. if (this.isSeriesBoosting) {
  231. return false;
  232. }
  233. return proceed.apply(this, [].slice.call(arguments, 1));
  234. });
  235. }
  236. seriesTypes.scatter.prototype.fill = true;
  237. extend(seriesTypes.area.prototype, {
  238. fill: true,
  239. fillOpacity: true,
  240. sampling: true
  241. });
  242. extend(seriesTypes.column.prototype, {
  243. fill: true,
  244. sampling: true
  245. });
  246. Chart.prototype.propsRequireUpdateSeries.push('boost');
  247. // Take care of the canvas blitting
  248. Chart.prototype.callbacks.push(function (chart) {
  249. /**
  250. * Convert chart-level canvas to image.
  251. * @private
  252. */
  253. function canvasToSVG() {
  254. if (chart.ogl && chart.isChartSeriesBoosting()) {
  255. chart.ogl.render(chart);
  256. }
  257. }
  258. /**
  259. * Clear chart-level canvas.
  260. * @private
  261. */
  262. function preRender() {
  263. // Reset force state
  264. chart.boostForceChartBoost = void 0;
  265. chart.boostForceChartBoost = shouldForceChartSeriesBoosting(chart);
  266. chart.isBoosting = false;
  267. if (!chart.isChartSeriesBoosting() && chart.didBoost) {
  268. chart.didBoost = false;
  269. }
  270. // Clear the canvas
  271. if (chart.boostClear) {
  272. chart.boostClear();
  273. }
  274. if (chart.canvas && chart.ogl && chart.isChartSeriesBoosting()) {
  275. chart.didBoost = true;
  276. // Allocate
  277. chart.ogl.allocateBuffer(chart);
  278. }
  279. // see #6518 + #6739
  280. if (chart.markerGroup &&
  281. chart.xAxis &&
  282. chart.xAxis.length > 0 &&
  283. chart.yAxis &&
  284. chart.yAxis.length > 0) {
  285. chart.markerGroup.translate(chart.xAxis[0].pos, chart.yAxis[0].pos);
  286. }
  287. }
  288. addEvent(chart, 'predraw', preRender);
  289. addEvent(chart, 'render', canvasToSVG);
  290. // addEvent(chart, 'zoom', function () {
  291. // chart.boostForceChartBoost =
  292. // shouldForceChartSeriesBoosting(chart);
  293. // });
  294. });
  295. /* eslint-enable no-invalid-this */
  296. }
  297. export default init;