723 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			723 lines
		
	
	
		
			19 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();
 | 
						|
  } 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', { willReadFrequently: true });
 | 
						|
 | 
						|
    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', { willReadFrequently: true });
 | 
						|
    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', { willReadFrequently: true });
 | 
						|
    this.ctx = canvas.getContext('2d', { willReadFrequently: true });
 | 
						|
 | 
						|
    // @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;
 | 
						|
 | 
						|
      }
 | 
						|
 | 
						|
      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;
 | 
						|
 | 
						|
 | 
						|
}); |