text: /**
   * @license Highcharts JS v5.0.9 (2017-03-08)
   * Boost module
   *
   * (c) 2010-2016 Highsoft AS
   * Author: Torstein Honsi
   *
   * License: www.highcharts.com/license
   */
  'use strict';
  (function(factory) {
      if (typeof module === 'object' && module.exports) {
          module.exports = factory;
      } else {
          factory(Highcharts);
      }
  }(function(Highcharts) {
      (function(H) {
          /**
           * License: www.highcharts.com/license
           * Author: Torstein Honsi, Christer Vasseng
           * 
           * This is an experimental Highcharts module that draws long data series on a canvas
           * in order to increase performance of the initial load time and tooltip responsiveness.
           *
           * Compatible with HTML5 canvas compatible browsers (not IE < 9).
           *
           *
           * 
           * Development plan
           * - Column range.
           * - Heatmap. Modify the heatmap-canvas demo so that it uses this module.
           * - Treemap.
           * - Check how it works with Highstock and data grouping. Currently it only works when navigator.adaptToUpdatedData
           *   is false. It is also recommended to set scrollbar.liveRedraw to false.
           * - Check inverted charts.
           * - Check reversed axes.
           * - Chart callback should be async after last series is drawn. (But not necessarily, we don't do
          	 that with initial series animation).
           * - Cache full-size image so we don't have to redraw on hide/show and zoom up. But k-d-tree still
           *   needs to be built.
           * - Test IE9 and IE10.
           * - Stacking is not perhaps not correct since it doesn't use the translation given in 
           *   the translate method. If this gets to complicated, a possible way out would be to 
           *   have a simplified renderCanvas method that simply draws the areaPath on a canvas.
           *
           * If this module is taken in as part of the core
           * - All the loading logic should be merged with core. Update styles in the core.
           * - Most of the method wraps should probably be added directly in parent methods.
           *
           * Notes for boost mode
           * - Area lines are not drawn
           * - Point markers are not drawn on line-type series
           * - Lines are not drawn on scatter charts
           * - Zones and negativeColor don't work
           * - Initial point colors aren't rendered
           * - Columns are always one pixel wide. Don't set the threshold too low.
           *
           * Optimizing tips for users
           * - For scatter plots, use a marker.radius of 1 or less. It results in a rectangle being drawn, which is 
           *   considerably faster than a circle.
           * - Set extremes (min, max) explicitly on the axes in order for Highcharts to avoid computing extremes.
           * - Set enableMouseTracking to false on the series to improve total rendering time.
           * - The default threshold is set based on one series. If you have multiple, dense series, the combined
           *   number of points drawn gets higher, and you may want to set the threshold lower in order to 
           *   use optimizations.
           */
  
  
          var win = H.win,
              doc = win.document,
              noop = function() {},
              Color = H.Color,
              Series = H.Series,
              seriesTypes = H.seriesTypes,
              each = H.each,
              extend = H.extend,
              addEvent = H.addEvent,
              fireEvent = H.fireEvent,
              grep = H.grep,
              isNumber = H.isNumber,
              merge = H.merge,
              pick = H.pick,
              wrap = H.wrap,
              plotOptions = H.getOptions().plotOptions,
              CHUNK_SIZE = 50000,
              destroyLoadingDiv;
  
          function eachAsync(arr, fn, finalFunc, chunkSize, i) {
              i = i || 0;
              chunkSize = chunkSize || CHUNK_SIZE;
  
              var threshold = i + chunkSize,
                  proceed = true;
  
              while (proceed && i < threshold && i < arr.length) {
                  proceed = fn(arr[i], i);
                  i = i + 1;
              }
              if (proceed) {
                  if (i < arr.length) {
                      setTimeout(function() {
                          eachAsync(arr, fn, finalFunc, chunkSize, i);
                      });
                  } else if (finalFunc) {
                      finalFunc();
                  }
              }
          }
  
          /*
           * Returns true if the chart is in series boost mode
           * @param chart {Highchart.Chart} - the chart to check
           * @returns {Boolean} - true if the chart is in series boost mode
           */
          function isChartSeriesBoosting(chart) {
              var threshold = (chart.options.boost ? chart.options.boost.seriesThreshold : 0) ||
                  chart.options.chart.seriesBoostThreshold ||
                  10;
  
              return chart.series.length >= threshold;
          }
  
          H.initCanvasBoost = function() {
  
              if (H.seriesTypes.heatmap) {
                  H.wrap(H.seriesTypes.heatmap.prototype, 'drawPoints', function() {
                      var ctx = this.getContext();
                      if (ctx) {
  
                          // draw the columns
                          each(this.points, function(point) {
                              var plotY = point.plotY,
                                  shapeArgs,
                                  pointAttr;
  
                              if (plotY !== undefined && !isNaN(plotY) && point.y !== null) {
                                  shapeArgs = point.shapeArgs;
  
                                  pointAttr = (point.pointAttr && point.pointAttr['']) || point.series.pointAttribs(point);
  
                                  ctx.fillStyle = pointAttr.fill;
                                  ctx.fillRect(shapeArgs.x, shapeArgs.y, shapeArgs.width, shapeArgs.height);
                              }
                          });
  
                          this.canvasToSVG();
  
                      } else {
                          this.chart.showLoading('Your browser doesn\'t support HTML5 canvas, <br>please use a modern browser');
  
                          // Uncomment this to provide low-level (slow) support in oldIE. It will cause script errors on
                          // charts with more than a few thousand points.
                          // arguments[0].call(this);
                      }
                  });
              }
  
  
              /**
               * Override a bunch of methods the same way. If the number of points is below the threshold,
               * run the original method. If not, check for a canvas version or do nothing.
               */
              // each(['translate', 'generatePoints', 'drawTracker', 'drawPoints', 'render'], function (method) {
              // 	function branch(proceed) {
              // 		var letItPass = this.options.stacking && (method === 'translate' || method === 'generatePoints');
              // 		if (((this.processedXData || this.options.data).length < (this.options.boostThreshold || Number.MAX_VALUE) ||
              // 				letItPass) || !isChartSeriesBoosting(this.chart)) {
  
              // 			// Clear image
              // 			if (method === 'render' && this.image) {
              // 				this.image.attr({ href: '' });
              // 				this.animate = null; // We're zooming in, don't run animation
              // 			}
  
              // 			proceed.call(this);
  
              // 		// If a canvas version of the method exists, like renderCanvas(), run
              // 		} else if (this[method + 'Canvas']) {
  
              // 			this[method + 'Canvas']();
              // 		}
              // 	}
              // 	wrap(Series.prototype, method, branch);
  
              // 	// A special case for some types - its translate method is already wrapped
              // 	if (method === 'translate') {
              // 		each(['arearange', 'bubble', 'column'], function (type) {
              // 			if (seriesTypes[type]) {
              // 				wrap(seriesTypes[type].prototype, method, branch);
              // 			}
              // 		});
              // 	}
              // });
  
              H.extend(Series.prototype, {
                  directTouch: false,
                  pointRange: 0,
                  allowDG: false, // No data grouping, let boost handle large data 
                  hasExtremes: function(checkX) {
                      var options = this.options,
                          data = options.data,
                          xAxis = this.xAxis && this.xAxis.options,
                          yAxis = this.yAxis && this.yAxis.options;
                      return data.length > (options.boostThreshold || Number.MAX_VALUE) && isNumber(yAxis.min) && isNumber(yAxis.max) &&
                          (!checkX || (isNumber(xAxis.min) && isNumber(xAxis.max)));
                  },
  
                  /**
                   * If implemented in the core, parts of this can probably be shared with other similar
                   * methods in Highcharts.
                   */
                  destroyGraphics: function() {
                      var series = this,
                          points = this.points,
                          point,
                          i;
  
                      if (points) {
                          for (i = 0; i < points.length; i = i + 1) {
                              point = points[i];
                              if (point && point.graphic) {
                                  point.graphic = point.graphic.destroy();
                              }
                          }
                      }
  
                      each(['graph', 'area', 'tracker'], function(prop) {
                          if (series[prop]) {
                              series[prop] = series[prop].destroy();
                          }
                      });
                  },
  
                  /**
                   * Create a hidden canvas to draw the graph on. The contents is later copied over 
                   * to an SVG image element.
                   */
                  getContext: function() {
                      var chart = this.chart,
                          width = chart.chartWidth,
                          height = chart.chartHeight,
                          targetGroup = this.group,
                          target = this,
                          ctx,
                          swapXY = function(proceed, x, y, a, b, c, d) {
                              proceed.call(this, y, x, a, b, c, d);
                          };
  
                      if (isChartSeriesBoosting(chart)) {
                          target = chart;
                          targetGroup = chart.seriesGroup;
                      }
  
                      ctx = target.ctx;
  
                      if (!target.canvas) {
                          target.canvas = doc.createElement('canvas');
  
                          target.image = chart.renderer.image(
                              '',
                              0,
                              0,
                              width,
                              height
                          ).add(targetGroup);
  
                          target.ctx = ctx = target.canvas.getContext('2d');
  
                          if (chart.inverted) {
                              each(['moveTo', 'lineTo', 'rect', 'arc'], function(fn) {
                                  wrap(ctx, fn, swapXY);
                              });
                          }
  
                          target.boostClipRect = chart.renderer.clipRect(
                              chart.plotLeft,
                              chart.plotTop,
                              chart.plotWidth,
                              chart.chartHeight
                          );
  
                          target.image.clip(target.boostClipRect);
  
                      } else if (!(target instanceof H.Chart)) {
                          //ctx.clearRect(0, 0, width, height);
                      }
  
                      if (target.canvas.width !== width) {
                          target.canvas.width = width;
                      }
  
                      if (target.canvas.height !== height) {
                          target.canvas.height = height;
                      }
  
                      target.image.attr({
                          x: 0,
                          y: 0,
                          width: width,
                          height: height,
                          style: 'pointer-events: none'
                      });
  
                      target.boostClipRect.attr({
                          x: 0, //chart.plotLeft,
                          y: 0, //chart.plotTop,
                          width: chart.plotWidth,
                          height: chart.chartHeight
                      });
  
                      return ctx;
                  },
  
                  /** 
                   * Draw the canvas image inside an SVG image
                   */
                  canvasToSVG: function() {
                      if (!isChartSeriesBoosting(this.chart)) {
                          this.image.attr({
                              href: this.canvas.toDataURL('image/png')
                          });
                      } else if (this.image) {
                          this.image.attr({
                              href: ''
                          });
                      }
                  },
  
                  cvsLineTo: function(ctx, clientX, plotY) {
                      ctx.lineTo(clientX, plotY);
                  },
  
                  renderCanvas: function() {
                      var series = this,
                          options = series.options,
                          chart = series.chart,
                          xAxis = this.xAxis,
                          yAxis = this.yAxis,
                          activeBoostSettings = chart.options.boost || {},
                          boostSettings = {
                              timeRendering: activeBoostSettings.timeRendering || false,
                              timeSeriesProcessing: activeBoostSettings.timeSeriesProcessing || false,
                              timeSetup: activeBoostSettings.timeSetup || false
                          },
                          ctx,
                          c = 0,
                          xData = series.processedXData,
                          yData = series.processedYData,
                          rawData = options.data,
                          xExtremes = xAxis.getExtremes(),
                          xMin = xExtremes.min,
                          xMax = xExtremes.max,
                          yExtremes = yAxis.getExtremes(),
                          yMin = yExtremes.min,
                          yMax = yExtremes.max,
                          pointTaken = {},
                          lastClientX,
                          sampling = !!series.sampling,
                          points,
                          r = options.marker && options.marker.radius,
                          cvsDrawPoint = this.cvsDrawPoint,
                          cvsLineTo = options.lineWidth ? this.cvsLineTo : false,
                          cvsMarker = r && r <= 1 ?
                          this.cvsMarkerSquare :
                          this.cvsMarkerCircle,
                          strokeBatch = this.cvsStrokeBatch || 1000,
                          enableMouseTracking = options.enableMouseTracking !== false,
                          lastPoint,
                          threshold = options.threshold,
                          yBottom = yAxis.getThreshold(threshold),
                          hasThreshold = isNumber(threshold),
                          translatedThreshold = yBottom,
                          doFill = this.fill,
                          isRange = series.pointArrayMap && series.pointArrayMap.join(',') === 'low,high',
                          isStacked = !!options.stacking,
                          cropStart = series.cropStart || 0,
                          loadingOptions = chart.options.loading,
                          requireSorting = series.requireSorting,
                          wasNull,
                          connectNulls = options.connectNulls,
                          useRaw = !xData,
                          minVal,
                          maxVal,
                          minI,
                          maxI,
                          kdIndex,
                          sdata = isStacked ? series.data : (xData || rawData),
                          boostingOnChartLevel = isChartSeriesBoosting(chart),
                          fillColor = series.fillOpacity ?
                          new Color(series.color).setOpacity(pick(options.fillOpacity, 0.75)).get() :
                          series.color,
  
                          stroke = function() {
                              if (doFill) {
                                  ctx.fillStyle = fillColor;
                                  ctx.fill();
                              } else {
                                  ctx.strokeStyle = series.color;
                                  ctx.lineWidth = options.lineWidth;
                                  ctx.stroke();
                              }
                          },
  
                          drawPoint = function(clientX, plotY, yBottom, i) {
                              if (c === 0) {
                                  ctx.beginPath();
  
                                  if (cvsLineTo) {
                                      ctx.lineJoin = 'round';
                                  }
                              }
  
                              if (chart.scroller && series.options.className === 'highcharts-navigator-series') {
                                  plotY += chart.scroller.top;
                                  if (yBottom) {
                                      yBottom += chart.scroller.top;
                                  }
                              } else {
                                  plotY += chart.plotTop;
                              }
  
                              clientX += chart.plotLeft;
  
                              if (wasNull) {
                                  ctx.moveTo(clientX, plotY);
                              } else {
                                  if (cvsDrawPoint) {
                                      cvsDrawPoint(ctx, clientX, plotY, yBottom, lastPoint);
                                  } else if (cvsLineTo) {
                                      cvsLineTo(ctx, clientX, plotY);
                                  } else if (cvsMarker) {
                                      cvsMarker.call(series, ctx, clientX, plotY, r, i);
                                  }
                              }
  
                              // We need to stroke the line for every 1000 pixels. It will crash the browser
                              // memory use if we stroke too infrequently.
                              c = c + 1;
                              if (c === strokeBatch) {
                                  stroke();
                                  c = 0;
                              }
  
                              // Area charts need to keep track of the last point
                              lastPoint = {
                                  clientX: clientX,
                                  plotY: plotY,
                                  yBottom: yBottom
                              };
                          },
  
                          addKDPoint = function(clientX, plotY, i) {
                              // Avoid more string concatination than required
                              kdIndex = clientX + ',' + plotY;
  
                              // The k-d tree requires series points. Reduce the amount of points, since the time to build the 
                              // tree increases exponentially.
                              if (enableMouseTracking && !pointTaken[kdIndex]) {
                                  pointTaken[kdIndex] = true;
  
                                  if (chart.inverted) {
                                      clientX = xAxis.len - clientX;
                                      plotY = yAxis.len - plotY;
                                  }
  
                                  points.push({
                                      clientX: clientX,
                                      plotX: clientX,
                                      plotY: plotY,
                                      i: cropStart + i
                                  });
                              }
                          };
  
                      // If we are zooming out from SVG mode, destroy the graphics
                      if (this.points || this.graph) {
                          this.destroyGraphics();
                      }
  
                      // The group
                      series.plotGroup(
                          'group',
                          'series',
                          series.visible ? 'visible' : 'hidden',
                          options.zIndex,
                          chart.seriesGroup
                      );
  
                      series.markerGroup = series.group;
                      // addEvent(series, 'destroy', function () {
                      // 	series.markerGroup = null;
                      // });
  
                      points = this.points = [];
                      ctx = this.getContext();
                      series.buildKDTree = noop; // Do not start building while drawing 
  
                      // Display a loading indicator
                      if (rawData.length > 99999) {
                          chart.options.loading = merge(loadingOptions, {
                              labelStyle: {
                                  backgroundColor: H.color('#ffffff').setOpacity(0.75).get(),
                                  padding: '1em',
                                  borderRadius: '0.5em'
                              },
                              style: {
                                  backgroundColor: 'none',
                                  opacity: 1
                              }
                          });
                          clearTimeout(destroyLoadingDiv);
                          chart.showLoading('Drawing...');
                          chart.options.loading = loadingOptions; // reset
                      }
  
                      if (boostSettings.timeRendering) {
                          console.time('canvas rendering');
                      }
  
                      // Loop over the points
                      eachAsync(sdata, function(d, i) {
                          var x,
                              y,
                              clientX,
                              plotY,
                              isNull,
                              low,
                              isNextInside = false,
                              isPrevInside = false,
                              nx = false,
                              px = false,
                              chartDestroyed = typeof chart.index === 'undefined',
                              isYInside = true;
  
                          if (!chartDestroyed) {
                              if (useRaw) {
                                  x = d[0];
                                  y = d[1];
  
                                  if (sdata[i + 1]) {
                                      nx = sdata[i + 1][0];
                                  }
  
                                  if (sdata[i - 1]) {
                                      px = sdata[i - 1][0];
                                  }
                              } else {
                                  x = d;
                                  y = yData[i];
  
                                  if (sdata[i + 1]) {
                                      nx = sdata[i + 1];
                                  }
  
                                  if (sdata[i - 1]) {
                                      px = sdata[i - 1];
                                  }
                              }
  
                              if (nx && nx >= xMin && nx <= xMax) {
                                  isNextInside = true;
                              }
  
                              if (px && px >= xMin && px <= xMax) {
                                  isPrevInside = true;
                              }
  
                              // Resolve low and high for range series
                              if (isRange) {
                                  if (useRaw) {
                                      y = d.slice(1, 3);
                                  }
                                  low = y[0];
                                  y = y[1];
                              } else if (isStacked) {
                                  x = d.x;
                                  y = d.stackY;
                                  low = y - d.y;
                              }
  
                              isNull = y === null;
  
                              // Optimize for scatter zooming
                              if (!requireSorting) {
                                  isYInside = y >= yMin && y <= yMax;
                              }
  
                              if (!isNull &&
                                  (
                                      (x >= xMin && x <= xMax && isYInside) ||
                                      (isNextInside || isPrevInside)
                                  )) {
  
  
                                  clientX = Math.round(xAxis.toPixels(x, true));
  
                                  if (sampling) {
                                      if (minI === undefined || clientX === lastClientX) {
                                          if (!isRange) {
                                              low = y;
                                          }
                                          if (maxI === undefined || y > maxVal) {
                                              maxVal = y;
                                              maxI = i;
                                          }
                                          if (minI === undefined || low < minVal) {
                                              minVal = low;
                                              minI = i;
                                          }
  
                                      }
                                      if (clientX !== lastClientX) { // Add points and reset
                                          if (minI !== undefined) { // then maxI is also a number
                                              plotY = yAxis.toPixels(maxVal, true);
                                              yBottom = yAxis.toPixels(minVal, true);
                                              drawPoint(
                                                  clientX,
                                                  hasThreshold ? Math.min(plotY, translatedThreshold) : plotY,
                                                  hasThreshold ? Math.max(yBottom, translatedThreshold) : yBottom,
                                                  i
                                              );
                                              addKDPoint(clientX, plotY, maxI);
                                              if (yBottom !== plotY) {
                                                  addKDPoint(clientX, yBottom, minI);
                                              }
                                          }
  
                                          minI = maxI = undefined;
                                          lastClientX = clientX;
                                      }
                                  } else {
                                      plotY = Math.round(yAxis.toPixels(y, true));
                                      drawPoint(clientX, plotY, yBottom, i);
                                      addKDPoint(clientX, plotY, i);
                                  }
                              }
                              wasNull = isNull && !connectNulls;
  
                              if (i % CHUNK_SIZE === 0) {
                                  series.canvasToSVG();
                              }
                          }
  
                          return !chartDestroyed;
                      }, function() {
                          var loadingDiv = chart.loadingDiv,
                              loadingShown = chart.loadingShown;
                          stroke();
                          series.canvasToSVG();
  
                          if (boostSettings.timeRendering) {
                              console.timeEnd('canvas rendering');
                          }
  
                          fireEvent(series, 'renderedCanvas');
  
                          // Do not use chart.hideLoading, as it runs JS animation and will be blocked by buildKDTree.
                          // CSS animation looks good, but then it must be deleted in timeout. If we add the module to core,
                          // change hideLoading so we can skip this block.
                          if (loadingShown) {
                              extend(loadingDiv.style, {
                                  transition: 'opacity 250ms',
                                  opacity: 0
                              });
                              chart.loadingShown = false;
                              destroyLoadingDiv = setTimeout(function() {
                                  if (loadingDiv.parentNode) { // In exporting it is falsy
                                      loadingDiv.parentNode.removeChild(loadingDiv);
                                  }
                                  chart.loadingDiv = chart.loadingSpan = null;
                              }, 250);
                          }
  
                          // Pass tests in Pointer. 
                          // Replace this with a single property, and replace when zooming in
                          // below boostThreshold.
                          series.directTouch = false;
                          series.options.stickyTracking = true;
  
                          delete series.buildKDTree; // Go back to prototype, ready to build
                          series.buildKDTree();
  
                          // Don't do async on export, the exportChart, getSVGForExport and getSVG methods are not chained for it.
                      }, chart.renderer.forExport ? Number.MAX_VALUE : undefined);
                  }
              });
  
              wrap(Series.prototype, 'setData', function(proceed) {
                  if (!this.hasExtremes || !this.hasExtremes(true) || this.type === 'heatmap') {
                      proceed.apply(this, Array.prototype.slice.call(arguments, 1));
                  }
              });
  
              wrap(Series.prototype, 'processData', function(proceed) {
                  if (!this.hasExtremes || !this.hasExtremes(true) || this.type === 'heatmap') {
                      proceed.apply(this, Array.prototype.slice.call(arguments, 1));
                  }
              });
  
              seriesTypes.scatter.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r) {
                  ctx.moveTo(clientX, plotY);
                  ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
              };
  
              // Rect is twice as fast as arc, should be used for small markers
              seriesTypes.scatter.prototype.cvsMarkerSquare = function(ctx, clientX, plotY, r) {
                  ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
              };
              seriesTypes.scatter.prototype.fill = true;
  
              if (seriesTypes.bubble) {
                  seriesTypes.bubble.prototype.cvsMarkerCircle = function(ctx, clientX, plotY, r, i) {
                      ctx.moveTo(clientX, plotY);
                      ctx.arc(clientX, plotY, this.radii && this.radii[i], 0, 2 * Math.PI, false);
                  };
                  seriesTypes.bubble.prototype.cvsStrokeBatch = 1;
              }
  
              extend(seriesTypes.area.prototype, {
                  cvsDrawPoint: function(ctx, clientX, plotY, yBottom, lastPoint) {
                      if (lastPoint && clientX !== lastPoint.clientX) {
                          ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
                          ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
                          ctx.lineTo(clientX, plotY);
                          ctx.lineTo(clientX, yBottom);
                      }
                  },
                  fill: true,
                  fillOpacity: true,
                  sampling: true
              });
  
              extend(seriesTypes.column.prototype, {
                  cvsDrawPoint: function(ctx, clientX, plotY, yBottom) {
                      ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
                  },
                  fill: true,
                  sampling: true
              });
  
              H.Chart.prototype.callbacks.push(function(chart) {
                  function canvasToSVG() {
                      if (chart.image && chart.canvas) {
                          chart.image.attr({
                              href: chart.canvas.toDataURL('image/png')
                          });
                      }
                  }
  
                  function clear() {
                      if (chart.image) {
                          chart.image.attr({
                              href: ''
                          });
                      }
  
                      if (chart.canvas) {
                          chart.canvas.getContext('2d').clearRect(
                              0,
                              0,
                              chart.canvas.width,
                              chart.canvas.height
                          );
                      }
                  }
  
                  addEvent(chart, 'predraw', clear);
                  addEvent(chart, 'render', canvasToSVG);
              });
          };
      }(Highcharts));
  }));
  