BoostInit.js 12 KB

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