text: /**
   * @license Highcharts JS v5.0.13 (2017-07-27)
   * Boost module
   *
   * (c) 2010-2017 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: Christer Vasseng, Torstein Honsi
           *
           * 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 WebGL compatible browsers (not IE < 11).
           *
           * Development plan
           * - Column range.
           * - 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.
           * - Chart callback should be async after last series is drawn. (But not necessarily, we don't do
          	 that with initial series animation).
           *
           * 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
           * - Lines are not drawn on scatter charts
           * - Zones and negativeColor don't work
           * - Dash styles are not rendered on lines.
           * - Columns are always one pixel wide. Don't set the threshold too low.
           * - Disable animations
           * - Marker shapes are not supported: markers will always be circles
           *
           * Optimizing tips for users
           * - 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.
           * - If drawing large scatter charts, it's beneficial to set the marker radius to a value
           *   less than 1. This is to add additional spacing to make the chart more readable.
           * - If the value increments on both the X and Y axis aren't small, consider setting
           *	 useGPUTranslations to true on the boost settings object. If you do this and
           *	 the increments are small (e.g. datetime axis with small time increments)
           *	 it may cause rendering issues due to floating point rounding errors,
           *	 so your millage may vary.
           *
           * Settings
           *	There are two ways of setting the boost threshold:
           *	- Per. series: boost based on number of points in individual series
           *	- Per. chart: boost based on the number of series
           *
           *  To set the series boost threshold, set seriesBoostThreshold on the chart object.
           *  To set the series-specific threshold, set boostThreshold on the series object.
           *
           *  In addition, the following can be set in the boost object:
           *  {
           *  	//Wether or not to use alpha blending
           *  	useAlpha: boolean - default: true
           *  	//Set to true to perform translations on the GPU.
           *  	//Much faster, but may cause rendering issues
           *  	//when using values far from 0 due to floating point
           *  	//rounding issues
           *  	useGPUTranslations: boolean - default: false
           *  	//Use pre-allocated buffers, much faster,
           *  	//but may cause rendering issues with some data sets
           *  	usePreallocated: boolean - default: false
           *  	//Output rendering time in console
           *  	timeRendering: boolean - default: false
           *  	//Output processing time in console
           *  	timeSeriesProcessing: boolean - default: false
           *  	//Output setup time in console
           *  	timeSetup: boolean - default: false
           *  }
           */
  
          /**
           * Set the series threshold for when the boost should kick in globally.
           *
           * Setting to e.g. 20 will cause the whole chart to enter boost mode
           * if there are 20 or more series active. When the chart is in boost mode,
           * every series in it will be rendered to a common canvas. This offers
           * a significant speed improvment in charts with a very high
           * amount of series.
           *
           * Note: only available when including the boost module.
           *
           * @default  null
           * @apioption boost.seriesThreshold
           */
  
          /**
           * Set the point threshold for when a series should enter boost mode.
           *
           * Setting it to e.g. 2000 will cause the series to enter boost mode
           * when there are 2000 or more points in the series.
           *
           * Note: only available when including the boost module.
           *
           * @default  5000
           * @apioption series.boostThreshold
           */
  
          /* global Float32Array */
  
  
          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,
              index;
  
          // Register color names since GL can't render those directly.
          Color.prototype.names = {
              aliceblue: '#f0f8ff',
              antiquewhite: '#faebd7',
              aqua: '#00ffff',
              aquamarine: '#7fffd4',
              azure: '#f0ffff',
              beige: '#f5f5dc',
              bisque: '#ffe4c4',
              black: '#000000',
              blanchedalmond: '#ffebcd',
              blue: '#0000ff',
              blueviolet: '#8a2be2',
              brown: '#a52a2a',
              burlywood: '#deb887',
              cadetblue: '#5f9ea0',
              chartreuse: '#7fff00',
              chocolate: '#d2691e',
              coral: '#ff7f50',
              cornflowerblue: '#6495ed',
              cornsilk: '#fff8dc',
              crimson: '#dc143c',
              cyan: '#00ffff',
              darkblue: '#00008b',
              darkcyan: '#008b8b',
              darkgoldenrod: '#b8860b',
              darkgray: '#a9a9a9',
              darkgreen: '#006400',
              darkkhaki: '#bdb76b',
              darkmagenta: '#8b008b',
              darkolivegreen: '#556b2f',
              darkorange: '#ff8c00',
              darkorchid: '#9932cc',
              darkred: '#8b0000',
              darksalmon: '#e9967a',
              darkseagreen: '#8fbc8f',
              darkslateblue: '#483d8b',
              darkslategray: '#2f4f4f',
              darkturquoise: '#00ced1',
              darkviolet: '#9400d3',
              deeppink: '#ff1493',
              deepskyblue: '#00bfff',
              dimgray: '#696969',
              dodgerblue: '#1e90ff',
              feldspar: '#d19275',
              firebrick: '#b22222',
              floralwhite: '#fffaf0',
              forestgreen: '#228b22',
              fuchsia: '#ff00ff',
              gainsboro: '#dcdcdc',
              ghostwhite: '#f8f8ff',
              gold: '#ffd700',
              goldenrod: '#daa520',
              gray: '#808080',
              green: '#008000',
              greenyellow: '#adff2f',
              honeydew: '#f0fff0',
              hotpink: '#ff69b4',
              indianred: '#cd5c5c',
              indigo: '#4b0082',
              ivory: '#fffff0',
              khaki: '#f0e68c',
              lavender: '#e6e6fa',
              lavenderblush: '#fff0f5',
              lawngreen: '#7cfc00',
              lemonchiffon: '#fffacd',
              lightblue: '#add8e6',
              lightcoral: '#f08080',
              lightcyan: '#e0ffff',
              lightgoldenrodyellow: '#fafad2',
              lightgrey: '#d3d3d3',
              lightgreen: '#90ee90',
              lightpink: '#ffb6c1',
              lightsalmon: '#ffa07a',
              lightseagreen: '#20b2aa',
              lightskyblue: '#87cefa',
              lightslateblue: '#8470ff',
              lightslategray: '#778899',
              lightsteelblue: '#b0c4de',
              lightyellow: '#ffffe0',
              lime: '#00ff00',
              limegreen: '#32cd32',
              linen: '#faf0e6',
              magenta: '#ff00ff',
              maroon: '#800000',
              mediumaquamarine: '#66cdaa',
              mediumblue: '#0000cd',
              mediumorchid: '#ba55d3',
              mediumpurple: '#9370d8',
              mediumseagreen: '#3cb371',
              mediumslateblue: '#7b68ee',
              mediumspringgreen: '#00fa9a',
              mediumturquoise: '#48d1cc',
              mediumvioletred: '#c71585',
              midnightblue: '#191970',
              mintcream: '#f5fffa',
              mistyrose: '#ffe4e1',
              moccasin: '#ffe4b5',
              navajowhite: '#ffdead',
              navy: '#000080',
              oldlace: '#fdf5e6',
              olive: '#808000',
              olivedrab: '#6b8e23',
              orange: '#ffa500',
              orangered: '#ff4500',
              orchid: '#da70d6',
              palegoldenrod: '#eee8aa',
              palegreen: '#98fb98',
              paleturquoise: '#afeeee',
              palevioletred: '#d87093',
              papayawhip: '#ffefd5',
              peachpuff: '#ffdab9',
              peru: '#cd853f',
              pink: '#ffc0cb',
              plum: '#dda0dd',
              powderblue: '#b0e0e6',
              purple: '#800080',
              red: '#ff0000',
              rosybrown: '#bc8f8f',
              royalblue: '#4169e1',
              saddlebrown: '#8b4513',
              salmon: '#fa8072',
              sandybrown: '#f4a460',
              seagreen: '#2e8b57',
              seashell: '#fff5ee',
              sienna: '#a0522d',
              silver: '#c0c0c0',
              skyblue: '#87ceeb',
              slateblue: '#6a5acd',
              slategray: '#708090',
              snow: '#fffafa',
              springgreen: '#00ff7f',
              steelblue: '#4682b4',
              tan: '#d2b48c',
              teal: '#008080',
              thistle: '#d8bfd8',
              tomato: '#ff6347',
              turquoise: '#40e0d0',
              violet: '#ee82ee',
              violetred: '#d02090',
              wheat: '#f5deb3',
              white: '#ffffff',
              whitesmoke: '#f5f5f5',
              yellow: '#ffff00',
              yellowgreen: '#9acd32'
          };
  
          /**
           * Tolerant max() funciton
           * @return {number} max value
           */
          function patientMax() {
              var args = Array.prototype.slice.call(arguments),
                  r = -Number.MAX_VALUE;
  
              each(args, function(t) {
                  if (typeof t !== 'undefined' && typeof t.length !== 'undefined') {
                      //r = r < t.length ? t.length : r;
                      if (t.length > 0) {
                          r = t.length;
                          return true;
                      }
                  }
              });
  
              return r;
          }
  
          /*
           * Returns true if we should force chart series boosting
           */
          function shouldForceChartSeriesBoosting(chart) {
              // If there are more than five series currently boosting,
              // we should boost the whole chart to avoid running out of webgl contexts.
              var sboostCount = 0,
                  series;
  
              if (chart.series.length > 1) {
                  for (var i = 0; i < chart.series.length; i++) {
                      series = chart.series[i];
                      if (patientMax(
                              series.processedXData,
                              series.options.data,
                              series.points
                          ) >= (series.options.boostThreshold || Number.MAX_VALUE)) {
                          sboostCount++;
                      }
                  }
              }
  
              return sboostCount > 5;
          }
  
          /*
           * 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) {
              return shouldForceChartSeriesBoosting(chart) || chart.series.length >= pick(
                  chart.options.boost && chart.options.boost.seriesThreshold,
                  50
              );
          }
  
          /*
           * Returns true if the series is in boost mode
           * @param series {Highchart.Series} - the series to check
           * @returns {boolean} - true if the series is in boost mode
           */
          function isSeriesBoosting(series) {
              return isChartSeriesBoosting(series.chart) ||
                  patientMax(
                      series.processedXData,
                      series.options.data,
                      series.points
                  ) >= (series.options.boostThreshold || Number.MAX_VALUE);
          }
  
          ////////////////////////////////////////////////////////////////////////////////
          // START OF WEBGL ABSTRACTIONS
  
          /*
           * A static shader mimicing axis translation functions found in parts/Axis
           * @param gl {WebGLContext} - the context in which the shader is active
           */
          function GLShader(gl) {
              var vertShade = [
                      /* eslint-disable */
                      '#version 100',
                      'precision highp float;',
  
                      'attribute vec4 aVertexPosition;',
                      'attribute vec4 aColor;',
  
                      'varying highp vec2 position;',
                      'varying highp vec4 vColor;',
  
                      'uniform mat4 uPMatrix;',
                      'uniform float pSize;',
  
                      'uniform float translatedThreshold;',
                      'uniform bool hasThreshold;',
  
                      'uniform bool skipTranslation;',
  
                      'uniform float xAxisTrans;',
                      'uniform float xAxisMin;',
                      'uniform float xAxisMinPad;',
                      'uniform float xAxisPointRange;',
                      'uniform float xAxisLen;',
                      'uniform bool  xAxisPostTranslate;',
                      'uniform float xAxisOrdinalSlope;',
                      'uniform float xAxisOrdinalOffset;',
                      'uniform float xAxisPos;',
                      'uniform bool  xAxisCVSCoord;',
  
                      'uniform float yAxisTrans;',
                      'uniform float yAxisMin;',
                      'uniform float yAxisMinPad;',
                      'uniform float yAxisPointRange;',
                      'uniform float yAxisLen;',
                      'uniform bool  yAxisPostTranslate;',
                      'uniform float yAxisOrdinalSlope;',
                      'uniform float yAxisOrdinalOffset;',
                      'uniform float yAxisPos;',
                      'uniform bool  yAxisCVSCoord;',
  
                      'uniform bool  isBubble;',
                      'uniform bool  bubbleSizeByArea;',
                      'uniform float bubbleZMin;',
                      'uniform float bubbleZMax;',
                      'uniform float bubbleZThreshold;',
                      'uniform float bubbleMinSize;',
                      'uniform float bubbleMaxSize;',
                      'uniform bool  bubbleSizeAbs;',
                      'uniform bool  isInverted;',
  
                      'float bubbleRadius(){',
                      'float value = aVertexPosition.w;',
                      'float zMax = bubbleZMax;',
                      'float zMin = bubbleZMin;',
                      'float radius = 0.0;',
                      'float pos = 0.0;',
                      'float zRange = zMax - zMin;',
  
                      'if (bubbleSizeAbs){',
                      'value = value - bubbleZThreshold;',
                      'zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);',
                      'zMin = 0.0;',
                      '}',
  
                      'if (value < zMin){',
                      'radius = bubbleZMin / 2.0 - 1.0;',
                      '} else {',
                      'pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;',
                      'if (bubbleSizeByArea && pos > 0.0){',
                      'pos = sqrt(pos);',
                      '}',
                      'radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;',
                      '}',
  
                      'return radius * 2.0;',
                      '}',
  
                      'float translate(float val,',
                      'float pointPlacement,',
                      'float localA,',
                      'float localMin,',
                      'float minPixelPadding,',
                      'float pointRange,',
                      'float len,',
                      'bool  cvsCoord',
                      '){',
  
                      'float sign = 1.0;',
                      'float cvsOffset = 0.0;',
  
                      'if (cvsCoord) {',
                      'sign *= -1.0;',
                      'cvsOffset = len;',
                      '}',
  
                      'return sign * (val - localMin) * localA + cvsOffset + ',
                      '(sign * minPixelPadding);', //' + localA * pointPlacement * pointRange;',
                      '}',
  
                      'float xToPixels(float value){',
                      'if (skipTranslation){',
                      'return value;// + xAxisPos;',
                      '}',
  
                      'return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord);// + xAxisPos;',
                      '}',
  
                      'float yToPixels(float value, float checkTreshold){',
                      'float v;',
                      'if (skipTranslation){',
                      'v = value;// + yAxisPos;',
                      '} else {',
                      'v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord);// + yAxisPos;',
                      '}',
                      'if (checkTreshold > 0.0 && hasThreshold) {',
                      'v = min(v, translatedThreshold);',
                      '}',
                      'return v;',
                      '}',
  
                      'void main(void) {',
                      'if (isBubble){',
                      'gl_PointSize = bubbleRadius();',
                      '} else {',
                      'gl_PointSize = pSize;',
                      '}',
                      //'gl_PointSize = 10.0;',
                      'vColor = aColor;',
  
                      'if (isInverted) {',
                      'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.y) + yAxisPos, yToPixels(aVertexPosition.x, aVertexPosition.z) + xAxisPos, 0.0, 1.0);',
                      '} else {',
                      'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);',
                      '}',
                      //'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
                      '}'
                      /* eslint-enable */
                  ].join('\n'),
                  //Fragment shader source
                  fragShade = [
                      /* eslint-disable */
                      'precision highp float;',
                      'uniform vec4 fillColor;',
                      'varying highp vec2 position;',
                      'varying highp vec4 vColor;',
                      'uniform sampler2D uSampler;',
                      'uniform bool isCircle;',
                      'uniform bool hasColor;',
  
                      // 'vec4 toColor(float value, vec2 point) {',
                      // 	'return vec4(0.0, 0.0, 0.0, 0.0);',
                      // '}',
  
                      'void main(void) {',
                      'vec4 col = fillColor;',
  
                      'if (hasColor) {',
                      'col = vColor;',
                      '}',
  
                      'if (isCircle) {',
                      'gl_FragColor = col * texture2D(uSampler, gl_PointCoord.st);',
                      '} else {',
                      'gl_FragColor = col;',
                      '}',
                      '}'
                      /* eslint-enable */
                  ].join('\n'),
                  uLocations = {},
                  //The shader program
                  shaderProgram,
                  //Uniform handle to the perspective matrix
                  pUniform,
                  //Uniform for point size
                  psUniform,
                  //Uniform for fill color
                  fillColorUniform,
                  //Uniform for isBubble
                  isBubbleUniform,
                  //Uniform for bubble abs sizing
                  bubbleSizeAbsUniform,
                  bubbleSizeAreaUniform,
                  //Skip translation uniform
                  skipTranslationUniform,
                  //Set to 1 if circle
                  isCircleUniform,
                  //Uniform for invertion
                  isInverted,
                  //Texture uniform
                  uSamplerUniform;
  
              /* String to shader program
               * @param {string} str - the program source
               * @param {string} type - the program type: either `vertex` or `fragment`
               * @returns {bool|shader}
               */
              function stringToProgram(str, type) {
                  var t = type === 'vertex' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER,
                      shader = gl.createShader(t);
  
                  gl.shaderSource(shader, str);
                  gl.compileShader(shader);
  
                  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                      //console.error('shader error:', gl.getShaderInfoLog(shader));
                      return false;
                  }
                  return shader;
              }
  
              /*
               * Create the shader.
               * Loads the shader program statically defined above
               */
              function createShader() {
                  var v = stringToProgram(vertShade, 'vertex'),
                      f = stringToProgram(fragShade, 'fragment');
  
                  if (!v || !f) {
                      shaderProgram = false;
                      //console.error('error creating shader program');
                      return false;
                  }
  
                  function uloc(n) {
                      return gl.getUniformLocation(shaderProgram, n);
                  }
  
                  shaderProgram = gl.createProgram();
  
                  gl.attachShader(shaderProgram, v);
                  gl.attachShader(shaderProgram, f);
                  gl.linkProgram(shaderProgram);
  
                  gl.useProgram(shaderProgram);
  
                  gl.bindAttribLocation(shaderProgram, 0, 'aVertexPosition');
  
                  pUniform = uloc('uPMatrix');
                  psUniform = uloc('pSize');
                  fillColorUniform = uloc('fillColor');
                  isBubbleUniform = uloc('isBubble');
                  bubbleSizeAbsUniform = uloc('bubbleSizeAbs');
                  bubbleSizeAreaUniform = uloc('bubbleSizeByArea');
                  uSamplerUniform = uloc('uSampler');
                  skipTranslationUniform = uloc('skipTranslation');
                  isCircleUniform = uloc('isCircle');
                  isInverted = uloc('isInverted');
  
                  return true;
              }
  
              /*
               * Destroy the shader
               */
              function destroy() {
                  if (gl && shaderProgram) {
                      gl.deleteProgram(shaderProgram);
                  }
              }
  
              /*
               * Bind the shader.
               * This makes the shader the active one until another one is bound,
               * or until 0 is bound.
               */
              function bind() {
                  gl.useProgram(shaderProgram);
              }
  
              /*
               * Set a uniform value.
               * This uses a hash map to cache uniform locations.
               * @param name {string} - the name of the uniform to set
               * @param val {float} - the value to set
               */
              function setUniform(name, val) {
                  var u = uLocations[name] = uLocations[name] ||
                      gl.getUniformLocation(shaderProgram, name);
                  gl.uniform1f(u, val);
              }
  
              /*
               * Set the active texture
               * @param texture - the texture
               */
              function setTexture() {
                  gl.uniform1i(uSamplerUniform, 0);
              }
  
              /*
               * Set if inversion state
               * @flag is the state
               */
              function setInverted(flag) {
                  gl.uniform1i(isInverted, flag);
              }
  
              ////////////////////////////////////////////////////////////////////////////
  
              /*
               * Enable/disable circle drawing
               */
              function setDrawAsCircle(flag) {
                  gl.uniform1i(isCircleUniform, flag ? 1 : 0);
              }
  
              /*
               * Flush
               */
              function reset() {
                  gl.uniform1i(isBubbleUniform, 0);
                  gl.uniform1i(isCircleUniform, 0);
              }
  
              /*
               * Set bubble uniforms
               * @param series {Highcharts.Series} - the series to use
               */
              function setBubbleUniforms(series, zCalcMin, zCalcMax) {
                  var seriesOptions = series.options,
                      zMin = Number.MAX_VALUE,
                      zMax = -Number.MAX_VALUE;
  
                  if (series.type === 'bubble') {
                      zMin = pick(seriesOptions.zMin, Math.min(
                          zMin,
                          Math.max(
                              zCalcMin,
                              seriesOptions.displayNegative === false ?
                              seriesOptions.zThreshold : -Number.MAX_VALUE
                          )
                      ));
  
                      zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
  
                      gl.uniform1i(isBubbleUniform, 1);
                      gl.uniform1i(isCircleUniform, 1);
                      gl.uniform1i(bubbleSizeAreaUniform, series.options.sizeBy !== 'width');
                      gl.uniform1i(bubbleSizeAbsUniform, series.options.sizeByAbsoluteValue);
  
                      setUniform('bubbleZMin', zMin);
                      setUniform('bubbleZMax', zMax);
                      setUniform('bubbleZThreshold', series.options.zThreshold);
                      setUniform('bubbleMinSize', series.minPxSize);
                      setUniform('bubbleMaxSize', series.maxPxSize);
                  }
              }
  
              /*
               * Set the Color uniform.
               * @param color {Array<float>} - an array with RGBA values
               */
              function setColor(color) {
                  gl.uniform4f(
                      fillColorUniform,
                      color[0] / 255.0,
                      color[1] / 255.0,
                      color[2] / 255.0,
                      color[3]
                  );
              }
  
              /*
               * Set skip translation
               */
              function setSkipTranslation(flag) {
                  gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
              }
  
              /*
               * Set the perspective matrix
               * @param m {Matrix4x4} - the matrix
               */
              function setPMatrix(m) {
                  gl.uniformMatrix4fv(pUniform, false, m);
              }
  
              /*
               * Set the point size.
               * @param p {float} - point size
               */
              function setPointSize(p) {
                  gl.uniform1f(psUniform, p);
              }
  
              /*
               * Get the shader program handle
               * @returns {GLInt} - the handle for the program
               */
              function getProgram() {
                  return shaderProgram;
              }
  
              if (gl) {
                  createShader();
              }
  
              return {
                  psUniform: function() {
                      return psUniform;
                  },
                  pUniform: function() {
                      return pUniform;
                  },
                  fillColorUniform: function() {
                      return fillColorUniform;
                  },
                  setBubbleUniforms: setBubbleUniforms,
                  bind: bind,
                  program: getProgram,
                  create: createShader,
                  setUniform: setUniform,
                  setPMatrix: setPMatrix,
                  setColor: setColor,
                  setPointSize: setPointSize,
                  setSkipTranslation: setSkipTranslation,
                  setTexture: setTexture,
                  setDrawAsCircle: setDrawAsCircle,
                  reset: reset,
                  setInverted: setInverted,
                  destroy: destroy
              };
          }
  
          /*
           * Vertex Buffer abstraction
           * A vertex buffer is a set of vertices which are passed to the GPU
           * in a single call.
           * @param gl {WebGLContext} - the context in which to create the buffer
           * @param shader {GLShader} - the shader to use
           */
          function GLVertexBuffer(gl, shader, dataComponents /*, type */ ) {
              var buffer = false,
                  vertAttribute = false,
                  components = dataComponents || 2,
                  preAllocated = false,
                  iterator = 0,
                  data;
  
              // type = type || 'float';
  
              function destroy() {
                  if (buffer) {
                      gl.deleteBuffer(buffer);
                  }
              }
  
              /*
               * Build the buffer
               * @param dataIn {Array<float>} - a 0 padded array of indices
               * @param attrib {String} - the name of the Attribute to bind the buffer to
               * @param dataComponents {Integer} - the number of components per. indice
               */
              function build(dataIn, attrib, dataComponents) {
  
                  data = dataIn || [];
  
                  if ((!data || data.length === 0) && !preAllocated) {
                      //console.error('trying to render empty vbuffer');
                      buffer = false;
                      return false;
                  }
  
                  components = dataComponents || components;
  
                  if (buffer) {
                      gl.deleteBuffer(buffer);
                  }
  
                  buffer = gl.createBuffer();
                  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                  gl.bufferData(
                      gl.ARRAY_BUFFER,
                      preAllocated || new Float32Array(data),
                      gl.STATIC_DRAW
                  );
  
                  // gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
                  vertAttribute = gl.getAttribLocation(shader.program(), attrib);
                  gl.enableVertexAttribArray(vertAttribute);
  
                  return true;
              }
  
              /*
               * Bind the buffer
               */
              function bind() {
                  if (!buffer) {
                      return false;
                  }
  
                  // gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
                  //gl.enableVertexAttribArray(vertAttribute);
                  //gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                  gl.vertexAttribPointer(vertAttribute, components, gl.FLOAT, false, 0, 0);
                  //gl.enableVertexAttribArray(vertAttribute);
              }
  
              /*
               * Render the buffer
               * @param from {Integer} - the start indice
               * @param to {Integer} - the end indice
               * @param drawMode {String} - the draw mode
               */
              function render(from, to, drawMode) {
                  var length = preAllocated ? preAllocated.length : data.length;
  
                  if (!buffer) {
                      return false;
                  }
  
                  if (!length) {
                      return false;
                  }
  
                  if (!from || from > length || from < 0) {
                      from = 0;
                  }
  
                  if (!to || to > length) {
                      to = length;
                  }
  
                  drawMode = drawMode || 'points';
  
                  gl.drawArrays(
                      gl[drawMode.toUpperCase()],
                      from / components,
                      (to - from) / components
                  );
  
                  return true;
              }
  
              function push(x, y, a, b) {
                  if (preAllocated) { // && iterator <= preAllocated.length - 4) {
                      preAllocated[++iterator] = x;
                      preAllocated[++iterator] = y;
                      preAllocated[++iterator] = a;
                      preAllocated[++iterator] = b;
                  }
              }
  
              /*
               * Note about pre-allocated buffers:
               * 	- This is slower for charts with many series
               */
              function allocate(size) {
                  size *= 4;
                  iterator = -1;
  
                  //if (!preAllocated || (preAllocated && preAllocated.length !== size)) {
                  preAllocated = new Float32Array(size);
                  //}
              }
  
              ////////////////////////////////////////////////////////////////////////////
              return {
                  destroy: destroy,
                  bind: bind,
                  data: data,
                  build: build,
                  render: render,
                  allocate: allocate,
                  push: push
              };
          }
  
          /* Main renderer. Used to render series.
           *	Notes to self:
           *		- May be able to build a point map by rendering to a separate canvas
           *		  and encoding values in the color data.
           *		- Need to figure out a way to transform the data quicker
           */
          function GLRenderer(postRenderCallback) {
              var // Shader
                  shader = false,
                  // Vertex buffers - keyed on shader attribute name
                  vbuffer = false,
                  // Opengl context
                  gl = false,
                  // Width of our viewport in pixels
                  width = 0,
                  // Height of our viewport in pixels
                  height = 0,
                  // The data to render - array of coordinates
                  data = false,
                  // The marker data
                  markerData = false,
                  // Is the texture ready?
                  textureIsReady = false,
                  // Exports
                  exports = {},
                  // Is it inited?
                  isInited = false,
                  // The series stack
                  series = [],
                  // Texture for circles
                  circleTexture = doc.createElement('canvas'),
                  // Context for circle texture
                  circleCtx = circleTexture.getContext('2d'),
                  // Handle for the circle texture
                  circleTextureHandle,
                  // Things to draw as "rectangles" (i.e lines)
                  asBar = {
                      'column': true,
                      'area': true
                  },
                  asCircle = {
                      'scatter': true,
                      'bubble': true
                  },
                  //Render settings
                  settings = {
                      pointSize: 1,
                      lineWidth: 3,
                      fillColor: '#AA00AA',
                      useAlpha: true,
                      usePreallocated: false,
                      useGPUTranslations: false,
                      timeRendering: false,
                      timeSeriesProcessing: false,
                      timeSetup: false
                  };
  
              ////////////////////////////////////////////////////////////////////////////
  
              function setOptions(options) {
                  merge(true, settings, options);
              }
  
              function seriesPointCount(series) {
                  var isStacked,
                      xData,
                      s;
  
                  if (isSeriesBoosting(series)) {
                      isStacked = !!series.options.stacking;
                      xData = series.xData || series.options.xData || series.processedXData;
                      s = (isStacked ? series.data : (xData || series.options.data)).length;
  
                      if (series.type === 'treemap') {
                          s *= 12;
                      } else if (series.type === 'heatmap') {
                          s *= 6;
                      } else if (asBar[series.type]) {
                          s *= 2;
                      }
  
                      return s;
                  }
  
                  return 0;
              }
  
              /* Allocate a float buffer to fit all series */
              function allocateBuffer(chart) {
                  var s = 0;
  
                  if (!settings.usePreallocated) {
                      return;
                  }
  
                  each(chart.series, function(series) {
                      if (isSeriesBoosting(series)) {
                          s += seriesPointCount(series);
                      }
                  });
  
                  vbuffer.allocate(s);
              }
  
              function allocateBufferForSingleSeries(series) {
                  var s = 0;
  
                  if (!settings.usePreallocated) {
                      return;
                  }
  
                  if (isSeriesBoosting(series)) {
                      s = seriesPointCount(series);
                  }
  
                  vbuffer.allocate(s);
              }
  
              /*
               * Returns an orthographic perspective matrix
               * @param {number} width - the width of the viewport in pixels
               * @param {number} height - the height of the viewport in pixels
               */
              function orthoMatrix(width, height) {
                  var near = 0,
                      far = 1;
  
                  return [
                      2 / width, 0, 0, 0,
                      0, -(2 / height), 0, 0,
                      0, 0, -2 / (far - near), 0, -1, 1, -(far + near) / (far - near), 1
                  ];
              }
  
              /*
               * Clear the depth and color buffer
               */
              function clear() {
                  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
              }
  
              /*
               * Get the WebGL context
               * @returns {WebGLContext} - the context
               */
              function getGL() {
                  return gl;
              }
  
              /*
               * Push data for a single series
               * This calculates additional vertices and transforms the data to be
               * aligned correctly in memory
               */
              function pushSeriesData(series, inst) {
                  var isRange = series.pointArrayMap &&
                      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 || series.processedZData,
                      yAxis = series.yAxis,
                      xAxis = series.xAxis,
                      useRaw = !xData || xData.length === 0,
                      // threshold = options.threshold,
                      // yBottom = chart.yAxis[0].getThreshold(threshold),
                      // hasThreshold = isNumber(threshold),
                      // colorByPoint = series.options.colorByPoint,
                      // This is required for color by point, so make sure this is
                      // uncommented if enabling that
                      // colorIndex = 0,
                      // Required for color axis support
                      // caxis,
                      // connectNulls = options.connectNulls,
                      // For some reason eslint doesn't pick up that this is actually used
                      maxVal, //eslint-disable-line no-unused-vars
                      points = series.points || false,
                      lastX = false,
                      minVal,
                      color,
                      scolor,
                      sdata = isStacked ? series.data : (xData || rawData),
                      closestLeft = {
                          x: Number.MIN_VALUE,
                          y: 0
                      },
                      closestRight = {
                          x: Number.MIN_VALUE,
                          y: 0
                      };
  
                  if (options.boostData && options.boostData.length > 0) {
                      return;
                  }
  
                  series.closestPointRangePx = Number.MAX_VALUE;
  
                  // Push color to color buffer - need to do this per. vertex
                  function pushColor(color) {
                      if (color) {
                          inst.colorData.push(color[0]);
                          inst.colorData.push(color[1]);
                          inst.colorData.push(color[2]);
                          inst.colorData.push(color[3]);
                      }
                  }
  
                  //Push a vertice to the data buffer
                  function vertice(x, y, checkTreshold, pointSize, color) {
                      pushColor(color);
                      if (settings.usePreallocated) {
                          vbuffer.push(x, y, checkTreshold ? 1 : 0, pointSize || 1);
                      } else {
                          data.push(x);
                          data.push(y);
                          data.push(checkTreshold ? 1 : 0);
                          data.push(pointSize || 1);
                      }
                  }
  
                  // Push a rectangle to the data buffer
                  function pushRect(x, y, w, h, color) {
                      pushColor(color);
                      vertice(x + w, y);
                      pushColor(color);
                      vertice(x, y);
                      pushColor(color);
                      vertice(x, y + h);
  
                      pushColor(color);
                      vertice(x, y + h);
                      pushColor(color);
                      vertice(x + w, y + h);
                      pushColor(color);
                      vertice(x + w, y);
                  }
  
                  // Special case for point shapes
                  if (points && points.length > 0) {
  
                      // If we're doing points, we assume that the points are already
                      // translated, so we skip the shader translation.
                      inst.skipTranslation = true;
                      // Force triangle draw mode
                      inst.drawMode = 'triangles';
  
                      // We don't have a z component in the shader, so we need to sort.
                      if (points[0].node && points[0].node.levelDynamic) {
                          points.sort(function(a, b) {
                              if (a.node) {
                                  if (a.node.levelDynamic > b.node.levelDynamic) {
                                      return 1;
                                  } else if (a.node.levelDynamic < b.node.levelDynamic) {
                                      return -1;
                                  }
                              }
                              return 0;
                          });
                      }
  
                      each(points, function(point) {
                          var plotY = point.plotY,
                              shapeArgs,
                              swidth,
                              pointAttr;
  
                          if (typeof plotY !== 'undefined' && !isNaN(plotY) && point.y !== null) {
                              shapeArgs = point.shapeArgs;
  
  
                              pointAttr = point.series.colorAttribs(point);
  
                              swidth = pointAttr['stroke-width'] || 0;
  
                              // Handle point colors
                              color = H.color(pointAttr.fill).rgba;
                              color[0] /= 255.0;
                              color[1] /= 255.0;
                              color[2] /= 255.0;
  
                              // So there are two ways of doing this. Either we can
                              // create a rectangle of two triangles, or we can do a
                              // point and use point size. Latter is faster, but
                              // only supports squares. So we're doing triangles.
                              // We could also use one color per. vertice to get
                              // better color interpolation.
  
                              // If there's stroking, we do an additional rect
                              //if (pointAttr.stroke !== 'none' && swidth && swidth > 0) {
                              if (series.type === 'treemap') {
                                  swidth = swidth || 1;
                                  scolor = H.color(pointAttr.stroke).rgba;
  
                                  scolor[0] /= 255.0;
                                  scolor[1] /= 255.0;
                                  scolor[2] /= 255.0;
  
                                  pushRect(
                                      shapeArgs.x,
                                      shapeArgs.y,
                                      shapeArgs.width,
                                      shapeArgs.height,
                                      scolor
                                  );
  
                                  swidth /= 2;
                              }
                              // } else {
                              // 	swidth = 0;
                              // }
  
                              // Fixes issues with inverted heatmaps (see #6981)
                              // The root cause is that the coordinate system is flipped.
                              // In other words, instead of [0,0] being top-left, it's bottom-right.
                              // This causes a vertical and horizontal flip in the resulting image,
                              // making it rotated 180 degrees.
                              if (series.type === 'heatmap' && chart.inverted) {
                                  shapeArgs.x = xAxis.len - shapeArgs.x;
                                  shapeArgs.y = yAxis.len - shapeArgs.y;
                                  shapeArgs.width = -shapeArgs.width;
                                  shapeArgs.height = -shapeArgs.height;
                              }
  
                              pushRect(
                                  shapeArgs.x + swidth,
                                  shapeArgs.y + swidth,
                                  shapeArgs.width - (swidth * 2),
                                  shapeArgs.height - (swidth * 2),
                                  color
                              );
                          }
                      });
  
                      return;
                  }
  
                  // Extract color axis
                  // each(chart.axes || [], function (a) {
                  // 	if (H.ColorAxis && a instanceof H.ColorAxis) {
                  // 		caxis = a;
                  // 	}
                  // });
  
                  each(sdata, function(d, i) {
                      var x,
                          y,
                          z,
                          px = false,
                          nx = false,
                          // This is in fact used.
                          low, //eslint-disable-line no-unused-vars
                          chartDestroyed = typeof chart.index === 'undefined',
                          nextInside = false,
                          prevInside = false,
                          pcolor = false,
                          drawAsBar = asBar[series.type],
                          isXInside = false,
                          isYInside = true;
  
                      if (chartDestroyed) {
                          return false;
                      }
  
                      // Uncomment this to enable color by point.
                      // This currently left disabled as the charts look really ugly
                      // when enabled and there's a lot of points.
                      // Leaving in for the future (tm).
                      // if (colorByPoint) {
                      // 	colorIndex = ++colorIndex % series.chart.options.colors.length;
                      // 	pcolor = toRGBAFast(series.chart.options.colors[colorIndex]);
                      // 	pcolor[0] /= 255.0;
                      // 	pcolor[1] /= 255.0;
                      // 	pcolor[2] /= 255.0;
                      // }
  
                      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];
                          }
  
                          if (d.length >= 3) {
                              z = d[2];
  
                              if (d[2] > inst.zMax) {
                                  inst.zMax = d[2];
                              }
  
                              if (d[2] < inst.zMin) {
                                  inst.zMin = d[2];
                              }
                          }
  
                      } else {
                          x = d;
                          y = yData[i];
  
                          if (sdata[i + 1]) {
                              nx = sdata[i + 1];
                          }
  
                          if (sdata[i - 1]) {
                              px = sdata[i - 1];
                          }
  
                          if (zData && zData.length) {
                              z = zData[i];
  
                              if (zData[i] > inst.zMax) {
                                  inst.zMax = zData[i];
                              }
  
                              if (zData[i] < inst.zMin) {
                                  inst.zMin = zData[i];
                              }
                          }
                      }
  
                      if (nx && nx >= xMin && nx <= xMax) {
                          nextInside = true;
                      }
  
                      if (px && px >= xMin && px <= xMax) {
                          prevInside = true;
                      }
  
                      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;
                      }
  
                      if (!series.requireSorting) {
                          isYInside = y >= yMin && y <= yMax;
                      }
  
                      if (x > xMax && closestRight.x < xMax) {
                          closestRight.x = x;
                          closestRight.y = y;
                      }
  
                      if (x < xMin && closestLeft.x < xMin) {
                          closestLeft.x = x;
                          closestLeft.y = y;
                      }
  
                      if (y !== 0 && (!y || !isYInside)) {
                          return;
                      }
  
                      if (x >= xMin && x <= xMax) {
                          isXInside = true;
                      }
  
                      if (!isXInside && !nextInside && !prevInside) {
                          return;
                      }
  
                      // Skip translations - temporary floating point fix
                      if (!settings.useGPUTranslations) {
                          inst.skipTranslation = true;
                          x = xAxis.toPixels(x, true);
                          y = yAxis.toPixels(y, true);
                      }
  
                      if (drawAsBar) {
  
                          maxVal = y;
                          minVal = 0;
  
                          if (y < 0) {
                              minVal = y;
                              y = 0;
                          }
  
                          if (!settings.useGPUTranslations) {
                              minVal = yAxis.toPixels(minVal, true);
                          }
  
                          // Need to add an extra point here
                          vertice(x, minVal, 0, 0, pcolor);
                      }
  
                      // No markers on out of bounds things.
                      // Out of bound things are shown if and only if the next
                      // or previous point is inside the rect.
                      if (inst.hasMarkers) { // && isXInside) {
                          // x = H.correctFloat(
                          // 	Math.min(Math.max(-1e5, xAxis.translate(
                          // 		x,
                          // 		0,
                          // 		0,
                          // 		0,
                          // 		1,
                          // 		0.5,
                          // 		false
                          // 	)), 1e5)
                          // );
  
                          if (lastX !== false) {
                              series.closestPointRangePx = Math.min(
                                  series.closestPointRangePx,
                                  Math.abs(x - lastX)
                              );
                          }
                      }
  
                      vertice(
                          x,
                          y,
                          0,
                          series.type === 'bubble' ? (z || 1) : 2,
                          pcolor
                      );
  
                      // Uncomment this to support color axis.
                      // if (caxis) {
                      // 	color = H.color(caxis.toColor(y)).rgba;
  
                      // 	inst.colorData.push(color[0] / 255.0);
                      // 	inst.colorData.push(color[1] / 255.0);
                      // 	inst.colorData.push(color[2] / 255.0);
                      // 	inst.colorData.push(color[3]);
                      // }
  
                      lastX = x;
  
                      //return true;
                  });
  
                  function pushSupplementPoint(point) {
                      if (!settings.useGPUTranslations) {
                          inst.skipTranslation = true;
                          point.x = xAxis.toPixels(point.x, true);
                          point.y = yAxis.toPixels(point.y, true);
                      }
  
                      // We should only do this for lines, and we should ignore markers
                      // since there's no point here that would have a marker.
  
                      vertice(
                          point.x,
                          point.y,
                          0,
                          2
                      );
                  }
  
                  if (!lastX) {
                      // There are no points within the selected range
                      pushSupplementPoint(closestLeft);
                      pushSupplementPoint(closestRight);
                  }
              }
  
              /*
               * Push a series to the renderer
               * If we render the series immediatly, we don't have to loop later
               * @param s {Highchart.Series} - the series to push
               */
              function pushSeries(s) {
                  if (series.length > 0) {
                      series[series.length - 1].to = data.length;
                      if (series[series.length - 1].hasMarkers) {
                          series[series.length - 1].markerTo = markerData.length;
                      }
                  }
  
                  if (settings.timeSeriesProcessing) {
                      console.time('building ' + s.type + ' series'); //eslint-disable-line no-console
                  }
  
                  series.push({
                      from: data.length,
                      markerFrom: markerData.length,
                      // Push RGBA values to this array to use per. point coloring.
                      // It should be 0-padded, so each component should be pushed in
                      // succession.
                      colorData: [],
                      series: s,
                      zMin: Number.MAX_VALUE,
                      zMax: -Number.MAX_VALUE,
                      hasMarkers: s.options.marker ? s.options.marker.enabled !== false : false,
                      showMarksers: true,
                      drawMode: ({
                          'area': 'lines',
                          'arearange': 'lines',
                          'areaspline': 'line_strip',
                          'column': 'lines',
                          'line': 'line_strip',
                          'scatter': 'points',
                          'heatmap': 'triangles',
                          'treemap': 'triangles',
                          'bubble': 'points'
                      })[s.type] || 'line_strip'
                  });
  
                  // Add the series data to our buffer(s)
                  pushSeriesData(s, series[series.length - 1]);
  
                  if (settings.timeSeriesProcessing) {
                      console.timeEnd('building ' + s.type + ' series'); //eslint-disable-line no-console
                  }
              }
  
              /*
               * Flush the renderer.
               * This removes pushed series and vertices.
               * Should be called after clearing and before rendering
               */
              function flush() {
                  series = [];
                  exports.data = data = [];
                  markerData = [];
              }
  
              /*
               * Pass x-axis to shader
               * @param axis {Highcharts.Axis} - the x-axis
               */
              function setXAxis(axis) {
                  if (!shader) {
                      return;
                  }
  
                  shader.setUniform('xAxisTrans', axis.transA);
                  shader.setUniform('xAxisMin', axis.min);
                  shader.setUniform('xAxisMinPad', axis.minPixelPadding);
                  shader.setUniform('xAxisPointRange', axis.pointRange);
                  shader.setUniform('xAxisLen', axis.len);
                  shader.setUniform('xAxisPos', axis.pos);
                  shader.setUniform('xAxisCVSCoord', !axis.horiz);
              }
  
              /*
               * Pass y-axis to shader
               * @param axis {Highcharts.Axis} - the y-axis
               */
              function setYAxis(axis) {
                  if (!shader) {
                      return;
                  }
  
                  shader.setUniform('yAxisTrans', axis.transA);
                  shader.setUniform('yAxisMin', axis.min);
                  shader.setUniform('yAxisMinPad', axis.minPixelPadding);
                  shader.setUniform('yAxisPointRange', axis.pointRange);
                  shader.setUniform('yAxisLen', axis.len);
                  shader.setUniform('yAxisPos', axis.pos);
                  shader.setUniform('yAxisCVSCoord', !axis.horiz);
              }
  
              /*
               * Set the translation threshold
               * @param has {boolean} - has threshold flag
               * @param translation {Float} - the threshold
               */
              function setThreshold(has, translation) {
                  shader.setUniform('hasThreshold', has);
                  shader.setUniform('translatedThreshold', translation);
              }
  
              /*
               * Render the data
               * This renders all pushed series.
               */
              function render(chart) {
  
                  if (chart) {
                      if (!chart.chartHeight || !chart.chartWidth) {
                          //chart.setChartSize();
                      }
  
                      width = chart.chartWidth || 800;
                      height = chart.chartHeight || 400;
                  } else {
                      return false;
                  }
  
                  if (!gl || !width || !height) {
                      return false;
                  }
  
                  if (settings.timeRendering) {
                      console.time('gl rendering'); //eslint-disable-line no-console
                  }
  
                  shader.bind();
  
                  gl.viewport(0, 0, width, height);
                  shader.setPMatrix(orthoMatrix(width, height));
  
                  if (settings.lineWidth > 1 && !H.isMS) {
                      gl.lineWidth(settings.lineWidth);
                  }
  
                  vbuffer.build(exports.data, 'aVertexPosition', 4);
                  vbuffer.bind();
  
                  if (textureIsReady) {
                      gl.bindTexture(gl.TEXTURE_2D, circleTextureHandle);
                      shader.setTexture(circleTextureHandle);
                  }
  
                  shader.setInverted(chart.options.chart ? chart.options.chart.inverted : false);
  
                  // Render the series
                  each(series, function(s, si) {
                      var options = s.series.options,
                          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 >
                              2 * ((
                                  options.marker ?
                                  options.marker.radius :
                                  10
                              ) || 10)
                          ),
                          fillColor = s.series.fillOpacity ?
                          new Color(s.series.color).setOpacity(
                              pick(options.fillOpacity, 0.85)
                          ).get() :
                          s.series.color,
                          color;
  
                      vbuffer.bind();
  
                      if (options.colorByPoint) {
                          fillColor = s.series.chart.options.colors[si];
                      }
  
                      color = H.color(fillColor).rgba;
  
                      if (!settings.useAlpha) {
                          color[3] = 1.0;
                      }
  
                      //Blending
                      if (options.boostBlending === 'add') {
                          gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
                          gl.blendEquation(gl.FUNC_ADD);
  
                      } else if (options.boostBlending === 'mult') {
                          gl.blendFunc(gl.DST_COLOR, gl.ZERO);
  
                      } else if (options.boostBlending === 'darken') {
                          gl.blendFunc(gl.ONE, gl.ONE);
                          gl.blendEquation(gl.FUNC_MIN);
  
                      } else {
                          //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);//, gl.ONE, gl.ZERO);
                          //gl.blendEquation(gl.FUNC_ADD);
                          gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
                      }
  
                      shader.reset();
  
                      // If there are entries in the colorData buffer, build and bind it.
                      if (s.colorData.length > 0) {
                          shader.setUniform('hasColor', 1.0);
                          cbuffer = GLVertexBuffer(gl, shader); //eslint-disable-line new-cap
                          cbuffer.build(s.colorData, 'aColor', 4);
                          cbuffer.bind();
                      }
  
                      // Set series specific uniforms
                      shader.setColor(color);
                      setXAxis(s.series.xAxis);
                      setYAxis(s.series.yAxis);
                      setThreshold(hasThreshold, translatedThreshold);
  
                      if (s.drawMode === 'points') {
                          if (options.marker && options.marker.radius) {
                              shader.setPointSize(options.marker.radius * 2.0);
                          } else {
                              shader.setPointSize(1);
                          }
                      }
  
                      // If set to true, the toPixels translations in the shader
                      // is skipped, i.e it's assumed that the value is a pixel coord.
                      shader.setSkipTranslation(s.skipTranslation);
  
                      if (s.series.type === 'bubble') {
                          shader.setBubbleUniforms(s.series, s.zMin, s.zMax);
                      }
  
                      shader.setDrawAsCircle((asCircle[s.series.type] && textureIsReady) || false);
  
                      // Do the actual rendering
                      vbuffer.render(s.from, s.to, s.drawMode);
  
                      if (s.hasMarkers && showMarkers) {
                          if (options.marker && options.marker.radius) {
                              shader.setPointSize(options.marker.radius * 2.0);
                          } else {
                              shader.setPointSize(10);
                          }
                          shader.setDrawAsCircle(true);
                          vbuffer.render(s.from, s.to, 'POINTS');
                      }
                  });
  
                  vbuffer.destroy();
  
                  if (settings.timeRendering) {
                      console.timeEnd('gl rendering'); //eslint-disable-line no-console
                  }
  
                  flush();
  
                  if (postRenderCallback) {
                      postRenderCallback();
                  }
              }
  
              /*
               * Render the data when ready
               */
              function renderWhenReady(chart) {
                  clear();
  
                  if (chart.renderer.forExport) {
                      return render(chart);
                  }
  
                  if (isInited) {
                      render(chart);
                  } else {
                      setTimeout(function() {
                          renderWhenReady(chart);
                      }, 1);
                  }
              }
  
              /*
               * Set the viewport size in pixels
               * Creates an orthographic perspective matrix and applies it.
               * @param w {Integer} - the width of the viewport
               * @param h {Integer} - the height of the viewport
               */
              function setSize(w, h) {
                  // Skip if there's no change
                  if (width === w && h === h) {
                      return;
                  }
  
                  width = w;
                  height = h;
  
                  shader.bind();
                  shader.setPMatrix(orthoMatrix(width, height));
              }
  
              /*
               * Init OpenGL
               * @param canvas {HTMLCanvas} - the canvas to render to
               */
              function init(canvas, noFlush) {
                  var i = 0,
                      contexts = [
                          'webgl',
                          'experimental-webgl',
                          'moz-webgl',
                          'webkit-3d'
                      ];
  
                  isInited = false;
  
                  if (!canvas) {
                      return false;
                  }
  
                  if (settings.timeSetup) {
                      console.time('gl setup'); //eslint-disable-line no-console
                  }
  
                  for (; i < contexts.length; i++) {
                      gl = canvas.getContext(contexts[i]);
                      if (gl) {
                          break;
                      }
                  }
  
                  if (gl) {
                      if (!noFlush) {
                          flush();
                      }
                  } else {
                      return false;
                  }
  
                  gl.enable(gl.BLEND);
                  // gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
                  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
                  gl.disable(gl.DEPTH_TEST);
                  gl.depthMask(gl.FALSE);
  
                  shader = GLShader(gl); //eslint-disable-line new-cap
                  vbuffer = GLVertexBuffer(gl, shader); //eslint-disable-line new-cap
  
                  textureIsReady = false;
  
                  // Set up the circle texture used for bubbles
                  circleTextureHandle = gl.createTexture();
  
                  // Draw the circle
                  circleTexture.width = 512;
                  circleTexture.height = 512;
  
                  circleCtx.fillStyle = '#FFF';
                  circleCtx.beginPath();
                  circleCtx.arc(256, 256, 256, 0, 2 * Math.PI);
                  circleCtx.fill();
  
                  try {
  
                      gl.bindTexture(gl.TEXTURE_2D, circleTextureHandle);
  
                      gl.texImage2D(
                          gl.TEXTURE_2D,
                          0,
                          gl.RGBA,
                          gl.RGBA,
                          gl.UNSIGNED_BYTE,
                          circleTexture
                      );
  
                      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
  
                      gl.generateMipmap(gl.TEXTURE_2D);
  
                      gl.bindTexture(gl.TEXTURE_2D, null);
  
                      textureIsReady = true;
                  } catch (e) {}
  
                  isInited = true;
  
                  if (settings.timeSetup) {
                      console.timeEnd('gl setup'); //eslint-disable-line no-console
                  }
  
                  return true;
              }
  
              /*
               * Check if we have a valid OGL context
               * @returns {Boolean} - true if the context is valid
               */
              function valid() {
                  return gl !== false;
              }
  
              /*
               * Check if the renderer has been initialized
               * @returns {Boolean} - true if it has, false if not
               */
              function inited() {
                  return isInited;
              }
  
              function destroy() {
                  vbuffer.destroy();
                  shader.destroy();
                  if (gl) {
                      if (circleTextureHandle) {
                          gl.deleteTexture(circleTextureHandle);
                      }
                      gl.canvas.width = 1;
                      gl.canvas.height = 1;
                  }
              }
  
              ////////////////////////////////////////////////////////////////////////////
              exports = {
                  allocateBufferForSingleSeries: allocateBufferForSingleSeries,
                  pushSeries: pushSeries,
                  setSize: setSize,
                  inited: inited,
                  setThreshold: setThreshold,
                  init: init,
                  render: renderWhenReady,
                  settings: settings,
                  valid: valid,
                  clear: clear,
                  flush: flush,
                  setXAxis: setXAxis,
                  setYAxis: setYAxis,
                  data: data,
                  gl: getGL,
                  allocateBuffer: allocateBuffer,
                  destroy: destroy,
                  setOptions: setOptions
              };
  
              return exports;
          }
  
          // END OF WEBGL ABSTRACTIONS
          ////////////////////////////////////////////////////////////////////////////////
  
          /*
           * Create a canvas + context and attach it to the target
           * @param target {Highcharts.Chart|Highcharts.Series} - the canvas target
           * @param chart {Highcharts.Chart} - the chart
           */
          function createAndAttachRenderer(chart, series) {
              var width = chart.chartWidth,
                  height = chart.chartHeight,
                  target = chart,
                  targetGroup = chart.seriesGroup || series.group,
                  swapXY = function(proceed, x, y, a, b, c, d) {
                      proceed.call(series, y, x, a, b, c, d);
                  };
  
              if (isChartSeriesBoosting(chart)) {
                  target = chart;
              } else {
                  target = series;
              }
  
              if (target.ogl) {
                  //target.ogl.destroy();
              }
  
              if (!target.image) {
                  target.canvas = doc.createElement('canvas');
  
                  target.image = chart.renderer.image(
                      '',
                      0,
                      0,
                      width,
                      height
                  ).add(targetGroup);
  
                  target.boostClipRect = chart.renderer.clipRect(
                      chart.plotLeft,
                      chart.plotTop,
                      chart.plotWidth,
                      chart.chartHeight
                  );
  
                  target.image.clip(target.boostClipRect);
  
                  if (target.inverted || (target.chart && target.chart.inverted)) {
                      each(['moveTo', 'lineTo', 'rect', 'arc'], function(fn) {
                          wrap(false, fn, swapXY);
                      });
                  }
  
                  if (target instanceof H.Chart) {
                      target.markerGroup = target.renderer.g().add(targetGroup);
  
                      target.markerGroup.translate(series.xAxis.pos, series.yAxis.pos);
                  }
              }
  
              target.canvas.width = width;
              target.canvas.height = height;
  
              target.image.attr({
                  x: 0,
                  y: 0,
                  width: width,
                  height: height,
                  style: 'pointer-events: none'
              });
  
              target.boostClipRect.attr({
                  x: chart.plotLeft,
                  y: chart.plotTop,
                  width: chart.plotWidth,
                  height: chart.chartHeight
              });
  
              if (!target.ogl) {
  
  
                  target.ogl = GLRenderer(function() { // eslint-disable-line new-cap
                      target.image.attr({
                          href: target.canvas.toDataURL('image/png')
                      });
  
                      // Destroy gl context when we're done with it
                      target.ogl.destroy();
                      target.ogl = false;
                  }); //eslint-disable-line new-cap
  
                  target.ogl.init(target.canvas);
                  // target.ogl.clear();
                  target.ogl.setOptions(chart.options.boost || {});
  
                  if (target instanceof H.Chart) {
                      target.ogl.allocateBuffer(chart);
                  }
              }
  
              target.ogl.setSize(width, height);
  
              return target.ogl;
          }
  
          /*
           * Performs the actual render if the renderer is
           * attached to the series.
           * @param renderer {OGLRenderer} - the renderer
           * @param series {Highcharts.Series} - the series
           */
          function renderIfNotSeriesBoosting(renderer, series, chart) {
              if (renderer &&
                  series.image &&
                  series.canvas &&
                  !isChartSeriesBoosting(chart || series.chart)
              ) {
                  renderer.render(chart || series.chart);
              }
          }
  
          function allocateIfNotSeriesBoosting(renderer, series) {
              if (renderer &&
                  series.image &&
                  series.canvas &&
                  !isChartSeriesBoosting(series.chart)
              ) {
                  renderer.allocateBufferForSingleSeries(series);
              }
          }
  
          /*
           * An "async" foreach loop.
           * Uses a setTimeout to keep the loop from blocking the UI thread
           * @param arr {Array} - the array to loop through
           * @param fn {Function} - the callback to call for each item
           * @param finalFunc {Function} - the callback to call when done
           * @param chunkSize {Number} - the number of iterations per. timeout
           * @param i {Number} - the current index
           * @param noTimeout {Boolean} - set to true to skip timeouts
           */
          function eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout) {
              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;
              }
              if (proceed) {
                  if (i < arr.length) {
  
                      if (noTimeout) {
                          eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout);
                      } else if (win.requestAnimationFrame) {
                          //If available, do requestAnimationFrame - shaves off a few ms
                          win.requestAnimationFrame(function() {
                              eachAsync(arr, fn, finalFunc, chunkSize, i);
                          });
                      } else {
                          setTimeout(function() {
                              eachAsync(arr, fn, finalFunc, chunkSize, i);
                          });
                      }
  
                  } else if (finalFunc) {
                      finalFunc();
                  }
              }
          }
  
          ////////////////////////////////////////////////////////////////////////////////
          // Following is the parts of the boost that's common between OGL/Legacy
  
          /**
           * Return a full Point object based on the index.
           * The boost module uses stripped point objects for performance reasons.
           * @param   {Number} boostPoint A stripped-down point object
           * @returns {Object} A Point object as per http://api.highcharts.com/highcharts#Point
           */
          Series.prototype.getPoint = function(boostPoint) {
              var point = boostPoint,
                  xData = this.xData || this.options.xData || this.processedXData || false;
  
              if (boostPoint && !(boostPoint instanceof this.pointClass)) {
                  point = (new this.pointClass()).init( // eslint-disable-line new-cap
                      this,
                      this.options.data[boostPoint.i],
                      xData ? xData[boostPoint.i] : undefined
                  );
  
                  point.category = point.x;
  
                  point.dist = boostPoint.dist;
                  point.distX = boostPoint.distX;
                  point.plotX = boostPoint.plotX;
                  point.plotY = boostPoint.plotY;
                  point.index = boostPoint.i;
              }
  
              return point;
          };
  
          /**
           * Return a point instance from the k-d-tree
           */
          wrap(Series.prototype, 'searchPoint', function(proceed) {
              return this.getPoint(
                  proceed.apply(this, [].slice.call(arguments, 1))
              );
          });
  
          /**
           * Extend series.destroy to also remove the fake k-d-tree points (#5137).
           * Normally this is handled by Series.destroy that calls Point.destroy,
           * but the fake search points are not registered like that.
           */
          wrap(Series.prototype, 'destroy', function(proceed) {
              var series = this,
                  chart = series.chart;
  
              if (chart.markerGroup === series.markerGroup) {
                  series.markerGroup = null;
              }
  
              if (chart.hoverPoints) {
                  chart.hoverPoints = grep(chart.hoverPoints, function(point) {
                      return point.series === series;
                  });
              }
  
              if (chart.hoverPoint && chart.hoverPoint.series === series) {
                  chart.hoverPoint = null;
              }
  
              proceed.call(this);
          });
  
          /**
           * Do not compute extremes when min and max are set.
           * If we use this in the core, we can add the hook
           * to hasExtremes to the methods directly.
           */
          wrap(Series.prototype, 'getExtremes', function(proceed) {
              if (!isSeriesBoosting(this) || (!this.hasExtremes || !this.hasExtremes())) {
                  return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
              }
          });
  
          // Set default options
          each([
                  'area',
                  'arearange',
                  'column',
                  'line',
                  'scatter',
                  'heatmap',
                  'bubble',
                  'treemap',
                  'heatmap'
              ],
              function(type) {
                  if (plotOptions[type]) {
                      plotOptions[type].boostThreshold = 5000;
                      plotOptions[type].boostData = [];
                  }
              }
          );
  
          /**
           * 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.
           *
           * Note that we're not overriding any of these for heatmaps.
           */
          each([
              'translate',
              'generatePoints',
              'drawTracker',
              'drawPoints',
              'render'
          ], function(method) {
              function branch(proceed) {
                  var letItPass = this.options.stacking &&
                      (method === 'translate' || method === 'generatePoints');
  
                  if (!isSeriesBoosting(this) ||
                      letItPass ||
                      this.type === 'heatmap' ||
                      this.type === 'treemap'
                  ) {
  
                      // Clear image
                      if (method === 'render' && this.image && !isChartSeriesBoosting(this.chart)) {
                          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 - their translate method is already wrapped
              if (method === 'translate') {
                  if (seriesTypes.column) {
                      wrap(seriesTypes.column.prototype, method, branch);
                  }
  
                  if (seriesTypes.arearange) {
                      wrap(seriesTypes.arearange.prototype, method, branch);
                  }
  
                  if (seriesTypes.treemap) {
                      wrap(seriesTypes.treemap.prototype, method, branch);
                  }
  
                  if (seriesTypes.heatmap) {
                      wrap(seriesTypes.heatmap.prototype, method, branch);
                  }
              }
          });
  
          /*
           * Returns true if the current browser supports webgl
           */
          function hasWebGLSupport() {
              var i = 0,
                  canvas,
                  contexts = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'],
                  context = false;
  
              if (typeof win.WebGLRenderingContext !== 'undefined') {
                  canvas = doc.createElement('canvas');
  
                  for (; i < contexts.length; i++) {
                      try {
                          context = canvas.getContext(contexts[i]);
                          if (typeof context !== 'undefined' && context !== null) {
                              return true;
                          }
                      } catch (e) {
  
                      }
                  }
              }
  
              return false;
          }
  
          /* Used for treemap|heatmap.drawPoints */
          function pointDrawHandler(proceed) {
              if (!isSeriesBoosting(this)) {
                  return proceed.call(this);
              }
  
              //Make sure we have a valid OGL context
              var renderer = createAndAttachRenderer(this.chart, this);
  
              if (renderer) {
                  allocateIfNotSeriesBoosting(renderer, this);
                  renderer.pushSeries(this);
              }
  
              renderIfNotSeriesBoosting(renderer, this);
          }
  
          ////////////////////////////////////////////////////////////////////////////////
          // We're wrapped in a closure, so just return if there's no webgl support
  
          if (!hasWebGLSupport()) {
              if (typeof H.initCanvasBoost !== 'undefined') {
                  // Fallback to canvas boost
                  H.initCanvasBoost();
              } else {
                  H.error(26);
              }
          } else {
  
              ////////////////////////////////////////////////////////////////////////////
              // GL-SPECIFIC WRAPPINGS FOLLOWS
  
              /** If the series is a heatmap or treemap, or if the series is not boosting
               *  do the default behaviour. Otherwise, process if the series has no
               *  extremes.
               */
              wrap(Series.prototype, 'processData', function(proceed) {
                  // If this is a heatmap, do default behaviour
                  if (!isSeriesBoosting(this) ||
                      this.type === 'heatmap' ||
                      this.type === 'treemap') {
                      proceed.apply(this, Array.prototype.slice.call(arguments, 1));
                  }
  
                  if (!this.hasExtremes || !this.hasExtremes(true)) {
                      proceed.apply(this, Array.prototype.slice.call(arguments, 1));
                  }
              });
  
              H.extend(Series.prototype, {
                  pointRange: 0,
                  directTouch: false,
                  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();
                          }
                      });
                  },
  
                  renderCanvas: function() {
                      var series = this,
                          options = series.options || {},
                          renderer = false,
                          chart = series.chart,
                          xAxis = this.xAxis,
                          yAxis = this.yAxis,
                          //ctx,
                          //c = 0,
                          xData = options.xData || series.processedXData,
                          yData = options.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,
                          enableMouseTracking = options.enableMouseTracking !== false,
                          threshold = options.threshold,
                          yBottom = yAxis.getThreshold(threshold),
                          isRange = series.pointArrayMap &&
                          series.pointArrayMap.join(',') === 'low,high',
                          isStacked = !!options.stacking,
                          cropStart = series.cropStart || 0,
                          requireSorting = series.requireSorting,
                          useRaw = !xData,
                          minVal,
                          maxVal,
                          minI,
                          maxI,
  
                          addKDPoint = function(clientX, plotY, i) {
                              //Shaves off about 60ms compared to repeated concatination
                              index = 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[index]) {
                                  pointTaken[index] = true;
  
                                  if (chart.inverted) {
                                      clientX = xAxis.len - clientX;
                                      plotY = yAxis.len - plotY;
                                  }
  
                                  points.push({
                                      clientX: clientX,
                                      plotX: clientX,
                                      plotY: plotY,
                                      i: cropStart + i
                                  });
                              }
                          };
  
                      // Get or create the renderer
                      renderer = createAndAttachRenderer(chart, series);
  
                      if (!this.visible) {
                          if (!isChartSeriesBoosting(chart) && renderer) {
                              renderer.clear();
                              this.image.attr({
                                  href: ''
                              });
                          }
                          return;
                      }
  
                      // If we are zooming out from SVG mode, destroy the graphics
                      if (this.points || this.graph) {
                          this.destroyGraphics();
                      }
  
                      // If we're rendering per. series we should create the marker groups
                      // as usual.
                      if (!isChartSeriesBoosting(chart)) {
                          this.markerGroup = series.plotGroup(
                              'markerGroup',
                              'markers',
                              true,
                              1,
                              chart.seriesGroup
                          );
                      } else {
                          //Use a single group for the markers
                          this.markerGroup = chart.markerGroup;
                      }
  
                      points = this.points = [];
  
                      // Do not start building while drawing
                      series.buildKDTree = noop;
  
                      if (renderer) {
                          allocateIfNotSeriesBoosting(renderer, this);
                          renderer.pushSeries(series);
                          // Perform the actual renderer if we're on series level
                          renderIfNotSeriesBoosting(renderer, this, chart);
                          //console.log(series, chart);
                      }
  
                      /* This builds the KD-tree */
                      function processPoint(d, i) {
                          var x,
                              y,
                              clientX,
                              plotY,
                              isNull,
                              low,
                              chartDestroyed = typeof chart.index === 'undefined',
                              isYInside = true;
  
                          if (!chartDestroyed) {
                              if (useRaw) {
                                  x = d[0];
                                  y = d[1];
                              } else {
                                  x = d;
                                  y = yData[i];
                              }
  
                              // 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) {
  
                                  // We use ceil to allow the KD tree to work with sub
                                  // pixels, which can be used in boost to space pixels
                                  clientX = Math.ceil(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);
  
                                              addKDPoint(clientX, plotY, maxI);
                                              if (yBottom !== plotY) {
                                                  addKDPoint(clientX, yBottom, minI);
                                              }
                                          }
  
                                          minI = maxI = undefined;
                                          lastClientX = clientX;
                                      }
                                  } else {
                                      plotY = Math.ceil(yAxis.toPixels(y, true));
                                      addKDPoint(clientX, plotY, i);
                                  }
                              }
                          }
  
                          return !chartDestroyed;
                      }
  
                      function doneProcessing() {
                          fireEvent(series, 'renderedCanvas');
                          // Pass tests in Pointer.
                          // Replace this with a single property, and replace when zooming
                          // in below boostThreshold.
                          series.directTouch = false;
                          series.options.stickyTracking = true;
  
                          // Go back to prototype, ready to build
                          delete series.buildKDTree;
                          series.buildKDTree();
                      }
  
                      // Loop over the points to build the k-d tree
                      eachAsync(
                          isStacked ? series.data : (xData || rawData),
                          processPoint,
                          doneProcessing,
                          chart.renderer.forExport ? Number.MAX_VALUE : undefined
                      );
                  }
              });
  
              /*
               * We need to handle heatmaps separatly, since we can't perform the
               * size/color calculations in the shader easily.
               *
               * This likely needs future optimization.
               *
               */
              each(['heatmap', 'treemap'],
                  function(t) {
                      if (seriesTypes[t]) {
                          wrap(seriesTypes[t].prototype, 'drawPoints', pointDrawHandler);
                          seriesTypes[t].prototype.directTouch = false; // Use k-d-tree
                      }
                  }
              );
  
              if (seriesTypes.bubble) {
                  // By default, the bubble series does not use the KD-tree, so force it
                  // to.
                  delete seriesTypes.bubble.prototype.buildKDTree;
                  seriesTypes.bubble.prototype.directTouch = false;
  
                  // Needed for markers to work correctly
                  wrap(
                      seriesTypes.bubble.prototype,
                      'markerAttribs',
                      function(proceed) {
                          if (isSeriesBoosting(this)) {
                              return false;
                          }
                          return proceed.apply(this, [].slice.call(arguments, 1));
                      }
                  );
              }
  
              seriesTypes.scatter.prototype.fill = true;
  
              extend(seriesTypes.area.prototype, {
                  fill: true,
                  fillOpacity: true,
                  sampling: true
              });
  
              extend(seriesTypes.column.prototype, {
                  fill: true,
                  sampling: true
              });
  
              wrap(Series.prototype, 'setVisible', function(proceed, vis) {
                  proceed.call(this, vis, false);
                  if (this.visible === false && this.ogl && this.canvas && this.image) {
                      this.ogl.clear();
                      this.image.attr({
                          href: ''
                      });
                  } else {
                      this.chart.redraw();
                  }
              });
  
              /**
               * Take care of the canvas blitting
               */
              H.Chart.prototype.callbacks.push(function(chart) {
  
                  /* Convert chart-level canvas to image */
                  function canvasToSVG() {
                      if (chart.ogl && isChartSeriesBoosting(chart)) {
                          chart.ogl.render(chart);
                      }
                  }
  
                  /* Clear chart-level canvas */
                  function preRender() {
  
                      if (!isChartSeriesBoosting(chart) && chart.didBoost) {
                          chart.didBoost = false;
                          // Clear the canvas
                          if (chart.image) {
                              chart.image.attr({
                                  href: ''
                              });
                          }
                      }
  
                      if (chart.canvas && chart.ogl && isChartSeriesBoosting(chart)) {
                          chart.didBoost = true;
  
                          // Allocate
                          chart.ogl.allocateBuffer(chart);
                      }
  
                      //see #6518 + #6739
                      if (chart.markerGroup && chart.xAxis && chart.xAxis.length > 0 && chart.yAxis && chart.yAxis.length > 0) {
                          chart.markerGroup.translate(
                              chart.xAxis[0].pos,
                              chart.yAxis[0].pos
                          );
                      }
  
                  }
  
                  addEvent(chart, 'predraw', preRender);
                  addEvent(chart, 'render', canvasToSVG);
              });
          } // if hasCanvasSupport
  
      }(Highcharts));
  }));
  