Skip to content

Commit

Permalink
Add a grid feature.
Browse files Browse the repository at this point in the history
This refactors the contourFeature to avoid code duplication, as the grid
and contour features are nearly the same.  Specifically, the contour
feature has data values at each vertex of a mesh and the grid feature
has data values on the elements of the mesh.  The grid feature has solid
colors for each mesh element, while the contour feature has interpolated
values between the vertices.

This adds a small tutorial.

This closes #545.
  • Loading branch information
manthey committed Jun 8, 2021
1 parent 3dc5c77 commit cbc7bc5
Show file tree
Hide file tree
Showing 22 changed files with 1,054 additions and 331 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ website/**
jsdoc/template/publish.js
jsdoc/template/static/**
jsdoc/template/tmpl/**
docs/_build/**
2 changes: 1 addition & 1 deletion scripts/datastore.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var tar = require('tar');

var registry = {
'AdderallCities2015.csv': 'c3e984482cc6db1193a6dca2a55396a2baad8541a5c8c679f33750b76f766f40a119ec3e63abbabcd095d752c3df8ce21bf24cbe629502121f24ba90b4b0674c',
'base-images.tgz': '346dcdcf6e88aa9bfde684a311a452431af01b055f007e73839d42aa19ce0846af20d0bc296f7e1acca0af2759896d647dbbdbf07b20428f0e10464a1764c77e',
'base-images.tgz': '9eed90578ac35e96ba701198e2518cb03bf08447d649375cbf7729524139a9951a5b1b580fc03371532dc128a530794e061a8e05b69aa5b5869efbf98245670e',
'blue.jpg': '867b1f3c568289efc7d0dba97d827a2bc4d83a7465cebcb3b5aec7bac6a38cf70d037d1814402bc97ad1f2f6737cfb5ce97db0a4fb53a716e77fd3ba57a7ab3b',
'cities.csv': '5a665e5feda24f28e5cf4ed0801b67e73bbcf3ea781b2e50d11284214e67b25b68e6a1c48da46e5e4d4d0c54c2ec18f88d292224b4541fb279396cf7b94beac9',
'earthquakes.json': 'f098b6437411384b552419b4a36264c1bb3fed816ccfe9545145175e0b92a0b7ad5ebdcb9dddd0a12a90499143ffa471c02f6e049be5b973db607ff066892500',
Expand Down
2 changes: 1 addition & 1 deletion scripts/make_thumbnails.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@


def process_item(path, opts):
output = (open('/tmp/thumbnail.out', 'ab')
output = (open('/tmp/thumbnail.out', 'a')
if opts.get('verbose', 0) >= 1 else open(os.devnull, 'w'))
data = json.load(open(path))
if data.get('disabled') and not opts.get('all'):
Expand Down
101 changes: 7 additions & 94 deletions src/contourFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var meshFeature = require('./meshFeature');
* Contour feature specification.
*
* @typedef {geo.feature.spec} geo.contourFeature.spec
* @extends geo.feature.spec
* @property {object[]} [data=[]] An array of arbitrary objects used to
* construct the feature.
* @property {geo.contourFeature.styleSpec} [style] An object that contains
Expand Down Expand Up @@ -71,23 +72,8 @@ var meshFeature = require('./meshFeature');
/**
* Computed contour information.
*
* @typedef {geo.meshFeature.meshInfo} geo.contourFeature.contourInfo
* @property {number[]} value An array of values that have been normalized to a
* range of [0, steps]. There is one value per vertex.
* @property {number[]} opacity An array of opacities per vertex.
* @property {number} minValue the minimum value used for the contour. If
* `rangeValues` was specified, this is the first entry of that array.
* @property {number} maxValue the maximum value used for the contour. If
* `rangeValues` was specified, this is the last entry of that array.
* @property {number} factor If linear value scaling is used, this is the
* number of color values divided by the difference between the maximum and
* minimum values. It is ignored if non-linear value scaling is used.
* @property {geo.geoColorObject} minColor The color used for values below
* minValue. Includes opacity.
* @property {geo.geoColorObject} maxColor The color used for values above
* maxValue. Includes opacity.
* @property {geo.geoColorObject[]} colorMap The specified `colorRange` and
* `opacityRange` converted into objects that include opacity.
* @typedef {geo.meshFeature.meshColoredInfo} geo.contourFeature.contourInfo
* @extends geo.meshFeature.meshColoredInfo
*/

/**
Expand All @@ -110,6 +96,7 @@ var contourFeature = function (arg) {

var $ = require('jquery');
var util = require('./util');
var meshUtil = require('./util/mesh');

arg = arg || {};
meshFeature.call(this, arg);
Expand All @@ -129,82 +116,7 @@ var contourFeature = function (arg) {
* information.
*/
this._createContours = function () {
var contour = m_this.contour,
valueFunc = m_this.style.get('value'),
usedFunc = m_this.style('used') !== undefined ?
m_this.style.get('used') :
function (d, i) { return util.isNonNullFinite(valueFunc(d, i)); },
minmax, val, range, i, k;
var result = m_this._createMesh({
used: usedFunc,
opacity: m_this.style.get('opacity'),
value: valueFunc
});
if (!result.numVertices || !result.numElements) {
return result;
}
var stepped = contour.get('stepped')(result),
opacityRange = contour.get('opacityRange')(result),
rangeValues = contour.get('rangeValues')(result);
result.stepped = stepped === undefined || stepped ? true : false;
/* Create the min/max colors and the color array */
result.colorMap = [];
result.minColor = $.extend(
{a: contour.get('minOpacity')(result) || 0},
util.convertColor(contour.get('minColor')(result)));
result.maxColor = $.extend(
{a: contour.get('maxOpacity')(result) || 0},
util.convertColor(contour.get('maxColor')(result)));
contour.get('colorRange')(result).forEach(function (clr, idx) {
result.colorMap.push($.extend({
a: opacityRange && opacityRange[idx] !== undefined ? opacityRange[idx] : 1
}, util.convertColor(clr)));
});
/* Get min and max values */
minmax = util.getMinMaxValues(result.value, contour.get('min')(result), contour.get('max')(result));
result.minValue = minmax.min;
result.maxValue = minmax.max;
if (!rangeValues || !result.colorMap ||
(rangeValues.length !== result.colorMap.length + 1 && (
stepped || rangeValues.length !== result.colorMap.length))) {
rangeValues = null;
}
if (rangeValues) { /* ensure increasing monotonicity */
for (k = 1; k < rangeValues.length; k += 1) {
if (rangeValues[k - 1] > rangeValues[k]) {
rangeValues = null;
break;
}
}
}
if (rangeValues) {
result.minValue = rangeValues[0];
result.maxValue = rangeValues[rangeValues.length - 1];
}
range = result.maxValue - result.minValue;
if (!range) {
result.colorMap = result.colorMap.slice(0, 1);
range = 1;
rangeValues = null;
}
result.rangeValues = rangeValues;
result.factor = result.colorMap.length / range;
/* Scale values */
for (i = 0; i < result.numVertices; i += 1) {
val = result.value[i];
if (rangeValues && val >= result.minValue && val <= result.maxValue) {
for (k = 1; k < rangeValues.length; k += 1) {
if (val <= rangeValues[k]) {
result.value[i] = k - 1 + (val - rangeValues[k - 1]) /
(rangeValues[k] - rangeValues[k - 1]);
break;
}
}
} else {
result.value[i] = (val - result.minValue) * result.factor;
}
}
return result;
return meshUtil.createColoredMesh(m_this, false);
};

this.contour = m_this.mesh;
Expand All @@ -222,8 +134,9 @@ var contourFeature = function (arg) {
{
opacity: 1.0,
value: function (d, i) {
return m_this.position()(d, i).z;
return util.isNonNullFinite(d) ? d : m_this.position()(d, i).z;
},
position: (d) => d || {x: 0, y: 0},
origin: (p) => (p.length >= 3 ? [p[0], p[1], 0] : [0, 0, 0])
},
arg.style === undefined ? {} : arg.style
Expand Down
175 changes: 175 additions & 0 deletions src/gridFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
var inherit = require('./inherit');
var meshFeature = require('./meshFeature');

/**
* Grid feature specification.
*
* @typedef {geo.feature.spec} geo.gridFeature.spec
* @extends geo.feature.spec
* @property {object[]} [data=[]] An array of arbitrary objects used to
* construct the feature.
* @property {geo.gridFeature.styleSpec} [style] An object that contains
* style values for the feature.
* @property {geo.gridFeature.gridSpec} [grid] The grid specification for the
* feature.
*/

/**
* Style specification for a grid feature.
*
* @typedef {geo.feature.styleSpec} geo.gridFeature.styleSpec
* @extends geo.feature.styleSpec
* @property {geo.geoPosition|function} [position=data] The position of each
* data element. This defaults to just using `x`, `y`, and `z` properties
* of the data element itself. The position is in the feature's gcs
* coordinates.
* @property {number|function} [value=data.z] The value of each data element.
* This defaults to the `z` property of the data elements. If the value of
* a grid point is `null` or `undefined`, the point and elements that use
* that point won't be included in the results.
* @property {number|function} [opacity=1] The opacity for the whole feature on
* a scale of 0 to 1.
* @property {number[]|function} [origin] Origin in map gcs coordinates used
* for to ensure high precision drawing in this location. When called as a
* function, this is passed the vertex positions as a single continuous array
* in map gcs coordinates. It defaults to the first vertex used in the
* grid.
*/

/**
* Grid specification. All of these properties can be functions, which get
* passed the {@link geo.meshFeature.meshInfo} object.
*
* @typedef {geo.meshFeature.meshSpec} geo.gridFeature.gridSpec
* @extends geo.meshFeature.meshSpec
* @property {number} [min] Minimum grid value. If unspecified, taken from
* the computed minimum of the `value` style.
* @property {number} [max] Maximum grid value. If unspecified, taken from
* the computed maximum of the `value` style.
* @property {geo.geoColor} [minColor='black'] Color used for any value below
* the minimum.
* @property {number} [minOpacity=0] Opacity used for any value below the
* minimum.
* @property {geo.geoColor} [maxColor='black'] Color used for any value above
* the maximum.
* @property {number} [maxOpacity=0] Opacity used for any value above the
* maximum.
* @property {boolean} [stepped] If falsy but not `undefined`, smooth
* transitions between colors.
* @property {geo.geoColor[]} [colorRange=<color table>] An array of colors
* used to show the range of values. The default is a 9-step color table.
* @property {number[]} [opacityRange] An array of opacities used to show the
* range of values. If unspecified, the opacity is 1. If this is a shorter
* list than the `colorRange`, an opacity of 1 is used for the entries near
* the end of the color range.
* @property {number[]} [rangeValues] An array used to map values to the
* `colorRange`. By default, values are spaced linearly. If specified, the
* entries must be increasing weakly monotonic, and there must be one more
* entry then the length of `colorRange`.
*/

/**
* Computed grid information.
*
* @typedef {geo.meshFeature.meshColoredInfo} geo.gridFeature.gridInfo
* @extends geo.meshFeature.meshColoredInfo
*/

/**
* Create a new instance of class gridFeature.
*
* @class
* @alias geo.gridFeature
* @extends geo.meshFeature
*
* @borrows geo.gridFeature#mesh as geo.gridFeature#grid
*
* @param {geo.gridFeature.spec} arg
* @returns {geo.gridFeature}
*/
var gridFeature = function (arg) {
'use strict';
if (!(this instanceof gridFeature)) {
return new gridFeature(arg);
}

var $ = require('jquery');
var util = require('./util');
var meshUtil = require('./util/mesh');

arg = arg || {};
meshFeature.call(this, arg);

/**
* @private
*/
var m_this = this,
s_init = this._init;

/**
* Create a set of vertices and values and opacities inside triangles.
* Create a set of triangles of indices into the vertex array. Create a
* color and opacity map corresponding to the values.
*
* @returns {geo.gridFeature.gridInfo} An object with the grid
* information.
*/
this._createGrids = function () {
return meshUtil.createColoredMesh(m_this, true);
};

this.grid = m_this.mesh;

/**
* Initialize.
*
* @param {geo.gridFeature.spec} arg The grid feature specification.
*/
this._init = function (arg) {
s_init.call(m_this, arg);

var defaultStyle = $.extend(
{},
{
opacity: 1.0,
value: function (d, i) {
return util.isNonNullFinite(d) ? d : m_this.position()(d, i).z;
},
position: (d) => d || {x: 0, y: 0},
origin: (p) => (p.length >= 3 ? [p[0], p[1], 0] : [0, 0, 0])
},
arg.style === undefined ? {} : arg.style
);

m_this.style(defaultStyle);

m_this.grid($.extend({}, {
minColor: 'black',
minOpacity: 0,
maxColor: 'black',
maxOpacity: 0,
/* 9-step based on paraview bwr colortable */
colorRange: [
{r: 0.07514311, g: 0.468049805, b: 1},
{r: 0.468487184, g: 0.588057293, b: 1},
{r: 0.656658579, g: 0.707001303, b: 1},
{r: 0.821573924, g: 0.837809045, b: 1},
{r: 0.943467973, g: 0.943498599, b: 0.943398095},
{r: 1, g: 0.788626485, b: 0.750707739},
{r: 1, g: 0.6289553, b: 0.568237474},
{r: 1, g: 0.472800903, b: 0.404551679},
{r: 0.916482116, g: 0.236630659, b: 0.209939162}
]
}, arg.mesh || {}, arg.grid || {}));

if (arg.mesh || arg.grid) {
m_this.dataTime().modified();
}
};

this._init(arg);
return this;
};

inherit(gridFeature, meshFeature);
module.exports = gridFeature;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ module.exports = $.extend({
imageTile: require('./imageTile'),
isolineFeature: require('./isolineFeature'),
geojsonReader: require('./geojsonReader'),
gridFeature: require('./gridFeature'),
layer: require('./layer'),
lineFeature: require('./lineFeature'),
map: require('./map'),
Expand Down
Loading

0 comments on commit cbc7bc5

Please sign in to comment.