管道基础大数据平台系统开发-【前端】-新系統界面
编辑 | blame | 历史 | 原始文档
/*
 *  CesiumHeatmap.js v0.1 | Cesium Heatmap Library
 *
 *  Works with heatmap.js v2.0.0: http://www.patrick-wied.at/static/heatmapjs/
 */
(function(window) {
  'use strict'

  function define_CesiumHeatmap() {
    var CesiumHeatmap = {
      defaults: {
        useEntitiesIfAvailable: true, //whether to use entities if a Viewer is supplied or always use an ImageryProvider
        minCanvasSize: 700,           // minimum size (in pixels) for the heatmap canvas
        maxCanvasSize: 2000,          // maximum size (in pixels) for the heatmap canvas
        radiusFactor: 60,             // data point size factor used if no radius is given (the greater of height and width divided by this number yields the used radius)
        spacingFactor: 1.5,           // extra space around the borders (point radius multiplied by this number yields the spacing)
        maxOpacity: 0.8,              // the maximum opacity used if not given in the heatmap options object
        minOpacity: 0.1,              // the minimum opacity used if not given in the heatmap options object
        blur: 0.85,                   // the blur used if not given in the heatmap options object
        gradient: {                   // the gradient used if not given in the heatmap options object
          '.3': 'blue',
          '.65': 'yellow',
          '.8': 'orange',
          '.95': 'red'
        }
      }
    }

    /*  Create a CesiumHeatmap instance
     *
     *  cesium:  the CesiumWidget or Viewer instance
     *  bb:      the WGS84 bounding box like {north, east, south, west}
     *  options: a heatmap.js options object (see http://www.patrick-wied.at/static/heatmapjs/docs.html#h337-create)
     */
    CesiumHeatmap.create = function(cesium, bb, options) {
      var instance = new CHInstance(cesium, bb, options)
      return instance
    }

    CesiumHeatmap._changeContainerWidthHeight = function(width, height, id) {
      var c = document.getElementById(id)
      c.setAttribute('style', 'width: ' + width + 'px; height: ' + height + 'px; margin: 0px; display: none;')
    }

    CesiumHeatmap._getContainer = function(width, height, id) {
      var c = document.createElement('div')
      if (id) {
        c.setAttribute('id', id)
      }
      c.setAttribute('style', 'width: ' + width + 'px; height: ' + height + 'px; margin: 0px; display: none;')
      document.body.appendChild(c)
      return c
    }

    CesiumHeatmap._getImageryProvider = function(instance) {
      //var n = (new Date()).getTime();
      var d = instance._heatmap.getDataURL()
      //console.log("Create data URL: " + ((new Date()).getTime() - n));

      //var n = (new Date()).getTime();
      var imgprov = new Cesium.SingleTileImageryProvider({
        url: d,
        rectangle: instance._rectangle
      })
      //console.log("Create imageryprovider: " + ((new Date()).getTime() - n));

      imgprov._tilingScheme = new Cesium.WebMercatorTilingScheme({
        rectangleSouthwestInMeters: new Cesium.Cartesian2(instance._mbounds.west, instance._mbounds.south),
        rectangleNortheastInMeters: new Cesium.Cartesian2(instance._mbounds.east, instance._mbounds.north)
      })

      return imgprov
    }

    CesiumHeatmap._getID = function(len) {
      var text = ''
      var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

      for (var i = 0; i < ((len) ? len : 8); i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length))
      }

      return text
    }

    var WMP = new Cesium.WebMercatorProjection()

    /*  Convert a WGS84 location into a mercator location
     *
     *  p: the WGS84 location like {x: lon, y: lat}
     */
    CesiumHeatmap.wgs84ToMercator = function(p) {
      var mp = WMP.project(Cesium.Cartographic.fromDegrees(p.x, p.y))
      return {
        x: mp.x,
        y: mp.y
      }
    }

    /*  Convert a WGS84 bounding box into a mercator bounding box
     *
     *  bb: the WGS84 bounding box like {north, east, south, west}
     */
    CesiumHeatmap.wgs84ToMercatorBB = function(bb) {
      var sw = WMP.project(Cesium.Cartographic.fromDegrees(bb.west, bb.south))
      var ne = WMP.project(Cesium.Cartographic.fromDegrees(bb.east, bb.north))
      return {
        north: ne.y,
        east: ne.x,
        south: sw.y,
        west: sw.x
      }
    }

    /*  Convert a mercator location into a WGS84 location
     *
     *  p: the mercator lcation like {x, y}
     */
    CesiumHeatmap.mercatorToWgs84 = function(p) {
      var wp = WMP.unproject(new Cesium.Cartesian3(p.x, p.y))
      return {
        x: wp.longitude,
        y: wp.latitude
      }
    }

    /*  Convert a mercator bounding box into a WGS84 bounding box
     *
     *  bb: the mercator bounding box like {north, east, south, west}
     */
    CesiumHeatmap.mercatorToWgs84BB = function(bb) {
      var sw = WMP.unproject(new Cesium.Cartesian3(bb.west, bb.south))
      var ne = WMP.unproject(new Cesium.Cartesian3(bb.east, bb.north))
      return {
        north: this.rad2deg(ne.latitude),
        east: this.rad2deg(ne.longitude),
        south: this.rad2deg(sw.latitude),
        west: this.rad2deg(sw.longitude)
      }
    }

    /*  Convert degrees into radians
     *
     *  d: the degrees to be converted to radians
     */
    CesiumHeatmap.deg2rad = function(d) {
      var r = d * (Math.PI / 180.0)
      return r
    }

    /*  Convert radians into degrees
     *
     *  r: the radians to be converted to degrees
     */
    CesiumHeatmap.rad2deg = function(r) {
      var d = r / (Math.PI / 180.0)
      return d
    }

    return CesiumHeatmap
  }

  if (typeof (CesiumHeatmap) === 'undefined') {
    window.CesiumHeatmap = define_CesiumHeatmap()
  } else {
    console.log('CesiumHeatmap already defined.')
  }
})(window)

/*  Initiate a CesiumHeatmap instance
 *
 *  c:  CesiumWidget instance
 *  bb: a WGS84 bounding box like {north, east, south, west}
 *  o:  a heatmap.js options object (see http://www.patrick-wied.at/static/heatmapjs/docs.html#h337-create)
 */
function CHInstance(c, bb, o) {
  if (!bb) {
    return null
  }
  if (!o) {
    o = {}
  }

  this._cesium = c
  this._options = o
  this._id = CesiumHeatmap._getID()

  this._options.gradient = ((this._options.gradient) ? this._options.gradient : CesiumHeatmap.defaults.gradient)
  this._options.maxOpacity = ((this._options.maxOpacity) ? this._options.maxOpacity : CesiumHeatmap.defaults.maxOpacity)
  this._options.minOpacity = ((this._options.minOpacity) ? this._options.minOpacity : CesiumHeatmap.defaults.minOpacity)
  this._options.blur = ((this._options.blur) ? this._options.blur : CesiumHeatmap.defaults.blur)

  this.computeBBAttr(bb)

  this._container = CesiumHeatmap._getContainer(this.width, this.height, this._id)
  this._options.container = this._container
  this._heatmap = h337.create(this._options)
  this._container.children[0].setAttribute('id', this._id + '-hm')
}

// 计算各种属性(关于边界的)
CHInstance.prototype.computeBBAttr = function(bb) {
  this._mbounds = CesiumHeatmap.wgs84ToMercatorBB(bb)
  this._setWidthAndHeight(this._mbounds)
  this._options.radius = Math.round((this._options.radius) ? this._options.radius : ((this.width > this.height) ? this.width / CesiumHeatmap.defaults.radiusFactor : this.height / CesiumHeatmap.defaults.radiusFactor))
  this._spacing = this._options.radius * CesiumHeatmap.defaults.spacingFactor
  this._xoffset = this._mbounds.west
  this._yoffset = this._mbounds.south
  this.width = Math.round(this.width + this._spacing * 2)
  this.height = Math.round(this.height + this._spacing * 2)

  this._mbounds.west -= this._spacing * this._factor
  this._mbounds.east += this._spacing * this._factor
  this._mbounds.south -= this._spacing * this._factor
  this._mbounds.north += this._spacing * this._factor

  this.bounds = CesiumHeatmap.mercatorToWgs84BB(this._mbounds)
  this._rectangle = Cesium.Rectangle.fromDegrees(this.bounds.west, this.bounds.south, this.bounds.east, this.bounds.north)
}

// 修改热力图区域
CHInstance.prototype.changeBounds = function(bb) {
  if (!bb) {
    return null
  }
  // 清除旧的热力图layer
  if (this._layer) {
    this._cesium.entities.remove(this._layer)
  }

  this.computeBBAttr(bb)

  CesiumHeatmap._changeContainerWidthHeight(this.width, this.height, this._id)

  this._heatmap.configure({
    width: this.width,
    height: this.height
  })

}


/*  Convert a WGS84 location to the corresponding heatmap location
 *
 *  p: a WGS84 location like {x:lon, y:lat}
 */
CHInstance.prototype.wgs84PointToHeatmapPoint = function(p) {
  return this.mercatorPointToHeatmapPoint(CesiumHeatmap.wgs84ToMercator(p))
}

/*  Convert a mercator location to the corresponding heatmap location
 *
 *  p: a WGS84 location like {x: lon, y:lat}
 */
CHInstance.prototype.mercatorPointToHeatmapPoint = function(p) {
  var pn = {}

  pn.x = Math.round((p.x - this._xoffset) / this._factor + this._spacing)
  pn.y = Math.round((p.y - this._yoffset) / this._factor + this._spacing)
  pn.y = this.height - pn.y

  return pn
}

CHInstance.prototype._setWidthAndHeight = function(mbb) {
  this.width = ((mbb.east > 0 && mbb.west < 0) ? mbb.east + Math.abs(mbb.west) : Math.abs(mbb.east - mbb.west))
  this.height = ((mbb.north > 0 && mbb.south < 0) ? mbb.north + Math.abs(mbb.south) : Math.abs(mbb.north - mbb.south))
  this._factor = 1

  if (this.width > this.height && this.width > CesiumHeatmap.defaults.maxCanvasSize) {
    this._factor = this.width / CesiumHeatmap.defaults.maxCanvasSize

    if (this.height / this._factor < CesiumHeatmap.defaults.minCanvasSize) {
      this._factor = this.height / CesiumHeatmap.defaults.minCanvasSize
    }
  } else if (this.height > this.width && this.height > CesiumHeatmap.defaults.maxCanvasSize) {
    this._factor = this.height / CesiumHeatmap.defaults.maxCanvasSize

    if (this.width / this._factor < CesiumHeatmap.defaults.minCanvasSize) {
      this._factor = this.width / CesiumHeatmap.defaults.minCanvasSize
    }
  } else if (this.width < this.height && this.width < CesiumHeatmap.defaults.minCanvasSize) {
    this._factor = this.width / CesiumHeatmap.defaults.minCanvasSize

    if (this.height / this._factor > CesiumHeatmap.defaults.maxCanvasSize) {
      this._factor = this.height / CesiumHeatmap.defaults.maxCanvasSize
    }
  } else if (this.height < this.width && this.height < CesiumHeatmap.defaults.minCanvasSize) {
    this._factor = this.height / CesiumHeatmap.defaults.minCanvasSize

    if (this.width / this._factor > CesiumHeatmap.defaults.maxCanvasSize) {
      this._factor = this.width / CesiumHeatmap.defaults.maxCanvasSize
    }
  }

  this.width = this.width / this._factor
  this.height = this.height / this._factor
}

/*  Set an array of heatmap locations
 *
 *  min:  the minimum allowed value for the data values
 *  max:  the maximum allowed value for the data values
 *  data: an array of data points in heatmap coordinates and values like {x, y, value}
 */
CHInstance.prototype.setData = function(min, max, data) {
  if (data && data.length > 0 && min !== null && min !== false && max !== null && max !== false) {
    this._heatmap.setData({
      min: min,
      max: max,
      data: data
    })

    this.updateLayer()
    return true
  }

  return false
}

/*  Set an array of WGS84 locations
 *
 *  min:  the minimum allowed value for the data values
 *  max:  the maximum allowed value for the data values
 *  data: an array of data points in WGS84 coordinates and values like { x:lon, y:lat, value }
 */
CHInstance.prototype.setWGS84Data = function(min, max, data) {
  if (data && data.length > 0 && min !== null && min !== false && max !== null && max !== false) {
    var convdata = []

    for (var i = 0; i < data.length; i++) {
      var gp = data[i]

      var hp = this.wgs84PointToHeatmapPoint(gp)
      if (gp.value || gp.value === 0) {
        hp.value = gp.value
      }


      // 增加半径处理(动态热力图)
      if (gp.radius) {
        hp.radius = gp.radius
      }

      convdata.push(hp)
    }

    return this.setData(min, max, convdata)
  }

  return false
}

/*  Set whether or not the heatmap is shown on the map
 *
 *  s: true means the heatmap is shown, false means the heatmap is hidden
 */
CHInstance.prototype.show = function(s) {
  if (this._layer) {
    this._layer.show = s
  }
}

// 创建Layer层
CHInstance.prototype.createLayerEntity = function() {

  // Work around issue with material rendering in Cesium
  // provided by https://github.com/criis
  var material = new Cesium.ImageMaterialProperty({
    image: this._heatmap._renderer.canvas
  })
  if (Cesium.VERSION >= '1.21') {
    material.transparent = true
  } else if (Cesium.VERSION >= '1.16') {
    material.alpha = 0.99
  }

  this._layer = this._cesium.entities.add({
    show: true,
    rectangle: {
      coordinates: this._rectangle,
      material: material
    }
  })
}

/*  Update/(re)draw the heatmap
 */
CHInstance.prototype.updateLayer = function() {

  // only works with a Viewer instance since the cesiumWidget
  // instance doesn't contain an entities property
  if (CesiumHeatmap.defaults.useEntitiesIfAvailable && this._cesium.entities) {
    if (this._layer) {
      this._cesium.entities.remove(this._layer)
    }

    this.createLayerEntity()

  } else {
    if (this._layer) {
      this._cesium.scene.imageryLayers.remove(this._layer)
    }

    this._layer = this._cesium.scene.imageryLayers.addImageryProvider(CesiumHeatmap._getImageryProvider(this))
  }
};

/*
 * 新版 heatmap
 * heatmap.js v2.0.5 | JavaScript Heatmap Library
 *
 * Copyright 2008-2016 Patrick Wied  - All rights reserved.
 * Dual licensed under MIT and Beerware license
 *
 * :: 2016-09-05 01:16
 */
;(function (name, context, factory) {

  // Supports UMD. AMD, CommonJS/Node.js and browser context
  if (typeof module !== "undefined" && module.exports) {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    context[name] = factory();
  }

})("h337", this, function () {

  // Heatmap Config stores default values and will be merged with instance config
  var HeatmapConfig = {
    defaultRadius: 40,
    defaultRenderer: 'canvas2d',
    defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"},
    defaultMaxOpacity: 1,
    defaultMinOpacity: 0,
    defaultBlur: .85,
    defaultXField: 'x',
    defaultYField: 'y',
    defaultValueField: 'value',
    plugins: {}
  };
  var Store = (function StoreClosure() {

    var Store = function Store(config) {
      this._coordinator = {};
      this._data = [];
      this._radi = [];
      this._min = 10;
      this._max = 1;
      this._xField = config['xField'] || config.defaultXField;
      this._yField = config['yField'] || config.defaultYField;
      this._valueField = config['valueField'] || config.defaultValueField;

      if (config["radius"]) {
        this._cfgRadius = config["radius"];
      }
    };

    var defaultRadius = HeatmapConfig.defaultRadius;

    Store.prototype = {
      // when forceRender = false -> called from setData, omits renderall event
      _organiseData: function(dataPoint, forceRender) {
        var x = dataPoint[this._xField];
        var y = dataPoint[this._yField];
        var radi = this._radi;
        var store = this._data;
        var max = this._max;
        var min = this._min;
        var value = dataPoint[this._valueField] || 1;
        var radius = dataPoint.radius || this._cfgRadius || defaultRadius;

        if (!store[x]) {
          store[x] = [];
          radi[x] = [];
        }

        if (!store[x][y]) {
          store[x][y] = value;
          radi[x][y] = radius;
        } else {
          store[x][y] += value;
        }
        var storedVal = store[x][y];

        if (storedVal > max) {
          if (!forceRender) {
            this._max = storedVal;
          } else {
            this.setDataMax(storedVal);
          }
          return false;
        } else if (storedVal < min) {
          if (!forceRender) {
            this._min = storedVal;
          } else {
            this.setDataMin(storedVal);
          }
          return false;
        } else {
          return {
            x: x,
            y: y,
            value: value,
            radius: radius,
            min: min,
            max: max
          };
        }
      },
      _unOrganizeData: function() {
        var unorganizedData = [];
        var data = this._data;
        var radi = this._radi;

        for (var x in data) {
          for (var y in data[x]) {

            unorganizedData.push({
              x: x,
              y: y,
              radius: radi[x][y],
              value: data[x][y]
            });

          }
        }
        return {
          min: this._min,
          max: this._max,
          data: unorganizedData
        };
      },
      _onExtremaChange: function() {
        this._coordinator.emit('extremachange', {
          min: this._min,
          max: this._max
        });
      },
      addData: function() {
        if (arguments[0].length > 0) {
          var dataArr = arguments[0];
          var dataLen = dataArr.length;
          while (dataLen--) {
            this.addData.call(this, dataArr[dataLen]);
          }
        } else {
          // add to store
          var organisedEntry = this._organiseData(arguments[0], true);
          if (organisedEntry) {
            // if it's the first datapoint initialize the extremas with it
            if (this._data.length === 0) {
              this._min = this._max = organisedEntry.value;
            }
            this._coordinator.emit('renderpartial', {
              min: this._min,
              max: this._max,
              data: [organisedEntry]
            });
          }
        }
        return this;
      },
      setData: function(data) {
        var dataPoints = data.data;
        var pointsLen = dataPoints.length;


        // reset data arrays
        this._data = [];
        this._radi = [];

        for(var i = 0; i < pointsLen; i++) {
          this._organiseData(dataPoints[i], false);
        }
        this._max = data.max;
        this._min = data.min || 0;

        this._onExtremaChange();
        this._coordinator.emit('renderall', this._getInternalData());
        return this;
      },
      removeData: function() {
        // TODO: implement
      },
      setDataMax: function(max) {
        this._max = max;
        this._onExtremaChange();
        this._coordinator.emit('renderall', this._getInternalData());
        return this;
      },
      setDataMin: function(min) {
        this._min = min;
        this._onExtremaChange();
        this._coordinator.emit('renderall', this._getInternalData());
        return this;
      },
      setCoordinator: function(coordinator) {
        this._coordinator = coordinator;
      },
      _getInternalData: function() {
        return {
          max: this._max,
          min: this._min,
          data: this._data,
          radi: this._radi
        };
      },
      getData: function() {
        return this._unOrganizeData();
      }/*,

       TODO: rethink.

       getValueAt: function(point) {
       var value;
       var radius = 100;
       var x = point.x;
       var y = point.y;
       var data = this._data;

       if (data[x] && data[x][y]) {
       return data[x][y];
       } else {
       var values = [];
       // radial search for datapoints based on default radius
       for(var distance = 1; distance < radius; distance++) {
       var neighbors = distance * 2 +1;
       var startX = x - distance;
       var startY = y - distance;

       for(var i = 0; i < neighbors; i++) {
       for (var o = 0; o < neighbors; o++) {
       if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
       if (data[startY+i] && data[startY+i][startX+o]) {
       values.push(data[startY+i][startX+o]);
       }
       } else {
       continue;
       }
       }
       }
       }
       if (values.length > 0) {
       return Math.max.apply(Math, values);
       }
       }
       return false;
       }*/
    };


    return Store;
  })();

  var Canvas2dRenderer = (function Canvas2dRendererClosure() {

    var _getColorPalette = function(config) {
      var gradientConfig = config.gradient || config.defaultGradient;
      var paletteCanvas = document.createElement('canvas');
      var paletteCtx = paletteCanvas.getContext('2d');

      paletteCanvas.width = 256;
      paletteCanvas.height = 1;

      var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
      for (var key in gradientConfig) {
        gradient.addColorStop(key, gradientConfig[key]);
      }

      paletteCtx.fillStyle = gradient;
      paletteCtx.fillRect(0, 0, 256, 1);

      return paletteCtx.getImageData(0, 0, 256, 1).data;
    };

    var _getPointTemplate = function(radius, blurFactor) {
      var tplCanvas = document.createElement('canvas');
      var tplCtx = tplCanvas.getContext('2d');
      var x = radius;
      var y = radius;
      tplCanvas.width = tplCanvas.height = radius*2;

      if (blurFactor == 1) {
        tplCtx.beginPath();
        tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
        tplCtx.fillStyle = 'rgba(0,0,0,1)';
        tplCtx.fill();
      } else {
        var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius);
        gradient.addColorStop(0, 'rgba(0,0,0,1)');
        gradient.addColorStop(1, 'rgba(0,0,0,0)');
        tplCtx.fillStyle = gradient;
        tplCtx.fillRect(0, 0, 2*radius, 2*radius);
      }



      return tplCanvas;
    };

    var _prepareData = function(data) {
      var renderData = [];
      var min = data.min;
      var max = data.max;
      var radi = data.radi;
      var data = data.data;

      var xValues = Object.keys(data);
      var xValuesLen = xValues.length;

      while(xValuesLen--) {
        var xValue = xValues[xValuesLen];
        var yValues = Object.keys(data[xValue]);
        var yValuesLen = yValues.length;
        while(yValuesLen--) {
          var yValue = yValues[yValuesLen];
          var value = data[xValue][yValue];
          var radius = radi[xValue][yValue];
          renderData.push({
            x: xValue,
            y: yValue,
            value: value,
            radius: radius
          });
        }
      }

      return {
        min: min,
        max: max,
        data: renderData
      };
    };


    function Canvas2dRenderer(config) {
      var container = config.container;
      var shadowCanvas = this.shadowCanvas = document.createElement('canvas');
      var canvas = this.canvas = config.canvas || document.createElement('canvas');
      var renderBoundaries = this._renderBoundaries = [10000, 10000, 0, 0];

      var computed = getComputedStyle(config.container) || {};

      canvas.className = 'heatmap-canvas';

      this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,''));
      this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,''));

      this.shadowCtx = shadowCanvas.getContext('2d');
      this.ctx = canvas.getContext('2d');

      // @TODO:
      // conditional wrapper

      canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;';

      container.style.position = 'relative';
      container.appendChild(canvas);

      this._palette = _getColorPalette(config);
      this._templates = {};

      this._setStyles(config);
    };

    Canvas2dRenderer.prototype = {
      renderPartial: function(data) {
        if (data.data.length > 0) {
          this._drawAlpha(data);
          this._colorize();
        }
      },
      renderAll: function(data) {
        // reset render boundaries
        this._clear();
        if (data.data.length > 0) {
          this._drawAlpha(_prepareData(data));
          this._colorize();
        }
      },
      _updateGradient: function(config) {
        this._palette = _getColorPalette(config);
      },
      updateConfig: function(config) {
        if (config['gradient']) {
          this._updateGradient(config);
        }
        this._setStyles(config);
      },
      setDimensions: function(width, height) {
        this._width = width;
        this._height = height;
        this.canvas.width = this.shadowCanvas.width = width;
        this.canvas.height = this.shadowCanvas.height = height;
      },
      _clear: function() {
        this.shadowCtx.clearRect(0, 0, this._width, this._height);
        this.ctx.clearRect(0, 0, this._width, this._height);
      },
      _setStyles: function(config) {
        this._blur = (config.blur == 0)?0:(config.blur || config.defaultBlur);

        if (config.backgroundColor) {
          this.canvas.style.backgroundColor = config.backgroundColor;
        }

        this._width = this.canvas.width = this.shadowCanvas.width = config.width || this._width;
        this._height = this.canvas.height = this.shadowCanvas.height = config.height || this._height;


        this._opacity = (config.opacity || 0) * 255;
        this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255;
        this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255;
        this._useGradientOpacity = !!config.useGradientOpacity;
      },
      _drawAlpha: function(data) {
        var min = this._min = data.min;
        var max = this._max = data.max;
        var data = data.data || [];
        var dataLen = data.length;
        // on a point basis?
        var blur = 1 - this._blur;

        while(dataLen--) {

          var point = data[dataLen];

          var x = point.x;
          var y = point.y;
          var radius = point.radius;
          // if value is bigger than max
          // use max as value
          var value = Math.min(point.value, max);
          var rectX = x - radius;
          var rectY = y - radius;
          var shadowCtx = this.shadowCtx;




          var tpl;
          if (!this._templates[radius]) {
            this._templates[radius] = tpl = _getPointTemplate(radius, blur);
          } else {
            tpl = this._templates[radius];
          }
          // value from minimum / value range
          // => [0, 1]
          var templateAlpha = (value-min)/(max-min);
          // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData
          shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha;

          shadowCtx.drawImage(tpl, rectX, rectY);

          // update renderBoundaries
          if (rectX < this._renderBoundaries[0]) {
            this._renderBoundaries[0] = rectX;
          }
          if (rectY < this._renderBoundaries[1]) {
            this._renderBoundaries[1] = rectY;
          }
          if (rectX + 2*radius > this._renderBoundaries[2]) {
            this._renderBoundaries[2] = rectX + 2*radius;
          }
          if (rectY + 2*radius > this._renderBoundaries[3]) {
            this._renderBoundaries[3] = rectY + 2*radius;
          }

        }
      },
      _colorize: function() {
        var x = this._renderBoundaries[0];
        var y = this._renderBoundaries[1];
        var width = this._renderBoundaries[2] - x;
        var height = this._renderBoundaries[3] - y;
        var maxWidth = this._width;
        var maxHeight = this._height;
        var opacity = this._opacity;
        var maxOpacity = this._maxOpacity;
        var minOpacity = this._minOpacity;
        var useGradientOpacity = this._useGradientOpacity;

        if (x < 0) {
          x = 0;
        }
        if (y < 0) {
          y = 0;
        }
        if (x + width > maxWidth) {
          width = maxWidth - x;
        }
        if (y + height > maxHeight) {
          height = maxHeight - y;
        }

        var img = this.shadowCtx.getImageData(x, y, width, height);
        var imgData = img.data;
        var len = imgData.length;
        var palette = this._palette;


        for (var i = 3; i < len; i+= 4) {
          var alpha = imgData[i];
          var offset = alpha * 4;


          if (!offset) {
            continue;
          }

          var finalAlpha;
          if (opacity > 0) {
            finalAlpha = opacity;
          } else {
            if (alpha < maxOpacity) {
              if (alpha < minOpacity) {
                finalAlpha = minOpacity;
              } else {
                finalAlpha = alpha;
              }
            } else {
              finalAlpha = maxOpacity;
            }
          }

          imgData[i-3] = palette[offset];
          imgData[i-2] = palette[offset + 1];
          imgData[i-1] = palette[offset + 2];
          imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;

        }

        img.data = imgData;
        this.ctx.putImageData(img, x, y);

        this._renderBoundaries = [1000, 1000, 0, 0];

      },
      getValueAt: function(point) {
        var value;
        var shadowCtx = this.shadowCtx;
        var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
        var data = img.data[3];
        var max = this._max;
        var min = this._min;

        value = (Math.abs(max-min) * (data/255)) >> 0;

        return value;
      },
      getDataURL: function() {
        return this.canvas.toDataURL();
      }
    };


    return Canvas2dRenderer;
  })();


  var Renderer = (function RendererClosure() {

    var rendererFn = false;

    if (HeatmapConfig['defaultRenderer'] === 'canvas2d') {
      rendererFn = Canvas2dRenderer;
    }

    return rendererFn;
  })();


  var Util = {
    merge: function() {
      var merged = {};
      var argsLen = arguments.length;
      for (var i = 0; i < argsLen; i++) {
        var obj = arguments[i]
        for (var key in obj) {
          merged[key] = obj[key];
        }
      }
      return merged;
    }
  };
  // Heatmap Constructor
  var Heatmap = (function HeatmapClosure() {

    var Coordinator = (function CoordinatorClosure() {

      function Coordinator() {
        this.cStore = {};
      };

      Coordinator.prototype = {
        on: function(evtName, callback, scope) {
          var cStore = this.cStore;

          if (!cStore[evtName]) {
            cStore[evtName] = [];
          }
          cStore[evtName].push((function(data) {
            return callback.call(scope, data);
          }));
        },
        emit: function(evtName, data) {
          var cStore = this.cStore;
          if (cStore[evtName]) {
            var len = cStore[evtName].length;
            for (var i=0; i