wgl-renderer.js 41 KB

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