718 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			718 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
 * heatmap.js v2.0.5 | JavaScript Heatmap Library
 | 
						|
 *
 | 
						|
 * Copyright 2008-2016 Patrick Wied <heatmapjs@patrick-wied.at> - 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()
 | 
						|
    // eslint-disable-next-line no-undef
 | 
						|
  } else if (typeof define === 'function' && define.amd) {
 | 
						|
    // eslint-disable-next-line no-undef
 | 
						|
    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: 0.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 < 0.01 ? 0.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 < len; i++) {
 | 
						|
              var callback = cStore[evtName][i]
 | 
						|
              callback(data)
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      return Coordinator
 | 
						|
    })()
 | 
						|
 | 
						|
    var _connect = function (scope) {
 | 
						|
      var renderer = scope._renderer
 | 
						|
      var coordinator = scope._coordinator
 | 
						|
      var store = scope._store
 | 
						|
 | 
						|
      coordinator.on('renderpartial', renderer.renderPartial, renderer)
 | 
						|
      coordinator.on('renderall', renderer.renderAll, renderer)
 | 
						|
      coordinator.on('extremachange', function (data) {
 | 
						|
        scope._config.onExtremaChange &&
 | 
						|
        scope._config.onExtremaChange({
 | 
						|
          min: data.min,
 | 
						|
          max: data.max,
 | 
						|
          gradient:
 | 
						|
            scope._config['gradient'] || scope._config['defaultGradient']
 | 
						|
        })
 | 
						|
      })
 | 
						|
      store.setCoordinator(coordinator)
 | 
						|
    }
 | 
						|
 | 
						|
    function Heatmap() {
 | 
						|
      var config = (this._config = Util.merge(
 | 
						|
        HeatmapConfig,
 | 
						|
        arguments[0] || {}
 | 
						|
      ))
 | 
						|
      this._coordinator = new Coordinator()
 | 
						|
      if (config['plugin']) {
 | 
						|
        var pluginToLoad = config['plugin']
 | 
						|
        if (!HeatmapConfig.plugins[pluginToLoad]) {
 | 
						|
          throw new Error(
 | 
						|
            "Plugin '" +
 | 
						|
            pluginToLoad +
 | 
						|
            "' not found. Maybe it was not registered."
 | 
						|
          )
 | 
						|
        } else {
 | 
						|
          var plugin = HeatmapConfig.plugins[pluginToLoad]
 | 
						|
          // set plugin renderer and store
 | 
						|
          this._renderer = new plugin.renderer(config)
 | 
						|
          this._store = new plugin.store(config)
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        this._renderer = new Renderer(config)
 | 
						|
        this._store = new Store(config)
 | 
						|
      }
 | 
						|
      _connect(this)
 | 
						|
    }
 | 
						|
 | 
						|
    // @TODO:
 | 
						|
    // add API documentation
 | 
						|
    Heatmap.prototype = {
 | 
						|
      addData: function () {
 | 
						|
        this._store.addData.apply(this._store, arguments)
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      removeData: function () {
 | 
						|
        this._store.removeData &&
 | 
						|
        this._store.removeData.apply(this._store, arguments)
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      setData: function () {
 | 
						|
        this._store.setData.apply(this._store, arguments)
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      setDataMax: function () {
 | 
						|
        this._store.setDataMax.apply(this._store, arguments)
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      setDataMin: function () {
 | 
						|
        this._store.setDataMin.apply(this._store, arguments)
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      configure: function (config) {
 | 
						|
        this._config = Util.merge(this._config, config)
 | 
						|
        this._renderer.updateConfig(this._config)
 | 
						|
        this._coordinator.emit('renderall', this._store._getInternalData())
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      repaint: function () {
 | 
						|
        this._coordinator.emit('renderall', this._store._getInternalData())
 | 
						|
        return this
 | 
						|
      },
 | 
						|
      getData: function () {
 | 
						|
        return this._store.getData()
 | 
						|
      },
 | 
						|
      getDataURL: function () {
 | 
						|
        return this._renderer.getDataURL()
 | 
						|
      },
 | 
						|
      getValueAt: function (point) {
 | 
						|
        if (this._store.getValueAt) {
 | 
						|
          return this._store.getValueAt(point)
 | 
						|
        } else if (this._renderer.getValueAt) {
 | 
						|
          return this._renderer.getValueAt(point)
 | 
						|
        } else {
 | 
						|
          return null
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return Heatmap
 | 
						|
  })()
 | 
						|
 | 
						|
  // core
 | 
						|
  var heatmapFactory = {
 | 
						|
    create: function (config) {
 | 
						|
      return new Heatmap(config)
 | 
						|
    },
 | 
						|
    register: function (pluginKey, plugin) {
 | 
						|
      HeatmapConfig.plugins[pluginKey] = plugin
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return heatmapFactory
 | 
						|
})
 |