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 | ||
|  | }) |