boost-utils.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /* *
  2. *
  3. * Copyright (c) 2019-2019 Highsoft AS
  4. *
  5. * Boost module: stripped-down renderer for higher performance
  6. *
  7. * License: highcharts.com/license
  8. *
  9. * This files contains generic utility functions used by the boost module.
  10. *
  11. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  12. *
  13. * */
  14. 'use strict';
  15. import H from '../../parts/Globals.js';
  16. import '../../parts/Series.js';
  17. import boostableMap from './boostable-map.js';
  18. import createAndAttachRenderer from './boost-attach.js';
  19. var win = H.win, doc = win.document, pick = H.pick;
  20. // This should be a const.
  21. var CHUNK_SIZE = 3000;
  22. /**
  23. * Tolerant max() function.
  24. *
  25. * @private
  26. * @function patientMax
  27. *
  28. * @param {...Array<Array<unknown>>} args
  29. * Max arguments
  30. *
  31. * @return {number}
  32. * Max value
  33. */
  34. function patientMax() {
  35. var args = [];
  36. for (var _i = 0; _i < arguments.length; _i++) {
  37. args[_i] = arguments[_i];
  38. }
  39. var r = -Number.MAX_VALUE;
  40. args.forEach(function (t) {
  41. if (typeof t !== 'undefined' &&
  42. t !== null &&
  43. typeof t.length !== 'undefined') {
  44. // r = r < t.length ? t.length : r;
  45. if (t.length > 0) {
  46. r = t.length;
  47. return true;
  48. }
  49. }
  50. });
  51. return r;
  52. }
  53. /**
  54. * Return true if ths boost.enabled option is true
  55. *
  56. * @private
  57. * @function boostEnabled
  58. *
  59. * @param {Highcharts.Chart} chart
  60. * The chart
  61. *
  62. * @return {boolean}
  63. * True, if boost is enabled.
  64. */
  65. function boostEnabled(chart) {
  66. return pick((chart &&
  67. chart.options &&
  68. chart.options.boost &&
  69. chart.options.boost.enabled), true);
  70. }
  71. /**
  72. * Returns true if we should force boosting the chart
  73. * @private
  74. * @function shouldForceChartSeriesBoosting
  75. *
  76. * @param {Highcharts.Chart} chart
  77. * The chart to check for forcing on
  78. *
  79. * @return {boolean}
  80. * True, if boosting should be forced.
  81. */
  82. function shouldForceChartSeriesBoosting(chart) {
  83. // If there are more than five series currently boosting,
  84. // we should boost the whole chart to avoid running out of webgl contexts.
  85. var sboostCount = 0, canBoostCount = 0, allowBoostForce = pick(chart.options.boost && chart.options.boost.allowForce, true), series;
  86. if (typeof chart.boostForceChartBoost !== 'undefined') {
  87. return chart.boostForceChartBoost;
  88. }
  89. if (chart.series.length > 1) {
  90. for (var i = 0; i < chart.series.length; i++) {
  91. series = chart.series[i];
  92. // Don't count series with boostThreshold set to 0
  93. // See #8950
  94. // Also don't count if the series is hidden.
  95. // See #9046
  96. if (series.options.boostThreshold === 0 ||
  97. series.visible === false) {
  98. continue;
  99. }
  100. // Don't count heatmap series as they are handled differently.
  101. // In the future we should make the heatmap/treemap path compatible
  102. // with forcing. See #9636.
  103. if (series.type === 'heatmap') {
  104. continue;
  105. }
  106. if (boostableMap[series.type]) {
  107. ++canBoostCount;
  108. }
  109. if (patientMax(series.processedXData, series.options.data,
  110. // series.xData,
  111. series.points) >= (series.options.boostThreshold || Number.MAX_VALUE)) {
  112. ++sboostCount;
  113. }
  114. }
  115. }
  116. chart.boostForceChartBoost = allowBoostForce && ((canBoostCount === chart.series.length &&
  117. sboostCount > 0) ||
  118. sboostCount > 5);
  119. return chart.boostForceChartBoost;
  120. }
  121. /* eslint-disable valid-jsdoc */
  122. /**
  123. * Performs the actual render if the renderer is
  124. * attached to the series.
  125. * @private
  126. * @param renderer {OGLRenderer} - the renderer
  127. * @param series {Highcharts.Series} - the series
  128. */
  129. function renderIfNotSeriesBoosting(renderer, series, chart) {
  130. if (renderer &&
  131. series.renderTarget &&
  132. series.canvas &&
  133. !(chart || series.chart).isChartSeriesBoosting()) {
  134. renderer.render(chart || series.chart);
  135. }
  136. }
  137. /**
  138. * @private
  139. */
  140. function allocateIfNotSeriesBoosting(renderer, series) {
  141. if (renderer &&
  142. series.renderTarget &&
  143. series.canvas &&
  144. !series.chart.isChartSeriesBoosting()) {
  145. renderer.allocateBufferForSingleSeries(series);
  146. }
  147. }
  148. /**
  149. * An "async" foreach loop. Uses a setTimeout to keep the loop from blocking the
  150. * UI thread.
  151. *
  152. * @private
  153. *
  154. * @param arr {Array} - the array to loop through
  155. * @param fn {Function} - the callback to call for each item
  156. * @param finalFunc {Function} - the callback to call when done
  157. * @param chunkSize {Number} - the number of iterations per timeout
  158. * @param i {Number} - the current index
  159. * @param noTimeout {Boolean} - set to true to skip timeouts
  160. */
  161. function eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout) {
  162. i = i || 0;
  163. chunkSize = chunkSize || CHUNK_SIZE;
  164. var threshold = i + chunkSize, proceed = true;
  165. while (proceed && i < threshold && i < arr.length) {
  166. proceed = fn(arr[i], i);
  167. ++i;
  168. }
  169. if (proceed) {
  170. if (i < arr.length) {
  171. if (noTimeout) {
  172. eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout);
  173. }
  174. else if (win.requestAnimationFrame) {
  175. // If available, do requestAnimationFrame - shaves off a few ms
  176. win.requestAnimationFrame(function () {
  177. eachAsync(arr, fn, finalFunc, chunkSize, i);
  178. });
  179. }
  180. else {
  181. setTimeout(function () {
  182. eachAsync(arr, fn, finalFunc, chunkSize, i);
  183. });
  184. }
  185. }
  186. else if (finalFunc) {
  187. finalFunc();
  188. }
  189. }
  190. }
  191. /**
  192. * Returns true if the current browser supports webgl
  193. *
  194. * @private
  195. * @function hasWebGLSupport
  196. *
  197. * @return {boolean}
  198. */
  199. function hasWebGLSupport() {
  200. var i = 0, canvas, contexts = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'], context = false;
  201. if (typeof win.WebGLRenderingContext !== 'undefined') {
  202. canvas = doc.createElement('canvas');
  203. for (; i < contexts.length; i++) {
  204. try {
  205. context = canvas.getContext(contexts[i]);
  206. if (typeof context !== 'undefined' && context !== null) {
  207. return true;
  208. }
  209. }
  210. catch (e) {
  211. // silent error
  212. }
  213. }
  214. }
  215. return false;
  216. }
  217. /* eslint-disable no-invalid-this */
  218. /**
  219. * Used for treemap|heatmap.drawPoints
  220. *
  221. * @private
  222. * @function pointDrawHandler
  223. *
  224. * @param {Function} proceed
  225. *
  226. * @return {*}
  227. */
  228. function pointDrawHandler(proceed) {
  229. var enabled = true, renderer;
  230. if (this.chart.options && this.chart.options.boost) {
  231. enabled = typeof this.chart.options.boost.enabled === 'undefined' ?
  232. true :
  233. this.chart.options.boost.enabled;
  234. }
  235. if (!enabled || !this.isSeriesBoosting) {
  236. return proceed.call(this);
  237. }
  238. this.chart.isBoosting = true;
  239. // Make sure we have a valid OGL context
  240. renderer = createAndAttachRenderer(this.chart, this);
  241. if (renderer) {
  242. allocateIfNotSeriesBoosting(renderer, this);
  243. renderer.pushSeries(this);
  244. }
  245. renderIfNotSeriesBoosting(renderer, this);
  246. }
  247. /* eslint-enable no-invalid-this, valid-jsdoc */
  248. var funs = {
  249. patientMax: patientMax,
  250. boostEnabled: boostEnabled,
  251. shouldForceChartSeriesBoosting: shouldForceChartSeriesBoosting,
  252. renderIfNotSeriesBoosting: renderIfNotSeriesBoosting,
  253. allocateIfNotSeriesBoosting: allocateIfNotSeriesBoosting,
  254. eachAsync: eachAsync,
  255. hasWebGLSupport: hasWebGLSupport,
  256. pointDrawHandler: pointDrawHandler
  257. };
  258. // This needs to be fixed.
  259. H.hasWebGLSupport = hasWebGLSupport;
  260. export default funs;