From 0bbdada07d58045c7db6fb0eb1f6a0ca3bc857ba Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 20 Jun 2018 16:40:43 -0400 Subject: [PATCH 1/2] Update documentation for pointFeature. This also touches documentation on util/clustering.js, though that could use some refactoring so that clusters are computed in map gcs rather than in feature gcs (usually interface gcs) and the cluster size is given in simpler units (such as display pixels). This fixes a number of trivial documentation issues in other files, too. Also, improve documentation of position in line and polygon features. --- src/d3/lineFeature.js | 1 + src/d3/pointFeature.js | 17 ++++--- src/d3/uniqueID.js | 7 +-- src/feature.js | 3 ++ src/gl/lineFeature.js | 2 +- src/gl/pointFeature.js | 66 +++++++++++++++++++++++---- src/lineFeature.js | 15 ++++--- src/pointFeature.js | 100 ++++++++++++++++++++++++++++++++++------- src/polygonFeature.js | 14 +++--- src/util/clustering.js | 25 +++++++---- 10 files changed, 191 insertions(+), 59 deletions(-) diff --git a/src/d3/lineFeature.js b/src/d3/lineFeature.js index ef96e0877d..3eadf23bc1 100644 --- a/src/d3/lineFeature.js +++ b/src/d3/lineFeature.js @@ -8,6 +8,7 @@ var lineFeature = require('../lineFeature'); * @class * @alias geo.d3.lineFeature * @extends geo.lineFeature + * @extends geo.d3.object * @param {geo.lineFeature.spec} arg * @returns {geo.d3.lineFeature} */ diff --git a/src/d3/pointFeature.js b/src/d3/pointFeature.js index ee40cb1c4a..83be3bfb4e 100644 --- a/src/d3/pointFeature.js +++ b/src/d3/pointFeature.js @@ -4,12 +4,13 @@ var pointFeature = require('../pointFeature'); /** * - * Create a new instance of pointFeature + * Create a new instance of d3.pointFeature. * * @class * @alias geo.d3.pointFeature * @extends geo.pointFeature * @extends geo.d3.object + * @param {geo.pointFeature.spec} arg * @returns {geo.d3.pointFeature} */ var d3_pointFeature = function (arg) { @@ -35,7 +36,10 @@ var d3_pointFeature = function (arg) { m_style = {}; /** - * Initialize + * Initialize. + * + * @param {geo.pointFeature.spec} arg The feature specification. + * @returns {this} */ this._init = function (arg) { s_init.call(m_this, arg); @@ -43,9 +47,9 @@ var d3_pointFeature = function (arg) { }; /** - * Build + * Build. Create the necessary elements to render points. * - * @override + * @returns {this} */ this._build = function () { var data = m_this.data(), @@ -84,11 +88,10 @@ var d3_pointFeature = function (arg) { m_this.updateTime().modified(); return m_this; }; - /** - * Update + * Update. Rebuild if necessary. * - * @override + * @returns {this} */ this._update = function () { s_update.call(m_this); diff --git a/src/d3/uniqueID.js b/src/d3/uniqueID.js index 83088bbfbe..6b9422f99c 100644 --- a/src/d3/uniqueID.js +++ b/src/d3/uniqueID.js @@ -2,9 +2,10 @@ var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz', strLength = 8; /** - * Get a random string to use as a div ID - * @function geo.d3.uniqueID - * @returns {string} + * Get a random string to use as a div ID. + * + * @alias geo.d3.uniqueID + * @returns {string} A random ID string. */ var uniqueID = function () { var strArray = [], diff --git a/src/feature.js b/src/feature.js index 798d4f9bcd..472d9e2842 100644 --- a/src/feature.js +++ b/src/feature.js @@ -405,6 +405,9 @@ var feature = function (arg) { * has a subfeature style, with `(subfeatureElement, subfeatureIndex, * dataElement, dataIndex)`. * + * See the feature's specification ({@link geo.feature.spec}) for available + * styles. + * * @param {string|object} [arg1] If `undefined`, return the current style * object. If a string and `arg2` is undefined, return the style * associated with the specified key. If a string and `arg2` is defined, diff --git a/src/gl/lineFeature.js b/src/gl/lineFeature.js index 6b4c5bf98f..9917afd366 100644 --- a/src/gl/lineFeature.js +++ b/src/gl/lineFeature.js @@ -376,7 +376,7 @@ var gl_lineFeature = function (arg) { return shader; } - /** + /** * Create and style the data needed to render the lines. * * @param {boolean} onlyStyle if true, use the existing geoemtry and just diff --git a/src/gl/pointFeature.js b/src/gl/pointFeature.js index abc61eed68..cde3e02805 100644 --- a/src/gl/pointFeature.js +++ b/src/gl/pointFeature.js @@ -4,11 +4,12 @@ var registerFeature = require('../registry').registerFeature; var pointFeature = require('../pointFeature'); /** - * Create a new instance of pointFeature + * Create a new instance of gl.pointFeature. * * @class * @alias geo.gl.pointFeature * @extends geo.pointFeature + * @param {geo.pointFeature.spec} arg * @returns {geo.gl.pointFeature} */ var gl_pointFeature = function (arg) { @@ -196,18 +197,39 @@ var gl_pointFeature = function (arg) { fragmentShaderSource = fragmentShaderSource.join('\n'); + /** + * Create the vertex shader for points. + * + * @returns {vgl.shader} + */ function createVertexShader() { var shader = new vgl.shader(vgl.GL.VERTEX_SHADER); shader.setShaderSource(vertexShaderSource); return shader; } + /** + * Create the fragment shader for points. + * + * @returns {vgl.shader} + */ function createFragmentShader() { var shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER); shader.setShaderSource(fragmentShaderSource); return shader; } + /** + * Given the current primitive shape and a basic size, return a set of + * vertices that can be used for a generic point. + * + * @param {number} x The base x coordinate. Usually 0. + * @param {number} y The base y coordinate. Usually 0. + * @param {number} w The base width. Usually 1. + * @param {number} h The base height. Usually 1. + * @returns {number[]} A flat array of vertices in the form of + * `[x0, y0, x1, y1, ...]`. + */ function pointPolygon(x, y, w, h) { var verts; switch (m_primitiveShape) { @@ -240,6 +262,9 @@ var gl_pointFeature = function (arg) { return verts; } + /** + * Create and style the data needed to render the points. + */ function createGLPoints() { // unit and associated data is not used when drawing sprite var i, j, numPts = m_this.data().length, @@ -351,7 +376,7 @@ var gl_pointFeature = function (arg) { } /** - * Return list of actors + * Return list of vgl actorss used for rendering. * * @returns {vgl.actor[]} */ @@ -365,13 +390,33 @@ var gl_pointFeature = function (arg) { /** * Return the number of vertices used for each point. * - * @returns {Number} + * @returns {number} */ this.verticesPerFeature = function () { var unit = pointPolygon(0, 0, 1, 1); return unit.length / 2; }; + /** + * Set style(s) from array(s). For each style, the array should have one + * value per data item. The values are not converted or validated. Color + * values should be {@link geo.geoColorObject}s. If invalid values are given + * the behavior is undefined. + * For some feature styles, if the first entry of an array is itself an + * array, then each entry of the array is expected to be an array, and values + * are used from these subarrays. This allows a style to apply, for + * instance, per vertex of a data item rather than per data item. + * + * @param {string|object} keyOrObject Either the name of a single style or + * an object where the keys are the names of styles and the values are + * each arrays. + * @param {array} styleArray If keyOrObject is a string, an array of values + * for the style. If keyOrObject is an object, this parameter is ignored. + * @param {boolean} [refresh=false] `true` to redraw the feature when it has + * been updated. If an object with styles is passed, the redraw is only + * done once. + * @returns {this} + */ this.updateStyleFromArray = function (keyOrObject, styleArray, refresh) { var bufferedKeys = { fill: 'bool', @@ -445,10 +490,11 @@ var gl_pointFeature = function (arg) { } else if (needsRender) { m_this.renderer()._render(); } + return m_this; }; /** - * Initialize + * Initialize. */ this._init = function () { var prog = vgl.shaderProgram(), @@ -547,9 +593,9 @@ var gl_pointFeature = function (arg) { }; /** - * Build + * Build. Create the necessary elements to render points. * - * @override + * @returns {this} */ this._build = function () { @@ -562,12 +608,13 @@ var gl_pointFeature = function (arg) { m_this.renderer().contextRenderer().addActor(m_actor); m_this.renderer().contextRenderer().render(); m_this.buildTime().modified(); + return m_this; }; /** - * Update + * Update. Rebuild if necessary. * - * @override + * @returns {this} */ this._update = function () { @@ -589,10 +636,11 @@ var gl_pointFeature = function (arg) { m_actor.material().setBinNumber(m_this.bin()); m_this.updateTime().modified(); + return m_this; }; /** - * Destroy + * Destroy. Free used resources. */ this._exit = function () { m_this.renderer().contextRenderer().removeActor(m_actor); diff --git a/src/lineFeature.js b/src/lineFeature.js index b1b58e49d0..4d1b065010 100644 --- a/src/lineFeature.js +++ b/src/lineFeature.js @@ -8,8 +8,8 @@ var util = require('./util'); * Line feature specification. * * @typedef {geo.feature.spec} geo.lineFeature.spec - * @property {object|function} [position] Position of the data. Default is - * (data). + * @property {geo.geoPosition|function} [position] Position of the data. + * Default is (data). * @property {object|function} [line] Lines from the data. Default is (data). * Typically, the data is an array of lines, each of which is an array of * points. Only lines that have at least two points are rendered. The @@ -105,11 +105,12 @@ var lineFeature = function (arg) { /** * Get/Set position accessor. * - * @param {object|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 - * `(vertexElement, vertexIndex, dataElement, dataIndex)`. - * @returns {object|function|this} The current position or this feature. + * @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 `(vertexElement, vertexIndex, dataElement, dataIndex)`. + * @returns {geo.geoPosition|function|this} The current position or this + * feature. */ this.position = function (val) { if (val === undefined) { diff --git a/src/pointFeature.js b/src/pointFeature.js index 5e00fd9bdd..fcf3732058 100644 --- a/src/pointFeature.js +++ b/src/pointFeature.js @@ -2,13 +2,59 @@ var inherit = require('./inherit'); var feature = require('./feature'); /** - * Create a new instance of class pointFeature + * Object specification for a point feature. + * + * @typedef {geo.feature.spec} geo.pointFeature.spec + * @property {geo.geoPosition|function} [position] Position of the data. + * Default is (data). + * @property {object} [style] Style object with default style options. + * @property {number|function} [style.radius=5] Radius of each point in pixels. + * This is the fill radius inside of the stroke. + * @property {boolean|function} [style.stroke=true] True to stroke point. + * @property {geo.geoColor|function} [style.strokeColor] Color to stroke each + * point. + * @property {number|function} [style.strokeOpacity=1] Opacity for each point's + * stroke. Opacity is on a [0-1] scale. + * @property {number|function} [style.strokeWidth=1.25] The weight of the + * point's stroke in pixels. + * @property {boolean|function} [style.fill=true] True to fill point. + * @property {geo.geoColor|function} [style.fillColor] Color to fill each + * point/ + * @property {number|function} [style.fillOpacity=1] Opacity for each point. + * Opacity is on a [0-1] scale. + * @property {boolean|geo.pointFeature.clusteringSpec} [clustering=false] + * Enable point clustering. + * @property {string} [primitiveShape='sprite'] For the gl renderer, select the + * primitive shape. This is one of `'triangle'`, `'square'`, or `'sprite'`. + * `sprite` uses the least memory, `triangle` is fastest if the vertex shader + * is the bottleneck, and `square` is fastest if the fragment shader is the + * bottleneck. `sprite` may not work for very large points. + * @property {boolean} [dynamicDraw=false] For the gl renderer, if this is + * truthy, webgl source buffers can be modifies and updated directly. + */ + +/** + * Point clustering specification. + * + * @typedef {object} geo.pointFeature.clusteringSpec + * @property {number} [radius=0.05] This is combined with the `width` and + * `height` to determine how close points need to be to each other to be + * clustered. + * @property {number} [maxZoom=18] Never cluster above this zoom level. + * @property {number} [width=256] + * @property {number} [height=256] + */ +// TODO: refactor point clustering to (a) not be based on window size, (b) use +// points in the map.gcs coordinate system, not the pointFeature.gcs coordinate +// system, (c) convert the clustering radius to be in display pixels + +/** + * Create a new instance of class pointFeature. * * @class * @alias geo.pointFeature - * @param {object} arg Options object - * @param {boolean} arg.clustering Enable point clustering * @extends geo.feature + * @param {geo.pointFeature.spec} arg * @returns {geo.pointFeature} */ var pointFeature = function (arg) { @@ -44,9 +90,13 @@ var pointFeature = function (arg) { this.featureType = 'point'; /** - * Get/Set clustering option + * Get/Set clustering option. * - * @returns {geo.pointFeature|boolean} + * @param {boolean|geo.pointFeature.clusteringSpec} [val] If not specified, + * return the current value. If specified and falsy, turn off clustering. + * If `true`, use a default clustering with `radius` set to `0.01`. + * Otherwise, turn on clustering with these options. + * @returns {geo.pointFeature.clusteringSpec|boolean|this} */ this.clustering = function (val) { if (val === undefined) { @@ -101,6 +151,8 @@ var pointFeature = function (arg) { * Handle zoom events for clustering. This keeps track of the last * clustering level, and only regenerates the displayed points when the * zoom level changes. + * + * @param {number} zoom The new zoom level. */ this._handleZoom = function (zoom) { // get the current zoom level rounded down @@ -138,9 +190,13 @@ var pointFeature = function (arg) { }; /** - * Get/Set position + * Get/Set position. * - * @returns {geo.pointFeature} + * @param {function|geo.geoPosition} [val] If not specified, return the + * position accessor, which is guaranteed to be a function. If specified, + * wrap the value in an function that handles clustering if it is enabled + * and set the position accessor to that function. + * @returns {this|function} */ this.position = function (val) { if (val === undefined) { @@ -199,8 +255,12 @@ var pointFeature = function (arg) { /** * Returns an array of datum indices that contain the given point. * Largely adapted from wigglemaps pointQuerier: - * * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js + * This does not take into account clustering. + * + * @param {geo.geoPosition} p point to search for in map interface gcs. + * @returns {object} An object with `index`: a list of point indices, and + * `found`: a list of points that contain the specified coordinate. */ this.pointSearch = function (p) { var min, max, data, idx = [], found = [], ifound = [], map, pt, @@ -269,6 +329,11 @@ var pointFeature = function (arg) { /** * Returns an array of datum indices that are contained in the given box. + * This does not take clustering into account. + * + * @param {geo.geoPosition} lowerLeft Lower-left corner in gcs coordinates. + * @param {geo.geoPosition} upperRight Upper-right corner in gcs coordinates. + * @returns {number[]} A list of point indices that are in the box region. */ this.boxSearch = function (lowerLeft, upperRight) { var pos = m_this.position(), @@ -289,6 +354,11 @@ var pointFeature = function (arg) { /** * Overloaded data method that updates the internal range tree on write. + * Get/Set the data array for the feature. + * + * @param {array} [data] A new data array or `undefined` to return the + * existing array. + * @returns {array|this} */ this.data = function (data) { if (data === undefined) { @@ -307,7 +377,10 @@ var pointFeature = function (arg) { }; /** - * Initialize + * Initialize. + * + * @param {geo.pointFeature.spec} arg The feature specification. + * @returns {this} */ this._init = function (arg) { arg = arg || {}; @@ -324,8 +397,6 @@ var pointFeature = function (arg) { fillColor: { r: 1.0, g: 0.839, b: 0.439 }, fill: true, fillOpacity: 0.8, - sprites: false, - sprites_image: null, position: function (d) { return d; } }, arg.style === undefined ? {} : arg.style @@ -345,17 +416,12 @@ var pointFeature = function (arg) { m_this.geoOn(geo_event.zoom, function (evt) { m_this._handleZoom(evt.zoomLevel); }); + return m_this; }; return m_this; }; -/** - * Object specification for a point feature. - * - * @typedef {geo.feature.spec} geo.pointFeature.spec - */ - /** * Create a pointFeature from an object. * @see {@link geo.feature.create} diff --git a/src/polygonFeature.js b/src/polygonFeature.js index 36b1ec005c..c7e42aa107 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -7,8 +7,8 @@ var transform = require('./transform'); * Polygon feature specification. * * @typedef {geo.feature.spec} geo.polygonFeature.spec - * @property {object|function} [position] Position of the data. Default is - * (data). + * @property {geo.geoPosition|function} [position] Position of the data. + * Default is (data). * @property {geo.polygon|function} [polygon] Polygons from the data. Default * (data). * @property {object} [style] Style object with default style options. @@ -205,11 +205,11 @@ var polygonFeature = function (arg) { /** * Get/Set position accessor. * - * @param {object|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 - * `(vertexElement, vertexIndex, dataElement, dataIndex)`. - * @returns {object|this} The current position or this feature. + * @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 `(vertexElement, vertexIndex, dataElement, dataIndex)`. + * @returns {geo.geoPosition|this} The current position or this feature. */ this.position = function (val) { if (val === undefined) { diff --git a/src/util/clustering.js b/src/util/clustering.js index 0d3d2a36b9..5e25230de5 100644 --- a/src/util/clustering.js +++ b/src/util/clustering.js @@ -83,7 +83,9 @@ ClusterTree.prototype.count = function () { * Recursively call a function on all points contained in the cluster. * Calls the function with `this` as the current ClusterTree object, and * arguments to arguments the point object and the zoom level: - * func.call(this, point, zoom) + * `func.call(this, point, zoom)`. + * + * @param {function} func The function to call. */ ClusterTree.prototype.each = function (func) { var i; @@ -101,6 +103,8 @@ ClusterTree.prototype.each = function (func) { /** * Get the coordinates of the cluster (the mean position of all the points * contained). This is lazily calculated and cached. + * + * @returns {geo.geoPosition} The 2-d coordinates of the center. */ ClusterTree.prototype.coords = function () { var i, center = {x: 0, y: 0}; @@ -128,15 +132,15 @@ ClusterTree.prototype.coords = function () { /** * This class manages clustering of an array of positions hierarchically. * The algorithm and code was adapted from the Leaflet marker cluster - * plugin by David Leaver: https://github.com/Leaflet/Leaflet.markercluster + * plugin by David Leaver: https://github.com/Leaflet/Leaflet.markercluster . * * @class * @alias geo.util.ClusterGroup * @param {object} opts An options object * @param {number} width The width of the window; used for scaling. * @param {number} height The height of the window; used for scaling. - * @param {number} maxZoom The maximimum zoom level to calculate - * @param {number} radius Proportional to the clustering radius in pixels + * @param {number} maxZoom The maximimum zoom level to calculate. + * @param {number} radius Proportional to the clustering radius in pixels. */ function C(opts, width, height) { @@ -169,6 +173,10 @@ function C(opts, width, height) { * it, this call should be replaced by a calculation involving the view port * size in point coordinates at a particular zoom level. * @private + * @param {number} zoom The zoom level for the scale. + * @param {number} width The viewport width. + * @param {number} height The viewport height. + * @returns {number} The scale for clustering the feature coordinates. */ C.prototype._scaleAtLevel = function (zoom, width, height) { return vgl.zoomToHeight(zoom, width, height) / 2 * this._opts.radius; @@ -177,6 +185,7 @@ C.prototype._scaleAtLevel = function (zoom, width, height) { /** * Add a position to the cluster group. * @protected + * @param {geo.geoPosition} point A point to add to the cluster. */ C.prototype.addPoint = function (point) { var zoom, closest, parent, newCluster, lastParent, z; @@ -248,8 +257,8 @@ C.prototype.addPoint = function (point) { /** * Return the unclustered points contained at a given zoom level. - * @param {number} zoom The zoom level - * @return {object[]} The array of unclustered points + * @param {number} zoom The zoom level. + * @returns {object[]} The array of unclustered points. */ C.prototype.points = function (zoom) { zoom = Math.min(Math.max(Math.floor(zoom), 0), this._opts.maxZoom - 1); @@ -258,8 +267,8 @@ C.prototype.points = function (zoom) { /** * Return the clusters contained at a given zoom level. - * @param {number} zoom The zoom level - * @return {ClusterTree[]} The array of clusters + * @param {number} zoom The zoom level. + * @returns {ClusterTree[]} The array of clusters. */ C.prototype.clusters = function (zoom) { zoom = Math.min(Math.max(Math.floor(zoom), 0), this._opts.maxZoom - 1); From ad7646c365cc4e6f5e80cdc856d9cebbcf38a4ee Mon Sep 17 00:00:00 2001 From: David Manthey Date: Thu, 12 Jul 2018 16:02:30 -0400 Subject: [PATCH 2/2] Remove the TODO comment and improve some wording. --- src/gl/pointFeature.js | 4 ++-- src/pointFeature.js | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/gl/pointFeature.js b/src/gl/pointFeature.js index cde3e02805..6ce4d4b5dd 100644 --- a/src/gl/pointFeature.js +++ b/src/gl/pointFeature.js @@ -400,8 +400,8 @@ var gl_pointFeature = function (arg) { /** * Set style(s) from array(s). For each style, the array should have one * value per data item. The values are not converted or validated. Color - * values should be {@link geo.geoColorObject}s. If invalid values are given - * the behavior is undefined. + * values are {@link geo.geoColorObject} objects. If invalid values are + * given the behavior is undefined. * For some feature styles, if the first entry of an array is itself an * array, then each entry of the array is expected to be an array, and values * are used from these subarrays. This allows a style to apply, for diff --git a/src/pointFeature.js b/src/pointFeature.js index fcf3732058..305b4e5d34 100644 --- a/src/pointFeature.js +++ b/src/pointFeature.js @@ -44,9 +44,6 @@ var feature = require('./feature'); * @property {number} [width=256] * @property {number} [height=256] */ -// TODO: refactor point clustering to (a) not be based on window size, (b) use -// points in the map.gcs coordinate system, not the pointFeature.gcs coordinate -// system, (c) convert the clustering radius to be in display pixels /** * Create a new instance of class pointFeature.