diff --git a/src/canvas/heatmapFeature.js b/src/canvas/heatmapFeature.js index a9a301a021..a887b606ba 100644 --- a/src/canvas/heatmapFeature.js +++ b/src/canvas/heatmapFeature.js @@ -5,7 +5,7 @@ var heatmapFeature = require('../heatmapFeature'); ////////////////////////////////////////////////////////////////////////////// /** * Create a new instance of class heatmapFeature - * The rendering borrows from + * Inspired from * https://github.com/mourner/simpleheat/blob/gh-pages/simpleheat.js * * @class @@ -14,7 +14,7 @@ var heatmapFeature = require('../heatmapFeature'); * @returns {canvas_heatmapFeature} */ ////////////////////////////////////////////////////////////////////////////// -canvas_heatmapFeature = function (arg) { +var canvas_heatmapFeature = function (arg) { 'use strict'; if (!(this instanceof canvas_heatmapFeature)) { @@ -32,17 +32,6 @@ canvas_heatmapFeature = function (arg) { s_init = this._init, s_update = this._update; - //////////////////////////////////////////////////////////////////////////// - /** - * Build - * @override - */ - //////////////////////////////////////////////////////////////////////////// - this._build = function () { - s_update.call(m_this); - return m_this; - }; - //////////////////////////////////////////////////////////////////////////// /** * Meta functions for converting from geojs styles to canvas. @@ -52,10 +41,11 @@ canvas_heatmapFeature = function (arg) { this._convertColor = function (c) { var color; if (c.hasOwnProperty('r') && - c.hasOwnProperty('g') && - c.hasOwnProperty('b') && - c.hasOwnProperty('a')) { - color = 'rgba('+255 * c.r+','+255 * c.g+','+255 * c.b+','+ c.a+')'; + c.hasOwnProperty('g') && + c.hasOwnProperty('b') && + c.hasOwnProperty('a')) { + color = 'rgba(' + 255 * c.r + ',' + 255 * c.g + ',' + + 255 * c.b + ',' + c.a + ')'; } return color; }; @@ -70,10 +60,10 @@ canvas_heatmapFeature = function (arg) { var canvas, stop, context2d, gradient, colors; if (!m_this._grad) { - canvas = document.createElement('canvas'), - context2d = canvas.getContext('2d'), - gradient = context2d.createLinearGradient(0, 0, 0, 256), - colors = m_this.style('color'); + canvas = document.createElement('canvas'); + context2d = canvas.getContext('2d'); + gradient = context2d.createLinearGradient(0, 0, 0, 256); + colors = m_this.style('color'); canvas.width = 1; canvas.height = 256; @@ -97,11 +87,11 @@ canvas_heatmapFeature = function (arg) { */ //////////////////////////////////////////////////////////////////////////// this._createCircle = function () { - var circle, ctx, r, r2; + var circle, ctx, r, r2, blur; if (!m_this._circle) { - circle = m_this._circle = document.createElement('canvas'), - ctx = circle.getContext('2d'), - r = m_this.style('radius'), + circle = m_this._circle = document.createElement('canvas'); + ctx = circle.getContext('2d'); + r = m_this.style('radius'); blur = m_this.style('blurRadius'); r2 = blur + r; @@ -128,14 +118,15 @@ canvas_heatmapFeature = function (arg) { //////////////////////////////////////////////////////////////////////////// this._colorize = function (pixels, gradient) { var i, j; - for (i = 0; i < pixels.length; i+=4) { - j = pixels[i + 3] * 4; // get opacity from the temporary canvas image, - // then multiply by 4 to get the color index on linear gradient + for (i = 0; i < pixels.length; i += 4) { + // Get opacity from the temporary canvas image, + // then multiply by 4 to get the color index on linear gradient + j = pixels[i + 3] * 4; if (j) { - pixels[i] = gradient[j]; - pixels[i+1] = gradient[j+1]; - pixels[i+2] = gradient[j+2]; - pixels[i+3] = m_this.style('opacity') * gradient[j+3]; + pixels[i] = gradient[j]; + pixels[i + 1] = gradient[j + 1]; + pixels[i + 2] = gradient[j + 2]; + pixels[i + 3] = m_this.style('opacity') * gradient[j + 3]; } } }; @@ -164,7 +155,7 @@ canvas_heatmapFeature = function (arg) { canvas = m_this.layer().canvas()[0]; pixelArray = context2d.getImageData(0, 0, canvas.width, canvas.height); m_this._colorize(pixelArray.data, m_this._grad); - context2d.putImageData(pixelArray, 0, 0) + context2d.putImageData(pixelArray, 0, 0); return m_this; }; @@ -213,4 +204,4 @@ inherit(canvas_heatmapFeature, heatmapFeature); // Now register it registerFeature('canvas', 'heatmap', canvas_heatmapFeature); -module.exports = canvas_heatmapFeature; \ No newline at end of file +module.exports = canvas_heatmapFeature; diff --git a/src/heatmapFeature.js b/src/heatmapFeature.js index 5934c6f645..79f7475865 100644 --- a/src/heatmapFeature.js +++ b/src/heatmapFeature.js @@ -11,19 +11,26 @@ var feature = require('./feature'); * @extends geo.feature * @param {Object|string|Function} [color] Color transfer function that. * will be used to evaluate color of each pixel using normalized intensity -* as the look up value. - * @param {number|Function} [opacity=1] Opacity for each pixel. + * as the look up value. + * @param {number|Function} [opacity=1] Homogeneous opacity for each pixel. * @param {Object|Function} [radius=10] Radius of a point in terms of number -* of pixels. - * @param {Object|Function} [blurRadius=10] Gaussian blur radius. + * of pixels. + * @param {Object|Function} [blurRadius=10] Gaussian blur radius for each + * point in terms of number of pixels. * @param {Object|Function} [position] Position of the data. Default is - * (data). The position is an Object which specifies the location of the + * (data). The position is an Object which specifies the location of the * data in geo-spatial context. - * @param {boolean} [intensity] Scalar value that of each data point. Scalar - * value will be used to compute the weight for each data point for the final - * computation of its opacity. - * @param {boolean} [maxIntensity=1] Maximum intensity of the data. Maximum - * intensity will be used to normalize all intensities with a dataset. + * @param {boolean} [intensity] Scalar value of each data point. Scalar + * value must be a positive real number and will be used to compute + * the weight for each data point. + * @param {boolean} [maxIntensity=null] Maximum intensity of the data. Maximum + * intensity must be a positive real number and will be used to normalize all + * intensities with a dataset. If no value is given, then a it will + * be computed. + * @param {boolean} [minIntensity=null] Minimum intensity of the data. Minimum + * intensity must be a positive real number will be used to normalize all + * intensities with a dataset. If no value is given, then a it will + * be computed. * @returns {geo.heatmapFetures} */ ////////////////////////////////////////////////////////////////////////////// @@ -31,7 +38,7 @@ var feature = require('./feature'); ////////////////////////////////////////////////////////////////////////////// var heatmapFeature = function (arg) { 'use strict'; - if (!(this instanceof heatmapFeature)){ + if (!(this instanceof heatmapFeature)) { return new heatmapFeature(arg); } arg = arg || {}; @@ -47,13 +54,12 @@ var heatmapFeature = function (arg) { m_intensity, m_maxIntensity, m_minIntensity, - s_init = this._init, - s_data = this.data; + s_init = this._init; m_position = arg.position || function (d) { return d; }; m_intensity = arg.intensity || function (d) { return 1; }; - m_maxIntensity = arg.maxIntensity || 1; - m_minIntensity = arg.minIntensity ? arg.minIntensity : 0; + m_maxIntensity = arg.maxIntensity || null; + m_minIntensity = arg.minIntensity ? arg.minIntensity : null; //////////////////////////////////////////////////////////////////////////// /** @@ -137,16 +143,16 @@ var heatmapFeature = function (arg) { var defaultStyle = $.extend( {}, - { - opacity: 0.1, - radius: 10, - blurRadius: 10, - color: {0: {r: 0, g: 0, b: 0.0, a: 0.0}, - .25: {r: 0, g: 0, b: 1, a: 0.5}, - .5: {r: 0, g: 1, b: 1, a: 0.6}, - .75: {r: 1, g: 1, b: 0, a: 0.7}, - 1: {r: 1, g: 0, b: 0, a: 0.8}} - }, + { + opacity: 0.1, + radius: 10, + blurRadius: 10, + color: {0: {r: 0, g: 0, b: 0.0, a: 0.0}, + 0.25: {r: 0, g: 0, b: 1, a: 0.5}, + 0.5: {r: 0, g: 1, b: 1, a: 0.6}, + 0.75: {r: 1, g: 1, b: 0, a: 0.7}, + 1: {r: 1, g: 0, b: 0, a: 0.8}} + }, arg.style === undefined ? {} : arg.style ); @@ -157,10 +163,38 @@ var heatmapFeature = function (arg) { } }; + //////////////////////////////////////////////////////////////////////////// + /** + * Build + * @override + */ + //////////////////////////////////////////////////////////////////////////// + this._build = function () { + var data = m_this.data(), + intensity = null; + + if (!m_maxIntensity || !m_minIntensity) { + data.forEach(function (d) { + intensity = m_this.intensity()(d); + if (!m_maxIntensity && !m_minIntensity) { + m_maxIntensity = m_minIntensity = intensity; + } else { + if (intensity > m_maxIntensity) { + m_maxIntensity = intensity; + } + if (intensity < m_minIntensity) { + m_minIntensity = intensity; + } + } + }); + } + + return m_this; + }; + this._init(arg); return this; - }; -inherit(heatmapFeature, feature) +inherit(heatmapFeature, feature); module.exports = heatmapFeature; diff --git a/tests/cases/heatmap.js b/tests/cases/heatmap.js index 458fd3beaf..87cbf99ef1 100644 --- a/tests/cases/heatmap.js +++ b/tests/cases/heatmap.js @@ -18,17 +18,10 @@ describe('canvas heatmap feature', function () { var stepAnimationFrame = require('../test-utils').stepAnimationFrame; var unmockAnimationFrame = require('../test-utils').unmockAnimationFrame; - var map, width = 800, height = 600, layer, feature1, feature2, - testData = "Strength, Lat,Lon\ - 0.6,42.8584,-70.9301\ - 0.233,42.2776,-83.7409\ - 0.2,42.2776,-83.7409,"; - testData = testData.split(/\r\n|\n|\r/); - testData = testData.map( function (r) { - var fields = r.split(','); - return [fields[12], fields[24], fields[25]].map(parseFloat); - }); - testData.splice(0, 1); + var map, width = 800, height = 600, layer, feature1, + testData = [[0.6, 42.8584, -70.9301], + [0.233, 42.2776, -83.7409], + [0.2, 42.2776, -83.7409]]; it('Setup map', function () { map = geo.map({node: '#map-canvas-heatmap-feature', center: [0, 0], zoom: 3}); @@ -38,24 +31,24 @@ describe('canvas heatmap feature', function () { it('Add features to a layer', function () { feature1 = layer.createFeature('heatmap') - .data(testData) - .intensity(function (d) { - return d[0]; - }) - .position(function (d) { - return { - x: d[2], - y: d[1] - }; - }) - .style('radius', 5) - .style('blurRadius', 15) - .style('opacity', 1.0); + .data(testData) + .intensity(function (d) { + return d[0]; + }) + .position(function (d) { + return { + x: d[2], + y: d[1] + }; + }) + .style('radius', 5) + .style('blurRadius', 15) + .style('opacity', 1.0); mockAnimationFrame(); map.draw(); stepAnimationFrame(new Date().getTime()); - expect(layer.children().length).toBe(1) + expect(layer.children().length).toBe(1); unmockAnimationFrame(); }); @@ -69,11 +62,11 @@ describe('canvas heatmap feature', function () { }); it('Validate maximum intensity', function () { - expect(feature1.maxIntensity()).toBe(1); + expect(feature1.maxIntensity()).toBe(0.6); }); it('Validate minimum intensity', function () { - expect(feature1.minIntensity()).toBe(0); + expect(feature1.minIntensity()).toBe(0.2); }); it('Remove a feature from a layer', function () { @@ -82,13 +75,13 @@ describe('canvas heatmap feature', function () { }); it('Compute gradient', function () { - feature1.style("color", {0: {r: 0, g: 0, b: 0.0, a: 0.0}, + feature1.style('color', {0: {r: 0, g: 0, b: 0.0, a: 0.0}, 0.25: {r: 0, g: 0, b: 1, a: 0.5}, 0.5: {r: 0, g: 1, b: 1, a: 0.6}, 0.75: {r: 1, g: 1, b: 0, a: 0.7}, 1: {r: 1, g: 0, b: 0, a: 0.1}}); feature1._computeGradient(); - expect(layer.node()[0].children[0].getContext('2d'). - getImageData(1, 0, 1, 1).data.length).toBe(4) + expect(layer.node()[0].children[0].getContext('2d') + .getImageData(1, 0, 1, 1).data.length).toBe(4); }); });