WGLRenderer.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  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 Color from '../../Core/Color/Color.js';
  14. var color = Color.parse;
  15. import GLShader from './WGLShader.js';
  16. import GLVertexBuffer from './WGLVBuffer.js';
  17. import H from '../../Core/Globals.js';
  18. var doc = H.doc;
  19. import U from '../../Core/Utilities.js';
  20. var isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
  21. /* eslint-disable valid-jsdoc */
  22. /**
  23. * Main renderer. Used to render series.
  24. *
  25. * Notes to self:
  26. * - May be able to build a point map by rendering to a separate canvas and
  27. * encoding values in the color data.
  28. * - Need to figure out a way to transform the data quicker
  29. *
  30. * @private
  31. * @function GLRenderer
  32. *
  33. * @param {Function} postRenderCallback
  34. *
  35. * @return {*}
  36. */
  37. function GLRenderer(postRenderCallback) {
  38. // // Shader
  39. var shader = false,
  40. // Vertex buffers - keyed on shader attribute name
  41. vbuffer = false, vlen = 0,
  42. // Opengl context
  43. gl = false,
  44. // Width of our viewport in pixels
  45. width = 0,
  46. // Height of our viewport in pixels
  47. height = 0,
  48. // The data to render - array of coordinates
  49. data = false,
  50. // The marker data
  51. markerData = false,
  52. // Exports
  53. exports = {},
  54. // Is it inited?
  55. isInited = false,
  56. // The series stack
  57. series = [],
  58. // Texture handles
  59. textureHandles = {},
  60. // Things to draw as "rectangles" (i.e lines)
  61. asBar = {
  62. 'column': true,
  63. 'columnrange': true,
  64. 'bar': true,
  65. 'area': true,
  66. 'arearange': true
  67. }, asCircle = {
  68. 'scatter': true,
  69. 'bubble': true
  70. },
  71. // Render settings
  72. settings = {
  73. pointSize: 1,
  74. lineWidth: 1,
  75. fillColor: '#AA00AA',
  76. useAlpha: true,
  77. usePreallocated: false,
  78. useGPUTranslations: false,
  79. debug: {
  80. timeRendering: false,
  81. timeSeriesProcessing: false,
  82. timeSetup: false,
  83. timeBufferCopy: false,
  84. timeKDTree: false,
  85. showSkipSummary: false
  86. }
  87. };
  88. // /////////////////////////////////////////////////////////////////////////
  89. /**
  90. * @private
  91. */
  92. function setOptions(options) {
  93. merge(true, settings, options);
  94. }
  95. /**
  96. * @private
  97. */
  98. function seriesPointCount(series) {
  99. var isStacked, xData, s;
  100. if (series.isSeriesBoosting) {
  101. isStacked = !!series.options.stacking;
  102. xData = (series.xData ||
  103. series.options.xData ||
  104. series.processedXData);
  105. s = (isStacked ? series.data : (xData || series.options.data))
  106. .length;
  107. if (series.type === 'treemap') {
  108. s *= 12;
  109. }
  110. else if (series.type === 'heatmap') {
  111. s *= 6;
  112. }
  113. else if (asBar[series.type]) {
  114. s *= 2;
  115. }
  116. return s;
  117. }
  118. return 0;
  119. }
  120. /**
  121. * Allocate a float buffer to fit all series
  122. * @private
  123. */
  124. function allocateBuffer(chart) {
  125. var s = 0;
  126. if (!settings.usePreallocated) {
  127. return;
  128. }
  129. chart.series.forEach(function (series) {
  130. if (series.isSeriesBoosting) {
  131. s += seriesPointCount(series);
  132. }
  133. });
  134. vbuffer.allocate(s);
  135. }
  136. /**
  137. * @private
  138. */
  139. function allocateBufferForSingleSeries(series) {
  140. var s = 0;
  141. if (!settings.usePreallocated) {
  142. return;
  143. }
  144. if (series.isSeriesBoosting) {
  145. s = seriesPointCount(series);
  146. }
  147. vbuffer.allocate(s);
  148. }
  149. /**
  150. * Returns an orthographic perspective matrix
  151. * @private
  152. * @param {number} width - the width of the viewport in pixels
  153. * @param {number} height - the height of the viewport in pixels
  154. */
  155. function orthoMatrix(width, height) {
  156. var near = 0, far = 1;
  157. return [
  158. 2 / width, 0, 0, 0,
  159. 0, -(2 / height), 0, 0,
  160. 0, 0, -2 / (far - near), 0,
  161. -1, 1, -(far + near) / (far - near), 1
  162. ];
  163. }
  164. /**
  165. * Clear the depth and color buffer
  166. * @private
  167. */
  168. function clear() {
  169. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  170. }
  171. /**
  172. * Get the WebGL context
  173. * @private
  174. * @returns {WebGLContext} - the context
  175. */
  176. function getGL() {
  177. return gl;
  178. }
  179. /**
  180. * Push data for a single series
  181. * This calculates additional vertices and transforms the data to be
  182. * aligned correctly in memory
  183. * @private
  184. */
  185. function pushSeriesData(series, inst) {
  186. var isRange = (series.pointArrayMap &&
  187. series.pointArrayMap.join(',') === 'low,high'), chart = series.chart, options = series.options, isStacked = !!options.stacking, rawData = options.data, xExtremes = series.xAxis.getExtremes(), xMin = xExtremes.min, xMax = xExtremes.max, yExtremes = series.yAxis.getExtremes(), yMin = yExtremes.min, yMax = yExtremes.max, xData = series.xData || options.xData || series.processedXData, yData = series.yData || options.yData || series.processedYData, zData = (series.zData || options.zData ||
  188. series.processedZData), yAxis = series.yAxis, xAxis = series.xAxis,
  189. // plotHeight = series.chart.plotHeight,
  190. plotWidth = series.chart.plotWidth, useRaw = !xData || xData.length === 0,
  191. // threshold = options.threshold,
  192. // yBottom = chart.yAxis[0].getThreshold(threshold),
  193. // hasThreshold = isNumber(threshold),
  194. // colorByPoint = series.options.colorByPoint,
  195. // This is required for color by point, so make sure this is
  196. // uncommented if enabling that
  197. // colorIndex = 0,
  198. // Required for color axis support
  199. // caxis,
  200. connectNulls = options.connectNulls,
  201. // For some reason eslint/TypeScript don't pick up that this is
  202. // actually used: --- bre1470: it is never read, just set
  203. // maxVal: (number|undefined), // eslint-disable-line no-unused-vars
  204. points = series.points || false, lastX = false, lastY = false, minVal, pcolor, scolor, sdata = isStacked ? series.data : (xData || rawData), closestLeft = { x: Number.MAX_VALUE, y: 0 }, closestRight = { x: -Number.MAX_VALUE, y: 0 },
  205. //
  206. skipped = 0, hadPoints = false,
  207. //
  208. cullXThreshold = 1, cullYThreshold = 1,
  209. // The following are used in the builder while loop
  210. x, y, d, z, i = -1, px = false, nx = false, low, chartDestroyed = typeof chart.index === 'undefined', nextInside = false, prevInside = false, pcolor = false, drawAsBar = asBar[series.type], isXInside = false, isYInside = true, firstPoint = true, zones = options.zones || false, zoneDefColor = false, threshold = options.threshold, gapSize = false;
  211. if (options.boostData && options.boostData.length > 0) {
  212. return;
  213. }
  214. if (options.gapSize) {
  215. gapSize = options.gapUnit !== 'value' ?
  216. options.gapSize * series.closestPointRange :
  217. options.gapSize;
  218. }
  219. if (zones) {
  220. zones.some(function (zone) {
  221. if (typeof zone.value === 'undefined') {
  222. zoneDefColor = new Color(zone.color);
  223. return true;
  224. }
  225. return false;
  226. });
  227. if (!zoneDefColor) {
  228. zoneDefColor = ((series.pointAttribs && series.pointAttribs().fill) ||
  229. series.color);
  230. zoneDefColor = new Color(zoneDefColor);
  231. }
  232. }
  233. if (chart.inverted) {
  234. // plotHeight = series.chart.plotWidth;
  235. plotWidth = series.chart.plotHeight;
  236. }
  237. series.closestPointRangePx = Number.MAX_VALUE;
  238. /**
  239. * Push color to color buffer - need to do this per vertex.
  240. * @private
  241. */
  242. function pushColor(color) {
  243. if (color) {
  244. inst.colorData.push(color[0]);
  245. inst.colorData.push(color[1]);
  246. inst.colorData.push(color[2]);
  247. inst.colorData.push(color[3]);
  248. }
  249. }
  250. /**
  251. * Push a vertice to the data buffer.
  252. * @private
  253. */
  254. function vertice(x, y, checkTreshold, pointSize, color) {
  255. pushColor(color);
  256. if (settings.usePreallocated) {
  257. vbuffer.push(x, y, checkTreshold ? 1 : 0, pointSize || 1);
  258. vlen += 4;
  259. }
  260. else {
  261. data.push(x);
  262. data.push(y);
  263. data.push(checkTreshold ? 1 : 0);
  264. data.push(pointSize || 1);
  265. }
  266. }
  267. /**
  268. * @private
  269. */
  270. function closeSegment() {
  271. if (inst.segments.length) {
  272. inst.segments[inst.segments.length - 1].to = data.length || vlen;
  273. }
  274. }
  275. /**
  276. * Create a new segment for the current set.
  277. * @private
  278. */
  279. function beginSegment() {
  280. // Insert a segment on the series.
  281. // A segment is just a start indice.
  282. // When adding a segment, if one exists from before, it should
  283. // set the previous segment's end
  284. if (inst.segments.length &&
  285. inst.segments[inst.segments.length - 1].from === (data.length || vlen)) {
  286. return;
  287. }
  288. closeSegment();
  289. inst.segments.push({
  290. from: data.length || vlen
  291. });
  292. }
  293. /**
  294. * Push a rectangle to the data buffer.
  295. * @private
  296. */
  297. function pushRect(x, y, w, h, color) {
  298. pushColor(color);
  299. vertice(x + w, y);
  300. pushColor(color);
  301. vertice(x, y);
  302. pushColor(color);
  303. vertice(x, y + h);
  304. pushColor(color);
  305. vertice(x, y + h);
  306. pushColor(color);
  307. vertice(x + w, y + h);
  308. pushColor(color);
  309. vertice(x + w, y);
  310. }
  311. // Create the first segment
  312. beginSegment();
  313. // Special case for point shapes
  314. if (points && points.length > 0) {
  315. // If we're doing points, we assume that the points are already
  316. // translated, so we skip the shader translation.
  317. inst.skipTranslation = true;
  318. // Force triangle draw mode
  319. inst.drawMode = 'triangles';
  320. // We don't have a z component in the shader, so we need to sort.
  321. if (points[0].node && points[0].node.levelDynamic) {
  322. points.sort(function (a, b) {
  323. if (a.node) {
  324. if (a.node.levelDynamic >
  325. b.node.levelDynamic) {
  326. return 1;
  327. }
  328. if (a.node.levelDynamic <
  329. b.node.levelDynamic) {
  330. return -1;
  331. }
  332. }
  333. return 0;
  334. });
  335. }
  336. points.forEach(function (point) {
  337. var plotY = point.plotY, shapeArgs, swidth, pointAttr;
  338. if (typeof plotY !== 'undefined' &&
  339. !isNaN(plotY) &&
  340. point.y !== null) {
  341. shapeArgs = point.shapeArgs;
  342. pointAttr = chart.styledMode ?
  343. point.series
  344. .colorAttribs(point) :
  345. pointAttr = point.series.pointAttribs(point);
  346. swidth = pointAttr['stroke-width'] || 0;
  347. // Handle point colors
  348. pcolor = color(pointAttr.fill).rgba;
  349. pcolor[0] /= 255.0;
  350. pcolor[1] /= 255.0;
  351. pcolor[2] /= 255.0;
  352. // So there are two ways of doing this. Either we can
  353. // create a rectangle of two triangles, or we can do a
  354. // point and use point size. Latter is faster, but
  355. // only supports squares. So we're doing triangles.
  356. // We could also use one color per. vertice to get
  357. // better color interpolation.
  358. // If there's stroking, we do an additional rect
  359. if (series.type === 'treemap') {
  360. swidth = swidth || 1;
  361. scolor = color(pointAttr.stroke).rgba;
  362. scolor[0] /= 255.0;
  363. scolor[1] /= 255.0;
  364. scolor[2] /= 255.0;
  365. pushRect(shapeArgs.x, shapeArgs.y, shapeArgs.width, shapeArgs.height, scolor);
  366. swidth /= 2;
  367. }
  368. // } else {
  369. // swidth = 0;
  370. // }
  371. // Fixes issues with inverted heatmaps (see #6981)
  372. // The root cause is that the coordinate system is flipped.
  373. // In other words, instead of [0,0] being top-left, it's
  374. // bottom-right. This causes a vertical and horizontal flip
  375. // in the resulting image, making it rotated 180 degrees.
  376. if (series.type === 'heatmap' && chart.inverted) {
  377. shapeArgs.x = xAxis.len - shapeArgs.x;
  378. shapeArgs.y = yAxis.len - shapeArgs.y;
  379. shapeArgs.width = -shapeArgs.width;
  380. shapeArgs.height = -shapeArgs.height;
  381. }
  382. pushRect(shapeArgs.x + swidth, shapeArgs.y + swidth, shapeArgs.width - (swidth * 2), shapeArgs.height - (swidth * 2), pcolor);
  383. }
  384. });
  385. closeSegment();
  386. return;
  387. }
  388. // Extract color axis
  389. // (chart.axes || []).forEach(function (a) {
  390. // if (H.ColorAxis && a instanceof H.ColorAxis) {
  391. // caxis = a;
  392. // }
  393. // });
  394. while (i < sdata.length - 1) {
  395. d = sdata[++i];
  396. // px = x = y = z = nx = low = false;
  397. // chartDestroyed = typeof chart.index === 'undefined';
  398. // nextInside = prevInside = pcolor = isXInside = isYInside = false;
  399. // drawAsBar = asBar[series.type];
  400. if (chartDestroyed) {
  401. break;
  402. }
  403. // Uncomment this to enable color by point.
  404. // This currently left disabled as the charts look really ugly
  405. // when enabled and there's a lot of points.
  406. // Leaving in for the future (tm).
  407. // if (colorByPoint) {
  408. // colorIndex = ++colorIndex %
  409. // series.chart.options.colors.length;
  410. // pcolor = toRGBAFast(series.chart.options.colors[colorIndex]);
  411. // pcolor[0] /= 255.0;
  412. // pcolor[1] /= 255.0;
  413. // pcolor[2] /= 255.0;
  414. // }
  415. // Handle the point.color option (#5999)
  416. var pointOptions = rawData && rawData[i];
  417. if (!useRaw && isObject(pointOptions, true)) {
  418. if (pointOptions.color) {
  419. pcolor = color(pointOptions.color).rgba;
  420. pcolor[0] /= 255.0;
  421. pcolor[1] /= 255.0;
  422. pcolor[2] /= 255.0;
  423. }
  424. }
  425. if (useRaw) {
  426. x = d[0];
  427. y = d[1];
  428. if (sdata[i + 1]) {
  429. nx = sdata[i + 1][0];
  430. }
  431. if (sdata[i - 1]) {
  432. px = sdata[i - 1][0];
  433. }
  434. if (d.length >= 3) {
  435. z = d[2];
  436. if (d[2] > inst.zMax) {
  437. inst.zMax = d[2];
  438. }
  439. if (d[2] < inst.zMin) {
  440. inst.zMin = d[2];
  441. }
  442. }
  443. }
  444. else {
  445. x = d;
  446. y = yData[i];
  447. if (sdata[i + 1]) {
  448. nx = sdata[i + 1];
  449. }
  450. if (sdata[i - 1]) {
  451. px = sdata[i - 1];
  452. }
  453. if (zData && zData.length) {
  454. z = zData[i];
  455. if (zData[i] > inst.zMax) {
  456. inst.zMax = zData[i];
  457. }
  458. if (zData[i] < inst.zMin) {
  459. inst.zMin = zData[i];
  460. }
  461. }
  462. }
  463. if (!connectNulls && (x === null || y === null)) {
  464. beginSegment();
  465. continue;
  466. }
  467. if (nx && nx >= xMin && nx <= xMax) {
  468. nextInside = true;
  469. }
  470. if (px && px >= xMin && px <= xMax) {
  471. prevInside = true;
  472. }
  473. if (isRange) {
  474. if (useRaw) {
  475. y = d.slice(1, 3);
  476. }
  477. low = y[0];
  478. y = y[1];
  479. }
  480. else if (isStacked) {
  481. x = d.x;
  482. y = d.stackY;
  483. low = y - d.y;
  484. }
  485. if (yMin !== null &&
  486. typeof yMin !== 'undefined' &&
  487. yMax !== null &&
  488. typeof yMax !== 'undefined') {
  489. isYInside = y >= yMin && y <= yMax;
  490. }
  491. if (x > xMax && closestRight.x < xMax) {
  492. closestRight.x = x;
  493. closestRight.y = y;
  494. }
  495. if (x < xMin && closestLeft.x > xMin) {
  496. closestLeft.x = x;
  497. closestLeft.y = y;
  498. }
  499. if (y === null && connectNulls) {
  500. continue;
  501. }
  502. // Cull points outside the extremes
  503. if (y === null || (!isYInside && !nextInside && !prevInside)) {
  504. beginSegment();
  505. continue;
  506. }
  507. // The first point before and first after extremes should be
  508. // rendered (#9962)
  509. if ((nx >= xMin || x >= xMin) &&
  510. (px <= xMax || x <= xMax)) {
  511. isXInside = true;
  512. }
  513. if (!isXInside && !nextInside && !prevInside) {
  514. continue;
  515. }
  516. if (gapSize && x - px > gapSize) {
  517. beginSegment();
  518. }
  519. // Note: Boost requires that zones are sorted!
  520. if (zones) {
  521. pcolor = zoneDefColor.rgba;
  522. zones.some(function (// eslint-disable-line no-loop-func
  523. zone, i) {
  524. var last = zones[i - 1];
  525. if (typeof zone.value !== 'undefined' && y <= zone.value) {
  526. if (!last || y >= last.value) {
  527. pcolor = color(zone.color).rgba;
  528. }
  529. return true;
  530. }
  531. return false;
  532. });
  533. pcolor[0] /= 255.0;
  534. pcolor[1] /= 255.0;
  535. pcolor[2] /= 255.0;
  536. }
  537. // Skip translations - temporary floating point fix
  538. if (!settings.useGPUTranslations) {
  539. inst.skipTranslation = true;
  540. x = xAxis.toPixels(x, true);
  541. y = yAxis.toPixels(y, true);
  542. // Make sure we're not drawing outside of the chart area.
  543. // See #6594. Update: this is no longer required as far as I
  544. // can tell. Leaving in for git blame in case there are edge
  545. // cases I've not found. Having this in breaks #10246.
  546. // if (y > plotHeight) {
  547. // y = plotHeight;
  548. // }
  549. if (x > plotWidth) {
  550. // If this is rendered as a point, just skip drawing it
  551. // entirely, as we're not dependandt on lineTo'ing to it.
  552. // See #8197
  553. if (inst.drawMode === 'points') {
  554. continue;
  555. }
  556. // Having this here will clamp markers and make the angle
  557. // of the last line wrong. See 9166.
  558. // x = plotWidth;
  559. }
  560. }
  561. // No markers on out of bounds things.
  562. // Out of bound things are shown if and only if the next
  563. // or previous point is inside the rect.
  564. if (inst.hasMarkers && isXInside) {
  565. // x = Highcharts.correctFloat(
  566. // Math.min(Math.max(-1e5, xAxis.translate(
  567. // x,
  568. // 0,
  569. // 0,
  570. // 0,
  571. // 1,
  572. // 0.5,
  573. // false
  574. // )), 1e5)
  575. // );
  576. if (lastX !== false) {
  577. series.closestPointRangePx = Math.min(series.closestPointRangePx, Math.abs(x - lastX));
  578. }
  579. }
  580. // If the last _drawn_ point is closer to this point than the
  581. // threshold, skip it. Shaves off 20-100ms in processing.
  582. if (!settings.useGPUTranslations &&
  583. !settings.usePreallocated &&
  584. (lastX && Math.abs(x - lastX) < cullXThreshold) &&
  585. (lastY && Math.abs(y - lastY) < cullYThreshold)) {
  586. if (settings.debug.showSkipSummary) {
  587. ++skipped;
  588. }
  589. continue;
  590. }
  591. if (drawAsBar) {
  592. // maxVal = y;
  593. minVal = low;
  594. if (low === false || typeof low === 'undefined') {
  595. if (y < 0) {
  596. minVal = y;
  597. }
  598. else {
  599. minVal = 0;
  600. }
  601. }
  602. if (!isRange && !isStacked) {
  603. minVal = Math.max(threshold === null ? yMin : threshold, // #5268
  604. yMin); // #8731
  605. }
  606. if (!settings.useGPUTranslations) {
  607. minVal = yAxis.toPixels(minVal, true);
  608. }
  609. // Need to add an extra point here
  610. vertice(x, minVal, 0, 0, pcolor);
  611. }
  612. // Do step line if enabled.
  613. // Draws an additional point at the old Y at the new X.
  614. // See #6976.
  615. if (options.step && !firstPoint) {
  616. vertice(x, lastY, 0, 2, pcolor);
  617. }
  618. vertice(x, y, 0, series.type === 'bubble' ? (z || 1) : 2, pcolor);
  619. // Uncomment this to support color axis.
  620. // if (caxis) {
  621. // pcolor = color(caxis.toColor(y)).rgba;
  622. // inst.colorData.push(color[0] / 255.0);
  623. // inst.colorData.push(color[1] / 255.0);
  624. // inst.colorData.push(color[2] / 255.0);
  625. // inst.colorData.push(color[3]);
  626. // }
  627. lastX = x;
  628. lastY = y;
  629. hadPoints = true;
  630. firstPoint = false;
  631. }
  632. if (settings.debug.showSkipSummary) {
  633. console.log('skipped points:', skipped); // eslint-disable-line no-console
  634. }
  635. /**
  636. * @private
  637. */
  638. function pushSupplementPoint(point, atStart) {
  639. if (!settings.useGPUTranslations) {
  640. inst.skipTranslation = true;
  641. point.x = xAxis.toPixels(point.x, true);
  642. point.y = yAxis.toPixels(point.y, true);
  643. }
  644. // We should only do this for lines, and we should ignore markers
  645. // since there's no point here that would have a marker.
  646. if (atStart) {
  647. data = [point.x, point.y, 0, 2].concat(data);
  648. return;
  649. }
  650. vertice(point.x, point.y, 0, 2);
  651. }
  652. if (!hadPoints &&
  653. connectNulls !== false &&
  654. series.drawMode === 'line_strip') {
  655. if (closestLeft.x < Number.MAX_VALUE) {
  656. // We actually need to push this *before* the complete buffer.
  657. pushSupplementPoint(closestLeft, true);
  658. }
  659. if (closestRight.x > -Number.MAX_VALUE) {
  660. pushSupplementPoint(closestRight);
  661. }
  662. }
  663. closeSegment();
  664. }
  665. /**
  666. * Push a series to the renderer
  667. * If we render the series immediatly, we don't have to loop later
  668. * @private
  669. * @param s {Highchart.Series} - the series to push
  670. */
  671. function pushSeries(s) {
  672. if (series.length > 0) {
  673. // series[series.length - 1].to = data.length;
  674. if (series[series.length - 1].hasMarkers) {
  675. series[series.length - 1].markerTo = markerData.length;
  676. }
  677. }
  678. if (settings.debug.timeSeriesProcessing) {
  679. console.time('building ' + s.type + ' series'); // eslint-disable-line no-console
  680. }
  681. series.push({
  682. segments: [],
  683. // from: data.length,
  684. markerFrom: markerData.length,
  685. // Push RGBA values to this array to use per. point coloring.
  686. // It should be 0-padded, so each component should be pushed in
  687. // succession.
  688. colorData: [],
  689. series: s,
  690. zMin: Number.MAX_VALUE,
  691. zMax: -Number.MAX_VALUE,
  692. hasMarkers: s.options.marker ?
  693. s.options.marker.enabled !== false :
  694. false,
  695. showMarkers: true,
  696. drawMode: {
  697. 'area': 'lines',
  698. 'arearange': 'lines',
  699. 'areaspline': 'line_strip',
  700. 'column': 'lines',
  701. 'columnrange': 'lines',
  702. 'bar': 'lines',
  703. 'line': 'line_strip',
  704. 'scatter': 'points',
  705. 'heatmap': 'triangles',
  706. 'treemap': 'triangles',
  707. 'bubble': 'points'
  708. }[s.type] || 'line_strip'
  709. });
  710. // Add the series data to our buffer(s)
  711. pushSeriesData(s, series[series.length - 1]);
  712. if (settings.debug.timeSeriesProcessing) {
  713. console.timeEnd('building ' + s.type + ' series'); // eslint-disable-line no-console
  714. }
  715. }
  716. /**
  717. * Flush the renderer.
  718. * This removes pushed series and vertices.
  719. * Should be called after clearing and before rendering
  720. * @private
  721. */
  722. function flush() {
  723. series = [];
  724. exports.data = data = [];
  725. markerData = [];
  726. if (vbuffer) {
  727. vbuffer.destroy();
  728. }
  729. }
  730. /**
  731. * Pass x-axis to shader
  732. * @private
  733. * @param axis {Highcharts.Axis} - the x-axis
  734. */
  735. function setXAxis(axis) {
  736. if (!shader) {
  737. return;
  738. }
  739. shader.setUniform('xAxisTrans', axis.transA);
  740. shader.setUniform('xAxisMin', axis.min);
  741. shader.setUniform('xAxisMinPad', axis.minPixelPadding);
  742. shader.setUniform('xAxisPointRange', axis.pointRange);
  743. shader.setUniform('xAxisLen', axis.len);
  744. shader.setUniform('xAxisPos', axis.pos);
  745. shader.setUniform('xAxisCVSCoord', (!axis.horiz));
  746. shader.setUniform('xAxisIsLog', (!!axis.logarithmic));
  747. shader.setUniform('xAxisReversed', (!!axis.reversed));
  748. }
  749. /**
  750. * Pass y-axis to shader
  751. * @private
  752. * @param axis {Highcharts.Axis} - the y-axis
  753. */
  754. function setYAxis(axis) {
  755. if (!shader) {
  756. return;
  757. }
  758. shader.setUniform('yAxisTrans', axis.transA);
  759. shader.setUniform('yAxisMin', axis.min);
  760. shader.setUniform('yAxisMinPad', axis.minPixelPadding);
  761. shader.setUniform('yAxisPointRange', axis.pointRange);
  762. shader.setUniform('yAxisLen', axis.len);
  763. shader.setUniform('yAxisPos', axis.pos);
  764. shader.setUniform('yAxisCVSCoord', (!axis.horiz));
  765. shader.setUniform('yAxisIsLog', (!!axis.logarithmic));
  766. shader.setUniform('yAxisReversed', (!!axis.reversed));
  767. }
  768. /**
  769. * Set the translation threshold
  770. * @private
  771. * @param has {boolean} - has threshold flag
  772. * @param translation {Float} - the threshold
  773. */
  774. function setThreshold(has, translation) {
  775. shader.setUniform('hasThreshold', has);
  776. shader.setUniform('translatedThreshold', translation);
  777. }
  778. /**
  779. * Render the data
  780. * This renders all pushed series.
  781. * @private
  782. */
  783. function render(chart) {
  784. if (chart) {
  785. if (!chart.chartHeight || !chart.chartWidth) {
  786. // chart.setChartSize();
  787. }
  788. width = chart.chartWidth || 800;
  789. height = chart.chartHeight || 400;
  790. }
  791. else {
  792. return false;
  793. }
  794. if (!gl || !width || !height || !shader) {
  795. return false;
  796. }
  797. if (settings.debug.timeRendering) {
  798. console.time('gl rendering'); // eslint-disable-line no-console
  799. }
  800. gl.canvas.width = width;
  801. gl.canvas.height = height;
  802. shader.bind();
  803. gl.viewport(0, 0, width, height);
  804. shader.setPMatrix(orthoMatrix(width, height));
  805. if (settings.lineWidth > 1 && !H.isMS) {
  806. gl.lineWidth(settings.lineWidth);
  807. }
  808. vbuffer.build(exports.data, 'aVertexPosition', 4);
  809. vbuffer.bind();
  810. shader.setInverted(chart.inverted);
  811. // Render the series
  812. series.forEach(function (s, si) {
  813. var options = s.series.options, shapeOptions = options.marker, sindex, lineWidth = (typeof options.lineWidth !== 'undefined' ?
  814. options.lineWidth :
  815. 1), threshold = options.threshold, hasThreshold = isNumber(threshold), yBottom = s.series.yAxis.getThreshold(threshold), translatedThreshold = yBottom, cbuffer, showMarkers = pick(options.marker ? options.marker.enabled : null, s.series.xAxis.isRadial ? true : null, s.series.closestPointRangePx >
  816. 2 * ((options.marker ?
  817. options.marker.radius :
  818. 10) || 10)), fillColor, shapeTexture = textureHandles[(shapeOptions && shapeOptions.symbol) ||
  819. s.series.symbol] || textureHandles.circle, scolor = [];
  820. if (s.segments.length === 0 ||
  821. (s.segmentslength &&
  822. s.segments[0].from === s.segments[0].to)) {
  823. return;
  824. }
  825. if (shapeTexture.isReady) {
  826. gl.bindTexture(gl.TEXTURE_2D, shapeTexture.handle);
  827. shader.setTexture(shapeTexture.handle);
  828. }
  829. if (chart.styledMode) {
  830. fillColor = (s.series.markerGroup &&
  831. s.series.markerGroup.getStyle('fill'));
  832. }
  833. else {
  834. fillColor =
  835. (s.drawMode === 'points' && // #14260
  836. s.series.pointAttribs &&
  837. s.series.pointAttribs().fill) ||
  838. s.series.color;
  839. if (options.colorByPoint) {
  840. fillColor = s.series.chart.options.colors[si];
  841. }
  842. }
  843. if (s.series.fillOpacity && options.fillOpacity) {
  844. fillColor = new Color(fillColor).setOpacity(pick(options.fillOpacity, 1.0)).get();
  845. }
  846. scolor = color(fillColor).rgba;
  847. if (!settings.useAlpha) {
  848. scolor[3] = 1.0;
  849. }
  850. // This is very much temporary
  851. if (s.drawMode === 'lines' &&
  852. settings.useAlpha &&
  853. scolor[3] < 1) {
  854. scolor[3] /= 10;
  855. }
  856. // Blending
  857. if (options.boostBlending === 'add') {
  858. gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  859. gl.blendEquation(gl.FUNC_ADD);
  860. }
  861. else if (options.boostBlending === 'mult' ||
  862. options.boostBlending === 'multiply') {
  863. gl.blendFunc(gl.DST_COLOR, gl.ZERO);
  864. }
  865. else if (options.boostBlending === 'darken') {
  866. gl.blendFunc(gl.ONE, gl.ONE);
  867. gl.blendEquation(gl.FUNC_MIN);
  868. }
  869. else {
  870. // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  871. // gl.blendEquation(gl.FUNC_ADD);
  872. gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  873. }
  874. shader.reset();
  875. // If there are entries in the colorData buffer, build and bind it.
  876. if (s.colorData.length > 0) {
  877. shader.setUniform('hasColor', 1.0);
  878. cbuffer = GLVertexBuffer(gl, shader); // eslint-disable-line new-cap
  879. cbuffer.build(s.colorData, 'aColor', 4);
  880. cbuffer.bind();
  881. }
  882. // Set series specific uniforms
  883. shader.setColor(scolor);
  884. setXAxis(s.series.xAxis);
  885. setYAxis(s.series.yAxis);
  886. setThreshold(hasThreshold, translatedThreshold);
  887. if (s.drawMode === 'points') {
  888. if (options.marker && isNumber(options.marker.radius)) {
  889. shader.setPointSize(options.marker.radius * 2.0);
  890. }
  891. else {
  892. shader.setPointSize(1);
  893. }
  894. }
  895. // If set to true, the toPixels translations in the shader
  896. // is skipped, i.e it's assumed that the value is a pixel coord.
  897. shader.setSkipTranslation(s.skipTranslation);
  898. if (s.series.type === 'bubble') {
  899. shader.setBubbleUniforms(s.series, s.zMin, s.zMax);
  900. }
  901. shader.setDrawAsCircle(asCircle[s.series.type] || false);
  902. // Do the actual rendering
  903. // If the line width is < 0, skip rendering of the lines. See #7833.
  904. if (lineWidth > 0 || s.drawMode !== 'line_strip') {
  905. for (sindex = 0; sindex < s.segments.length; sindex++) {
  906. // if (s.segments[sindex].from < s.segments[sindex].to) {
  907. vbuffer.render(s.segments[sindex].from, s.segments[sindex].to, s.drawMode);
  908. // }
  909. }
  910. }
  911. if (s.hasMarkers && showMarkers) {
  912. if (options.marker && isNumber(options.marker.radius)) {
  913. shader.setPointSize(options.marker.radius * 2.0);
  914. }
  915. else {
  916. shader.setPointSize(10);
  917. }
  918. shader.setDrawAsCircle(true);
  919. for (sindex = 0; sindex < s.segments.length; sindex++) {
  920. // if (s.segments[sindex].from < s.segments[sindex].to) {
  921. vbuffer.render(s.segments[sindex].from, s.segments[sindex].to, 'POINTS');
  922. // }
  923. }
  924. }
  925. });
  926. if (settings.debug.timeRendering) {
  927. console.timeEnd('gl rendering'); // eslint-disable-line no-console
  928. }
  929. if (postRenderCallback) {
  930. postRenderCallback();
  931. }
  932. flush();
  933. }
  934. /**
  935. * Render the data when ready
  936. * @private
  937. */
  938. function renderWhenReady(chart) {
  939. clear();
  940. if (chart.renderer.forExport) {
  941. return render(chart);
  942. }
  943. if (isInited) {
  944. render(chart);
  945. }
  946. else {
  947. setTimeout(function () {
  948. renderWhenReady(chart);
  949. }, 1);
  950. }
  951. }
  952. /**
  953. * Set the viewport size in pixels
  954. * Creates an orthographic perspective matrix and applies it.
  955. * @private
  956. * @param w {Integer} - the width of the viewport
  957. * @param h {Integer} - the height of the viewport
  958. */
  959. function setSize(w, h) {
  960. // Skip if there's no change, or if we have no valid shader
  961. if ((width === w && height === h) || !shader) {
  962. return;
  963. }
  964. width = w;
  965. height = h;
  966. shader.bind();
  967. shader.setPMatrix(orthoMatrix(width, height));
  968. }
  969. /**
  970. * Init OpenGL
  971. * @private
  972. * @param canvas {HTMLCanvas} - the canvas to render to
  973. */
  974. function init(canvas, noFlush) {
  975. var i = 0, contexts = [
  976. 'webgl',
  977. 'experimental-webgl',
  978. 'moz-webgl',
  979. 'webkit-3d'
  980. ];
  981. isInited = false;
  982. if (!canvas) {
  983. return false;
  984. }
  985. if (settings.debug.timeSetup) {
  986. console.time('gl setup'); // eslint-disable-line no-console
  987. }
  988. for (; i < contexts.length; i++) {
  989. gl = canvas.getContext(contexts[i], {
  990. // premultipliedAlpha: false
  991. });
  992. if (gl) {
  993. break;
  994. }
  995. }
  996. if (gl) {
  997. if (!noFlush) {
  998. flush();
  999. }
  1000. }
  1001. else {
  1002. return false;
  1003. }
  1004. gl.enable(gl.BLEND);
  1005. // gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  1006. gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  1007. gl.disable(gl.DEPTH_TEST);
  1008. // gl.depthMask(gl.FALSE);
  1009. gl.depthFunc(gl.LESS);
  1010. shader = GLShader(gl); // eslint-disable-line new-cap
  1011. if (!shader) {
  1012. // We need to abort, there's no shader context
  1013. return false;
  1014. }
  1015. vbuffer = GLVertexBuffer(gl, shader); // eslint-disable-line new-cap
  1016. /**
  1017. * @private
  1018. */
  1019. function createTexture(name, fn) {
  1020. var props = {
  1021. isReady: false,
  1022. texture: doc.createElement('canvas'),
  1023. handle: gl.createTexture()
  1024. }, ctx = props.texture.getContext('2d');
  1025. textureHandles[name] = props;
  1026. props.texture.width = 512;
  1027. props.texture.height = 512;
  1028. ctx.mozImageSmoothingEnabled = false;
  1029. ctx.webkitImageSmoothingEnabled = false;
  1030. ctx.msImageSmoothingEnabled = false;
  1031. ctx.imageSmoothingEnabled = false;
  1032. ctx.strokeStyle = 'rgba(255, 255, 255, 0)';
  1033. ctx.fillStyle = '#FFF';
  1034. fn(ctx);
  1035. try {
  1036. gl.activeTexture(gl.TEXTURE0);
  1037. gl.bindTexture(gl.TEXTURE_2D, props.handle);
  1038. // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
  1039. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, props.texture);
  1040. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  1041. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  1042. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  1043. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  1044. // gl.generateMipmap(gl.TEXTURE_2D);
  1045. gl.bindTexture(gl.TEXTURE_2D, null);
  1046. props.isReady = true;
  1047. }
  1048. catch (e) {
  1049. // silent error
  1050. }
  1051. }
  1052. // Circle shape
  1053. createTexture('circle', function (ctx) {
  1054. ctx.beginPath();
  1055. ctx.arc(256, 256, 256, 0, 2 * Math.PI);
  1056. ctx.stroke();
  1057. ctx.fill();
  1058. });
  1059. // Square shape
  1060. createTexture('square', function (ctx) {
  1061. ctx.fillRect(0, 0, 512, 512);
  1062. });
  1063. // Diamond shape
  1064. createTexture('diamond', function (ctx) {
  1065. ctx.beginPath();
  1066. ctx.moveTo(256, 0);
  1067. ctx.lineTo(512, 256);
  1068. ctx.lineTo(256, 512);
  1069. ctx.lineTo(0, 256);
  1070. ctx.lineTo(256, 0);
  1071. ctx.fill();
  1072. });
  1073. // Triangle shape
  1074. createTexture('triangle', function (ctx) {
  1075. ctx.beginPath();
  1076. ctx.moveTo(0, 512);
  1077. ctx.lineTo(256, 0);
  1078. ctx.lineTo(512, 512);
  1079. ctx.lineTo(0, 512);
  1080. ctx.fill();
  1081. });
  1082. // Triangle shape (rotated)
  1083. createTexture('triangle-down', function (ctx) {
  1084. ctx.beginPath();
  1085. ctx.moveTo(0, 0);
  1086. ctx.lineTo(256, 512);
  1087. ctx.lineTo(512, 0);
  1088. ctx.lineTo(0, 0);
  1089. ctx.fill();
  1090. });
  1091. isInited = true;
  1092. if (settings.debug.timeSetup) {
  1093. console.timeEnd('gl setup'); // eslint-disable-line no-console
  1094. }
  1095. return true;
  1096. }
  1097. /**
  1098. * Check if we have a valid OGL context
  1099. * @private
  1100. * @returns {Boolean} - true if the context is valid
  1101. */
  1102. function valid() {
  1103. return gl !== false;
  1104. }
  1105. /**
  1106. * Check if the renderer has been initialized
  1107. * @private
  1108. * @returns {Boolean} - true if it has, false if not
  1109. */
  1110. function inited() {
  1111. return isInited;
  1112. }
  1113. /**
  1114. * @private
  1115. */
  1116. function destroy() {
  1117. flush();
  1118. vbuffer.destroy();
  1119. shader.destroy();
  1120. if (gl) {
  1121. objectEach(textureHandles, function (texture) {
  1122. if (texture.handle) {
  1123. gl.deleteTexture(texture.handle);
  1124. }
  1125. });
  1126. gl.canvas.width = 1;
  1127. gl.canvas.height = 1;
  1128. }
  1129. }
  1130. // /////////////////////////////////////////////////////////////////////////
  1131. exports = {
  1132. allocateBufferForSingleSeries: allocateBufferForSingleSeries,
  1133. pushSeries: pushSeries,
  1134. setSize: setSize,
  1135. inited: inited,
  1136. setThreshold: setThreshold,
  1137. init: init,
  1138. render: renderWhenReady,
  1139. settings: settings,
  1140. valid: valid,
  1141. clear: clear,
  1142. flush: flush,
  1143. setXAxis: setXAxis,
  1144. setYAxis: setYAxis,
  1145. data: data,
  1146. gl: getGL,
  1147. allocateBuffer: allocateBuffer,
  1148. destroy: destroy,
  1149. setOptions: setOptions
  1150. };
  1151. return exports;
  1152. }
  1153. export default GLRenderer;