WGLRenderer.js 41 KB

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