boost-canvas.src.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Boost module
  4. *
  5. * (c) 2010-2017 Highsoft AS
  6. * Author: Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. module.exports = factory;
  14. } else {
  15. factory(Highcharts);
  16. }
  17. }(function (Highcharts) {
  18. (function (H) {
  19. /**
  20. * License: www.highcharts.com/license
  21. * Author: Torstein Honsi, Christer Vasseng
  22. *
  23. * This module serves as a fallback for the Boost module in IE9 and IE10. Newer
  24. * browsers support WebGL which is faster.
  25. *
  26. * It is recommended to include this module in conditional comments targeting
  27. * IE9 and IE10.
  28. */
  29. var win = H.win,
  30. doc = win.document,
  31. noop = function () {},
  32. Color = H.Color,
  33. Series = H.Series,
  34. seriesTypes = H.seriesTypes,
  35. each = H.each,
  36. extend = H.extend,
  37. addEvent = H.addEvent,
  38. fireEvent = H.fireEvent,
  39. isNumber = H.isNumber,
  40. merge = H.merge,
  41. pick = H.pick,
  42. wrap = H.wrap,
  43. CHUNK_SIZE = 50000,
  44. destroyLoadingDiv;
  45. H.initCanvasBoost = function () {
  46. if (H.seriesTypes.heatmap) {
  47. H.wrap(H.seriesTypes.heatmap.prototype, 'drawPoints', function () {
  48. var ctx = this.getContext();
  49. if (ctx) {
  50. // draw the columns
  51. each(this.points, function (point) {
  52. var plotY = point.plotY,
  53. shapeArgs,
  54. pointAttr;
  55. if (
  56. plotY !== undefined &&
  57. !isNaN(plotY) &&
  58. point.y !== null
  59. ) {
  60. shapeArgs = point.shapeArgs;
  61. pointAttr = point.series.pointAttribs(point);
  62. ctx.fillStyle = pointAttr.fill;
  63. ctx.fillRect(
  64. shapeArgs.x,
  65. shapeArgs.y,
  66. shapeArgs.width,
  67. shapeArgs.height
  68. );
  69. }
  70. });
  71. this.canvasToSVG();
  72. } else {
  73. this.chart.showLoading(
  74. 'Your browser doesn\'t support HTML5 canvas, <br>' +
  75. 'please use a modern browser');
  76. // Uncomment this to provide low-level (slow) support in oldIE.
  77. // It will cause script errors on charts with more than a few
  78. // thousand points.
  79. // arguments[0].call(this);
  80. }
  81. });
  82. }
  83. H.extend(Series.prototype, {
  84. /**
  85. * Create a hidden canvas to draw the graph on. The contents is later
  86. * copied over to an SVG image element.
  87. */
  88. getContext: function () {
  89. var chart = this.chart,
  90. width = chart.chartWidth,
  91. height = chart.chartHeight,
  92. targetGroup = chart.seriesGroup || this.group,
  93. target = this,
  94. ctx,
  95. swapXY = function (proceed, x, y, a, b, c, d) {
  96. proceed.call(this, y, x, a, b, c, d);
  97. };
  98. if (chart.isChartSeriesBoosting()) {
  99. target = chart;
  100. targetGroup = chart.seriesGroup;
  101. }
  102. ctx = target.ctx;
  103. if (!target.canvas) {
  104. target.canvas = doc.createElement('canvas');
  105. target.renderTarget = chart.renderer.image(
  106. '',
  107. 0,
  108. 0,
  109. width,
  110. height
  111. )
  112. .addClass('highcharts-boost-canvas')
  113. .add(targetGroup);
  114. target.ctx = ctx = target.canvas.getContext('2d');
  115. if (chart.inverted) {
  116. each(['moveTo', 'lineTo', 'rect', 'arc'], function (fn) {
  117. wrap(ctx, fn, swapXY);
  118. });
  119. }
  120. target.boostCopy = function () {
  121. target.renderTarget.attr({
  122. href: target.canvas.toDataURL('image/png')
  123. });
  124. };
  125. target.boostClear = function () {
  126. ctx.clearRect(
  127. 0,
  128. 0,
  129. target.canvas.width,
  130. target.canvas.height
  131. );
  132. if (target === this) {
  133. target.renderTarget.attr({ href: '' });
  134. }
  135. };
  136. target.boostClipRect = chart.renderer.clipRect();
  137. target.renderTarget.clip(target.boostClipRect);
  138. } else if (!(target instanceof H.Chart)) {
  139. // ctx.clearRect(0, 0, width, height);
  140. }
  141. if (target.canvas.width !== width) {
  142. target.canvas.width = width;
  143. }
  144. if (target.canvas.height !== height) {
  145. target.canvas.height = height;
  146. }
  147. target.renderTarget.attr({
  148. x: 0,
  149. y: 0,
  150. width: width,
  151. height: height,
  152. style: 'pointer-events: none',
  153. href: ''
  154. });
  155. target.boostClipRect.attr(chart.getBoostClipRect(target));
  156. return ctx;
  157. },
  158. /**
  159. * Draw the canvas image inside an SVG image
  160. */
  161. canvasToSVG: function () {
  162. if (!this.chart.isChartSeriesBoosting()) {
  163. if (this.boostCopy || this.chart.boostCopy) {
  164. (this.boostCopy || this.chart.boostCopy)();
  165. }
  166. } else {
  167. if (this.boostClear) {
  168. this.boostClear();
  169. }
  170. }
  171. },
  172. cvsLineTo: function (ctx, clientX, plotY) {
  173. ctx.lineTo(clientX, plotY);
  174. },
  175. renderCanvas: function () {
  176. var series = this,
  177. options = series.options,
  178. chart = series.chart,
  179. xAxis = this.xAxis,
  180. yAxis = this.yAxis,
  181. activeBoostSettings = chart.options.boost || {},
  182. boostSettings = {
  183. timeRendering: activeBoostSettings.timeRendering || false,
  184. timeSeriesProcessing:
  185. activeBoostSettings.timeSeriesProcessing || false,
  186. timeSetup: activeBoostSettings.timeSetup || false
  187. },
  188. ctx,
  189. c = 0,
  190. xData = series.processedXData,
  191. yData = series.processedYData,
  192. rawData = options.data,
  193. xExtremes = xAxis.getExtremes(),
  194. xMin = xExtremes.min,
  195. xMax = xExtremes.max,
  196. yExtremes = yAxis.getExtremes(),
  197. yMin = yExtremes.min,
  198. yMax = yExtremes.max,
  199. pointTaken = {},
  200. lastClientX,
  201. sampling = !!series.sampling,
  202. points,
  203. r = options.marker && options.marker.radius,
  204. cvsDrawPoint = this.cvsDrawPoint,
  205. cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
  206. cvsMarker = r && r <= 1 ?
  207. this.cvsMarkerSquare :
  208. this.cvsMarkerCircle,
  209. strokeBatch = this.cvsStrokeBatch || 1000,
  210. enableMouseTracking = options.enableMouseTracking !== false,
  211. lastPoint,
  212. threshold = options.threshold,
  213. yBottom = yAxis.getThreshold(threshold),
  214. hasThreshold = isNumber(threshold),
  215. translatedThreshold = yBottom,
  216. doFill = this.fill,
  217. isRange = series.pointArrayMap &&
  218. series.pointArrayMap.join(',') === 'low,high',
  219. isStacked = !!options.stacking,
  220. cropStart = series.cropStart || 0,
  221. loadingOptions = chart.options.loading,
  222. requireSorting = series.requireSorting,
  223. wasNull,
  224. connectNulls = options.connectNulls,
  225. useRaw = !xData,
  226. minVal,
  227. maxVal,
  228. minI,
  229. maxI,
  230. kdIndex,
  231. sdata = isStacked ? series.data : (xData || rawData),
  232. fillColor = series.fillOpacity ?
  233. new Color(series.color).setOpacity(
  234. pick(options.fillOpacity, 0.75)
  235. ).get() :
  236. series.color,
  237. stroke = function () {
  238. if (doFill) {
  239. ctx.fillStyle = fillColor;
  240. ctx.fill();
  241. } else {
  242. ctx.strokeStyle = series.color;
  243. ctx.lineWidth = options.lineWidth;
  244. ctx.stroke();
  245. }
  246. },
  247. drawPoint = function (clientX, plotY, yBottom, i) {
  248. if (c === 0) {
  249. ctx.beginPath();
  250. if (cvsLineTo) {
  251. ctx.lineJoin = 'round';
  252. }
  253. }
  254. if (
  255. chart.scroller &&
  256. series.options.className ===
  257. 'highcharts-navigator-series'
  258. ) {
  259. plotY += chart.scroller.top;
  260. if (yBottom) {
  261. yBottom += chart.scroller.top;
  262. }
  263. } else {
  264. plotY += chart.plotTop;
  265. }
  266. clientX += chart.plotLeft;
  267. if (wasNull) {
  268. ctx.moveTo(clientX, plotY);
  269. } else {
  270. if (cvsDrawPoint) {
  271. cvsDrawPoint(
  272. ctx,
  273. clientX,
  274. plotY,
  275. yBottom,
  276. lastPoint
  277. );
  278. } else if (cvsLineTo) {
  279. cvsLineTo(ctx, clientX, plotY);
  280. } else if (cvsMarker) {
  281. cvsMarker.call(series, ctx, clientX, plotY, r, i);
  282. }
  283. }
  284. // We need to stroke the line for every 1000 pixels. It will
  285. // crash the browser memory use if we stroke too
  286. // infrequently.
  287. c = c + 1;
  288. if (c === strokeBatch) {
  289. stroke();
  290. c = 0;
  291. }
  292. // Area charts need to keep track of the last point
  293. lastPoint = {
  294. clientX: clientX,
  295. plotY: plotY,
  296. yBottom: yBottom
  297. };
  298. },
  299. addKDPoint = function (clientX, plotY, i) {
  300. // Avoid more string concatination than required
  301. kdIndex = clientX + ',' + plotY;
  302. // The k-d tree requires series points. Reduce the amount of
  303. // points, since the time to build the tree increases
  304. // exponentially.
  305. if (enableMouseTracking && !pointTaken[kdIndex]) {
  306. pointTaken[kdIndex] = true;
  307. if (chart.inverted) {
  308. clientX = xAxis.len - clientX;
  309. plotY = yAxis.len - plotY;
  310. }
  311. points.push({
  312. clientX: clientX,
  313. plotX: clientX,
  314. plotY: plotY,
  315. i: cropStart + i
  316. });
  317. }
  318. };
  319. if (this.renderTarget) {
  320. this.renderTarget.attr({ 'href': '' });
  321. }
  322. // If we are zooming out from SVG mode, destroy the graphics
  323. if (this.points || this.graph) {
  324. this.destroyGraphics();
  325. }
  326. // The group
  327. series.plotGroup(
  328. 'group',
  329. 'series',
  330. series.visible ? 'visible' : 'hidden',
  331. options.zIndex,
  332. chart.seriesGroup
  333. );
  334. series.markerGroup = series.group;
  335. addEvent(series, 'destroy', function () { // Prevent destroy twice
  336. series.markerGroup = null;
  337. });
  338. points = this.points = [];
  339. ctx = this.getContext();
  340. series.buildKDTree = noop; // Do not start building while drawing
  341. if (this.boostClear) {
  342. this.boostClear();
  343. }
  344. // if (this.canvas) {
  345. // ctx.clearRect(
  346. // 0,
  347. // 0,
  348. // this.canvas.width,
  349. // this.canvas.height
  350. // );
  351. // }
  352. if (!this.visible) {
  353. return;
  354. }
  355. // Display a loading indicator
  356. if (rawData.length > 99999) {
  357. chart.options.loading = merge(loadingOptions, {
  358. labelStyle: {
  359. backgroundColor: H.color('#ffffff')
  360. .setOpacity(0.75).get(),
  361. padding: '1em',
  362. borderRadius: '0.5em'
  363. },
  364. style: {
  365. backgroundColor: 'none',
  366. opacity: 1
  367. }
  368. });
  369. H.clearTimeout(destroyLoadingDiv);
  370. chart.showLoading('Drawing...');
  371. chart.options.loading = loadingOptions; // reset
  372. }
  373. if (boostSettings.timeRendering) {
  374. console.time('canvas rendering'); // eslint-disable-line no-console
  375. }
  376. // Loop over the points
  377. H.eachAsync(sdata, function (d, i) {
  378. var x,
  379. y,
  380. clientX,
  381. plotY,
  382. isNull,
  383. low,
  384. isNextInside = false,
  385. isPrevInside = false,
  386. nx = false,
  387. px = false,
  388. chartDestroyed = typeof chart.index === 'undefined',
  389. isYInside = true;
  390. if (!chartDestroyed) {
  391. if (useRaw) {
  392. x = d[0];
  393. y = d[1];
  394. if (sdata[i + 1]) {
  395. nx = sdata[i + 1][0];
  396. }
  397. if (sdata[i - 1]) {
  398. px = sdata[i - 1][0];
  399. }
  400. } else {
  401. x = d;
  402. y = yData[i];
  403. if (sdata[i + 1]) {
  404. nx = sdata[i + 1];
  405. }
  406. if (sdata[i - 1]) {
  407. px = sdata[i - 1];
  408. }
  409. }
  410. if (nx && nx >= xMin && nx <= xMax) {
  411. isNextInside = true;
  412. }
  413. if (px && px >= xMin && px <= xMax) {
  414. isPrevInside = true;
  415. }
  416. // Resolve low and high for range series
  417. if (isRange) {
  418. if (useRaw) {
  419. y = d.slice(1, 3);
  420. }
  421. low = y[0];
  422. y = y[1];
  423. } else if (isStacked) {
  424. x = d.x;
  425. y = d.stackY;
  426. low = y - d.y;
  427. }
  428. isNull = y === null;
  429. // Optimize for scatter zooming
  430. if (!requireSorting) {
  431. isYInside = y >= yMin && y <= yMax;
  432. }
  433. if (!isNull &&
  434. (
  435. (x >= xMin && x <= xMax && isYInside) ||
  436. (isNextInside || isPrevInside)
  437. )) {
  438. clientX = Math.round(xAxis.toPixels(x, true));
  439. if (sampling) {
  440. if (minI === undefined || clientX === lastClientX) {
  441. if (!isRange) {
  442. low = y;
  443. }
  444. if (maxI === undefined || y > maxVal) {
  445. maxVal = y;
  446. maxI = i;
  447. }
  448. if (minI === undefined || low < minVal) {
  449. minVal = low;
  450. minI = i;
  451. }
  452. }
  453. // Add points and reset
  454. if (clientX !== lastClientX) {
  455. if (minI !== undefined) { // maxI also a number
  456. plotY = yAxis.toPixels(maxVal, true);
  457. yBottom = yAxis.toPixels(minVal, true);
  458. drawPoint(
  459. clientX,
  460. hasThreshold ?
  461. Math.min(
  462. plotY,
  463. translatedThreshold
  464. ) : plotY,
  465. hasThreshold ?
  466. Math.max(
  467. yBottom,
  468. translatedThreshold
  469. ) : yBottom,
  470. i
  471. );
  472. addKDPoint(clientX, plotY, maxI);
  473. if (yBottom !== plotY) {
  474. addKDPoint(clientX, yBottom, minI);
  475. }
  476. }
  477. minI = maxI = undefined;
  478. lastClientX = clientX;
  479. }
  480. } else {
  481. plotY = Math.round(yAxis.toPixels(y, true));
  482. drawPoint(clientX, plotY, yBottom, i);
  483. addKDPoint(clientX, plotY, i);
  484. }
  485. }
  486. wasNull = isNull && !connectNulls;
  487. if (i % CHUNK_SIZE === 0) {
  488. if (series.boostCopy || series.chart.boostCopy) {
  489. (series.boostCopy || series.chart.boostCopy)();
  490. }
  491. }
  492. }
  493. return !chartDestroyed;
  494. }, function () {
  495. var loadingDiv = chart.loadingDiv,
  496. loadingShown = chart.loadingShown;
  497. stroke();
  498. // if (series.boostCopy || series.chart.boostCopy) {
  499. // (series.boostCopy || series.chart.boostCopy)();
  500. // }
  501. series.canvasToSVG();
  502. if (boostSettings.timeRendering) {
  503. console.timeEnd('canvas rendering'); // eslint-disable-line no-console
  504. }
  505. fireEvent(series, 'renderedCanvas');
  506. // Do not use chart.hideLoading, as it runs JS animation and
  507. // will be blocked by buildKDTree. CSS animation looks good, but
  508. // then it must be deleted in timeout. If we add the module to
  509. // core, change hideLoading so we can skip this block.
  510. if (loadingShown) {
  511. extend(loadingDiv.style, {
  512. transition: 'opacity 250ms',
  513. opacity: 0
  514. });
  515. chart.loadingShown = false;
  516. destroyLoadingDiv = setTimeout(function () {
  517. if (loadingDiv.parentNode) { // In exporting it is falsy
  518. loadingDiv.parentNode.removeChild(loadingDiv);
  519. }
  520. chart.loadingDiv = chart.loadingSpan = null;
  521. }, 250);
  522. }
  523. // Go back to prototype, ready to build
  524. delete series.buildKDTree;
  525. series.buildKDTree();
  526. // Don't do async on export, the exportChart, getSVGForExport and
  527. // getSVG methods are not chained for it.
  528. }, chart.renderer.forExport ? Number.MAX_VALUE : undefined);
  529. }
  530. });
  531. seriesTypes.scatter.prototype.cvsMarkerCircle = function (
  532. ctx,
  533. clientX,
  534. plotY,
  535. r
  536. ) {
  537. ctx.moveTo(clientX, plotY);
  538. ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
  539. };
  540. // Rect is twice as fast as arc, should be used for small markers
  541. seriesTypes.scatter.prototype.cvsMarkerSquare = function (
  542. ctx,
  543. clientX,
  544. plotY,
  545. r
  546. ) {
  547. ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
  548. };
  549. seriesTypes.scatter.prototype.fill = true;
  550. if (seriesTypes.bubble) {
  551. seriesTypes.bubble.prototype.cvsMarkerCircle = function (
  552. ctx,
  553. clientX,
  554. plotY,
  555. r,
  556. i
  557. ) {
  558. ctx.moveTo(clientX, plotY);
  559. ctx.arc(
  560. clientX,
  561. plotY,
  562. this.radii && this.radii[i], 0, 2 * Math.PI,
  563. false
  564. );
  565. };
  566. seriesTypes.bubble.prototype.cvsStrokeBatch = 1;
  567. }
  568. extend(seriesTypes.area.prototype, {
  569. cvsDrawPoint: function (ctx, clientX, plotY, yBottom, lastPoint) {
  570. if (lastPoint && clientX !== lastPoint.clientX) {
  571. ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
  572. ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
  573. ctx.lineTo(clientX, plotY);
  574. ctx.lineTo(clientX, yBottom);
  575. }
  576. },
  577. fill: true,
  578. fillOpacity: true,
  579. sampling: true
  580. });
  581. extend(seriesTypes.column.prototype, {
  582. cvsDrawPoint: function (ctx, clientX, plotY, yBottom) {
  583. ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
  584. },
  585. fill: true,
  586. sampling: true
  587. });
  588. H.Chart.prototype.callbacks.push(function (chart) {
  589. function canvasToSVG() {
  590. if (chart.boostCopy) {
  591. chart.boostCopy();
  592. }
  593. }
  594. function clear() {
  595. if (chart.renderTarget) {
  596. chart.renderTarget.attr({ href: '' });
  597. }
  598. if (chart.canvas) {
  599. chart.canvas.getContext('2d').clearRect(
  600. 0,
  601. 0,
  602. chart.canvas.width,
  603. chart.canvas.height
  604. );
  605. }
  606. }
  607. addEvent(chart, 'predraw', clear);
  608. addEvent(chart, 'render', canvasToSVG);
  609. });
  610. };
  611. }(Highcharts));
  612. }));