From 28aeffbc5816085f3ec17495aba5d5ca0d1ba856 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Mon, 9 Jul 2018 13:17:46 -0400 Subject: [PATCH] Improve heatmap documentation. This also removes a utility function that is also handled in a proper utils method. --- src/canvas/heatmapFeature.js | 101 +++++++++++-------------- src/heatmapFeature.js | 143 +++++++++++++++++++++-------------- 2 files changed, 128 insertions(+), 116 deletions(-) diff --git a/src/canvas/heatmapFeature.js b/src/canvas/heatmapFeature.js index cbe022b663..1e551278de 100644 --- a/src/canvas/heatmapFeature.js +++ b/src/canvas/heatmapFeature.js @@ -2,15 +2,16 @@ var inherit = require('../inherit'); var registerFeature = require('../registry').registerFeature; var heatmapFeature = require('../heatmapFeature'); var timestamp = require('../timestamp'); +var util = require('../util'); /** - * Create a new instance of class heatmapFeature + * Create a new instance of class canvas.heatmapFeature. * Inspired from - * https://github.com/mourner/simpleheat/blob/gh-pages/simpleheat.js + * https://github.com/mourner/simpleheat/blob/gh-pages/simpleheat.js . * * @class * @alias geo.canvas.heatmapFeature - * @param {Object} arg Options object + * @param {geo.heatmapFeature.spec} arg * @extends geo.heatmapFeature * @returns {canvas_heatmapFeature} */ @@ -36,30 +37,14 @@ var canvas_heatmapFeature = function (arg) { m_typedBufferData, m_heatMapPosition, m_heatMapTransform, - s_exit = this._exit, s_init = this._init, s_update = this._update, m_renderTime = timestamp(); /** - * Meta functions for converting from geojs styles to canvas. - * @private - */ - 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 + ')'; - } - return color; - }; - - /** - * Compute gradient (color lookup table) - * @protected + * Compute gradient. This creates a color lookup table. + * + * @returns {this} */ this._computeGradient = function () { var canvas, stop, context2d, gradient, colors; @@ -74,7 +59,7 @@ var canvas_heatmapFeature = function (arg) { canvas.height = 256; for (stop in colors) { - gradient.addColorStop(stop, m_this._convertColor(colors[stop])); + gradient.addColorStop(stop, util.convertColorToRGBA(colors[stop])); } context2d.fillStyle = gradient; @@ -87,8 +72,9 @@ var canvas_heatmapFeature = function (arg) { }; /** - * Create circle for each data point - * @protected + * Create circle for each data point. + * + * @returns {this} */ this._createCircle = function () { var circle, ctx, r, r2, blur, gaussian; @@ -149,7 +135,11 @@ var canvas_heatmapFeature = function (arg) { }; /** - * Compute color for each pixel on the screen + * Compute color for each pixel on the screen. + * + * @param {Uint8ClampedArray} pixels A 2D canvas `getImageData` buffer. + * @param {Uint8ClampedArray} gradient A 2D canvas with 256 pixels that + * contain a color gradient. * @protected */ this._colorize = function (pixels, gradient) { @@ -174,11 +164,11 @@ var canvas_heatmapFeature = function (arg) { /** * Render individual data points on the canvas. - * @protected - * @param {object} context2d the canvas context to draw in. - * @param {object} map the parent map object. - * @param {Array} data the main data array. - * @param {number} radius the sum of radius and blurRadius. + * + * @param {RenderingContext} context2d The canvas context to draw in. + * @param {geo.map} map The parent map object. + * @param {array} data The main data array. + * @param {number} radius The sum of `radius` and `blurRadius`. */ this._renderPoints = function (context2d, map, data, radius) { var position = m_this.gcsPosition(), @@ -189,7 +179,7 @@ var canvas_heatmapFeature = function (arg) { for (idx = data.length - 1; idx >= 0; idx -= 1) { pos = map.worldToDisplay(position[idx]); - intensity = (intensityFunc(data[idx]) - minIntensity) / rangeIntensity; + intensity = (intensityFunc(data[idx], idx) - minIntensity) / rangeIntensity; if (intensity <= 0) { continue; } @@ -202,12 +192,12 @@ var canvas_heatmapFeature = function (arg) { /** * Render data points on the canvas by binning. - * @protected - * @param {object} context2d the canvas context to draw in. - * @param {object} map the parent map object. - * @param {Array} data the main data array. - * @param {number} radius the sum of radius and blurRadius. - * @param {number} binSize size of the bins in pixels. + * + * @param {RenderingContext} context2d The canvas context to draw in. + * @param {geo.map} map The parent map object. + * @param {array} data The main data array. + * @param {number} radius The sum of `radius` and `blurRadius`. + * @param {number} binSize Size of the bins in pixels. */ this._renderBinnedData = function (context2d, map, data, radius, binSize) { var position = m_this.gcsPosition(), @@ -249,7 +239,7 @@ var canvas_heatmapFeature = function (arg) { if (y < 0 || y >= maxy) { continue; } - intensity = (intensityFunc(data[idx]) - minIntensity) / rangeIntensity; + intensity = (intensityFunc(data[idx], idx) - minIntensity) / rangeIntensity; if (intensity <= 0) { continue; } @@ -300,9 +290,10 @@ var canvas_heatmapFeature = function (arg) { /** * Render the data on the canvas, then colorize the resulting opacity map. - * @protected - * @param {object} context2d the canvas context to draw in. - * @param {object} map the parent map object. + * + * @param {RenderingContext} context2d The canvas context to draw in. + * @param {geo.map} map The parent map object. + * @returns {this} */ this._renderOnCanvas = function (context2d, map) { @@ -365,8 +356,9 @@ var canvas_heatmapFeature = function (arg) { }; /** - * Initialize - * @protected + * Initialize. + * + * @returns {this} */ this._init = function () { s_init.call(m_this, arg); @@ -377,8 +369,9 @@ var canvas_heatmapFeature = function (arg) { }; /** - * Update - * @protected + * Update the feature. + * + * @returns {this} */ this._update = function () { s_update.call(m_this); @@ -392,7 +385,8 @@ var canvas_heatmapFeature = function (arg) { /** * Update the css transform for the layer as part of an animation frame. - * @protected + * This allows an existing rendered version of the heatmap to appear with a + * transform until a new version can be computed. */ this._setTransform = function () { if (m_this.layer() && m_this.layer().canvas() && m_this.layer().canvas()[0]) { @@ -401,10 +395,9 @@ var canvas_heatmapFeature = function (arg) { }; /** - * Animate pan (and zoom) - * @protected + * Animate pan and zoom. */ - this._animatePan = function (e) { + this._animatePan = function () { if (!m_heatMapPosition) { return; @@ -455,14 +448,6 @@ var canvas_heatmapFeature = function (arg) { } }; - /** - * Destroy - * @protected - */ - this._exit = function () { - s_exit.call(m_this); - }; - m_this._init(arg); return this; }; diff --git a/src/heatmapFeature.js b/src/heatmapFeature.js index d966308a5c..d82d080cfb 100644 --- a/src/heatmapFeature.js +++ b/src/heatmapFeature.js @@ -4,47 +4,48 @@ var feature = require('./feature'); var transform = require('./transform'); /** - * Create a new instance of class heatmapFeature + * Heatmap feature specification. * - * @class - * @alias geo.heatmapFeature - * @param {Object} arg Options object - * @extends geo.feature - * @param {Object|Function} [position] Position of the data. Default is - * (data). - * @param {Object|Function} [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 {number} [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 {number} [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. + * @typedef {geo.feature.spec} geo.heatmapFeature.spec + * @property {geo.geoPosition|function} [position] Position of the data. + * Default is (data). + * @param {function} [intensity] Scalar value of each data point. The scalar + * value must be a positive real number and is used to compute the weight + * for each data point. + * @param {number} [maxIntensity=null] Maximum intensity of the data. Maximum + * intensity must be a positive real number and is used to normalize all + * intensities within a dataset. If `null`, it is computed. + * @param {number} [minIntensity=null] Minimum intensity of the data. Minimum + * intensity must be a positive real number and is used to normalize all + * intensities within a dataset. If `null`, it is computed. * @param {number} [updateDelay=1000] Delay in milliseconds after a zoom, * rotate, or pan event before recomputing the heatmap. - * @param {boolean|number|'auto'} [binned='auto'] If true or a number, - * spatially bin data as part of producing the heatpmap. If false, each - * datapoint stands on its own. If 'auto', bin data if there are more data - * points than there would be bins. Using true or auto uses bins that are - * max(Math.floor((radius + blurRadius) / 8), 3). - * @param {Object|string|Function} [style.color] Color transfer function that. - * will be used to evaluate color of each pixel using normalized intensity - * as the look up value. - * @param {Object|Function} [style.radius=10] Radius of a point in terms of - * number of pixels. - * @param {Object|Function} [style.blurRadius=10] Blur radius for each point in - * terms of number of pixels. - * @param {boolean} [style.gaussian=true] If true, appoximate a gaussian + * @param {boolean|number|'auto'} [binned='auto'] If `true` or a number, + * spatially bin data as part of producing the heatmap. If falsy, each + * datapoint stands on its own. If `'auto'`, bin data if there are more data + * points than there would be bins. Using `true` or `auto` uses bins that + * are `max(Math.floor((radius + blurRadius) / 8), 3)`. + * @param {object} [style.color] An object where the keys are numbers from + * [0-1] and the values are {@link geo.geoColor}. This is used to transform + * normalized intensity. + * @param {number} [style.radius=10] Radius of a point in pixels. + * @param {number} [style.blurRadius=10] Blur radius for each point in pixels. + * @param {boolean} [style.gaussian=true] If truthy, appoximate a gaussian * distribution for each point using a multi-segment linear radial * appoximation. The total weight of the gaussian area is approximately the - * 9/16 r^2. The sum of radius + blurRadius is used as the radius for the - * gaussian distribution. - * @returns {geo.heatmapFeature} + * `9/16 r^2`. The sum of `radius + blurRadius` is used as the radius for + * the gaussian distribution. */ +/** + * Create a new instance of class heatmapFeature. + * + * @class + * @alias geo.heatmapFeature + * @param {geo.heatmapFeature.spec} arg + * @extends geo.feature + * @returns {geo.heatmapFeature} + */ var heatmapFeature = function (arg) { 'use strict'; if (!(this instanceof heatmapFeature)) { @@ -74,9 +75,12 @@ var heatmapFeature = function (arg) { m_updateDelay = arg.updateDelay ? parseInt(arg.updateDelay, 10) : 1000; /** - * Get/Set maxIntensity + * Get/Set maxIntensity. * - * @returns {geo.heatmap} + * @param {number|null} [val] If not specified, return the current value. + * If a number, use this as the maximum intensity. If `null`, compute + * the maximum intensity. + * @returns {number|null|this} */ this.maxIntensity = function (val) { if (val === undefined) { @@ -90,9 +94,12 @@ var heatmapFeature = function (arg) { }; /** - * Get/Set maxIntensity + * Get/Set minIntensity. * - * @returns {geo.heatmap} + * @param {number|null} [val] If not specified, return the current value. + * If a number, use this as the minimum intensity. If `null`, compute + * the minimum intensity. + * @returns {number|null|this} */ this.minIntensity = function (val) { if (val === undefined) { @@ -106,9 +113,12 @@ var heatmapFeature = function (arg) { }; /** - * Get/Set updateDelay + * Get/Set updateDelay. * - * @returns {geo.heatmap} + * @param {number} [val] If not specified, return the current update delay. + * If specified, this is the delay in milliseconds after a zoom, rotate, + * or pan event before recomputing the heatmap. + * @returns {number|this} */ this.updateDelay = function (val) { if (val === undefined) { @@ -120,9 +130,15 @@ var heatmapFeature = function (arg) { }; /** - * Get/Set binned + * Get/Set binned value. * - * @returns {geo.heatmap} + * @param {boolean|number|'auto'} [val] If not specified, return the current + * binned value. If `true` or a number, spatially bin data as part of + * producing the heatmap. If falsy, each datapoint stands on its own. + * If `'auto'`, bin data if there are more data points than there would be + * bins. Using `true` or `auto` uses bins that are + * `max(Math.floor((radius + blurRadius) / 8), 3)`. + * @returns {boolean|number|'auto'|this} */ this.binned = function (val) { if (val === undefined) { @@ -146,9 +162,14 @@ var heatmapFeature = function (arg) { }; /** - * Get/Set position accessor + * Get/Set position accessor. * - * @returns {geo.heatmap} + * @param {geo.geoPosition|function} [val] If not specified, return the + * current position accessor. If specified, use this for the position + * accessor and return `this`. If a function is given, this is called + * with `(dataElement, dataIndex)`. + * @returns {geo.geoPosition|function|this} The current position or this + * feature. */ this.position = function (val) { if (val === undefined) { @@ -162,7 +183,7 @@ var heatmapFeature = function (arg) { }; /** - * Get pre-computed gcs position accessor + * Get pre-computed gcs position accessor. * * @returns {geo.heatmap} */ @@ -172,9 +193,11 @@ var heatmapFeature = function (arg) { }; /** - * Get/Set intensity + * Get/Set intensity. * - * @returns {geo.heatmap} + * @param {function} [val] If not specified, the current intensity accessor. + * Otherwise, a function that returns the intensity of each data point. + * @returns {function|this} */ this.intensity = function (val) { if (val === undefined) { @@ -188,7 +211,9 @@ var heatmapFeature = function (arg) { }; /** - * Initialize + * Initialize. + * + * @param {geo.heatmapFeature.spec} arg */ this._init = function (arg) { s_init.call(m_this, arg); @@ -199,11 +224,12 @@ var heatmapFeature = function (arg) { radius: 10, blurRadius: 10, gaussian: true, - 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}} + 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 ); @@ -216,8 +242,9 @@ var heatmapFeature = function (arg) { }; /** - * Build - * @override + * Build the fetaure. + * + * @returns {this} */ this._build = function () { var data = m_this.data(), @@ -226,10 +253,10 @@ var heatmapFeature = function (arg) { setMax = (m_maxIntensity === null || m_maxIntensity === undefined), setMin = (m_minIntensity === null || m_minIntensity === undefined); - data.forEach(function (d) { - position.push(m_this.position()(d)); + data.forEach(function (d, i) { + position.push(m_this.position()(d, i)); if (setMax || setMin) { - intensity = m_this.intensity()(d); + intensity = m_this.intensity()(d, i); if (m_maxIntensity === null || m_maxIntensity === undefined) { m_maxIntensity = intensity; }