Skip to content

Commit

Permalink
Fix heatmap colorDomain (visgl#5802)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress authored May 26, 2021
1 parent bc2bfdf commit 2795a88
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 24 deletions.
16 changes: 12 additions & 4 deletions docs/api-reference/aggregation-layers/heatmap-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ Radius of the circle in pixels, to which the weight of an object is distributed.

* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` <img src="https://deck.gl/images/colorbrewer_YlOrRd_6.png"/>

Specified as an array of colors [color1, color2, ...]. Each color is an array of 3 or 4 values [R, G, B] or [R, G, B, A], representing intensities of Red, Green, Blue and Alpha channels. Each intensity is a value between 0 and 255. When Alpha not provided a value of 255 is used.
The color palette used in the heatmap, as an array of colors [color1, color2, ...]. Each color is in the format of `[r, g, b, [a]]`. Each channel is a number between 0-255 and `a` is 255 if not supplied.

`colorDomain` is divided into `colorRange.length` equal segments, each mapped to one color in `colorRange`.
See the `colorDomain` section below for how weight values are mapped to colors in `colorRange`.

##### `intensity` (Number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square")

Expand All @@ -102,16 +102,24 @@ The `HeatmapLayer` reduces the opacity of the pixels with relatively low weight

* Default: `null`

Weight of each data object is distributed to to all the pixels in a circle centered at the object position, weight a pixel receives is inversely proportional to its distance from the center. Pixels that fall into multiple circles will have sum of all weights. And the weight of the pixel determines its color. When `colorDomain` is specified, all pixels with weight with in specified `colorDomain` will get mapped to `colorRange`, pixels with weight less than `colorDomain[0]` will fade out (reduced alpha) and pixels with weight more than `colorDomain[1]` will mapped to the highest color in `colorRange`.
Controls how weight values are mapped to the `colorRange`, as an array of two numbers [`minValue`, `maxValue`].

When `colorDomain` is specified, a pixel with `minValue` is assigned the first color in `colorRange`, a pixel with `maxValue` is assigned the last color in `colorRange`, and any value in between is linearly interpolated. Pixels with weight less than `minValue` gradually fade out by reducing alpha, until 100% transparency representing `0`. Pixels with weight more than `maxValue` are capped to the last color in `colorRange`.

- If using `aggregation: 'SUM'`, values in `colorDomain` are interpreted as weight per square meter.
- If using `aggregation: 'MEAN'`, values in `colorDomain` are interpreted as weight.

When this prop is not specified, the maximum value is automatically determined from the current viewport, and the domain is set to [`maxValue * threshold`, `maxValue`]. This default behavior ensures that the colors are distributed somewhat reasonably regardless of the data in display. However, as a result, the color at a specific location is dependent on the current viewport and any other data points within view. To obtain a stable color mapping (e.g. for displaying a legend), you need to provide a custom `colorDomain`.

When not specified, maximum weight (`maxWeight`) is auto calculated and domain will be set to [`maxWeight * threshold`, `maxWeight`].

##### `aggregation` (String, optional)

* Default: `'SUM'`

Operation used to aggregate all data point weights to calculate a pixel's color value. One of `'SUM'` or `'MEAN'`. `'SUM'` is used when an invalid value is provided.

The weight of each data object is distributed to all the pixels in a circle centered at the object position. The weight that a pixel receives is inversely proportional to its distance from the center. In `'SUM'` mode, pixels that fall into multiple circles will have the sum of all weights. In `'MEAN'` mode, pixels that fall into multiple circles will have their weight calculated as the weighted average from all the neighboring data points. And the weight of the pixel determines its color.

### Data Accessors

##### `getPosition` ([Function](/docs/developer-guide/using-layers.md#accessors), optional)
Expand Down
1 change: 1 addition & 0 deletions docs/upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The module entry point is now only lightly transpiled for the most commonly used
- `GeoJsonLayer`'s `lineJointRounded` prop now only controls line joints. To use rounded line caps, set `lineCapRounded` to `true`.
- Dashed lines via `PathStyleExtension` now draw rounded dash caps if `capRounded` is `true`.
- `@deck.gl/geo-layers` now requires `@deck.gl/extensions`, due to `ClipExtension` dependency.
- `HeatmapLayer`'s `colorDomain` prop has redefined the unit of its values. See updated layer documentation for details.
- `MVTLayer`'s `binary` prop is now set to `true` by default.

### onError Callback
Expand Down
34 changes: 14 additions & 20 deletions modules/aggregation-layers/src/heatmap-layer/heatmap-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default class HeatmapLayer extends AggregationLayer {
return;
}
super.initializeState(DIMENSIONS);
this.setState({supported: true});
this.setState({supported: true, colorDomain: DEFAULT_COLOR_DOMAIN});
this._setupTextureParams();
this._setupAttributes();
this._setupResources();
Expand Down Expand Up @@ -133,23 +133,6 @@ export default class HeatmapLayer extends AggregationLayer {
this._updateColorTexture(opts);
}

if (oldProps.colorDomain !== props.colorDomain || changeFlags.viewportChanged) {
const {viewport} = this.context;
const {weightsScale} = this.state;
const domainScale = (viewport ? 1024 / viewport.scale : 1) * weightsScale;
const colorDomain = props.colorDomain
? props.colorDomain.map(x => x * domainScale)
: DEFAULT_COLOR_DOMAIN;
if (colorDomain[1] > 0 && weightsScale < 1) {
// Hack - when low precision texture is used, aggregated weights are in the [0, 1]
// range. Scale colorDomain to fit.
const max = Math.min(colorDomain[1], 1);
colorDomain[0] *= max / colorDomain[1];
colorDomain[1] = max;
}
this.setState({colorDomain});
}

if (this.state.isWeightMapDirty) {
this._updateWeightmap();
}
Expand Down Expand Up @@ -459,15 +442,26 @@ export default class HeatmapLayer extends AggregationLayer {
}

_updateWeightmap() {
const {radiusPixels} = this.props;
const {radiusPixels, colorDomain, aggregation} = this.props;
const {weightsTransform, worldBounds, textureSize, weightsTexture, weightsScale} = this.state;
this.state.isWeightMapDirty = false;

// #5: convert world bounds to common using Layer's coordiante system and origin
// convert world bounds to common using Layer's coordiante system and origin
const commonBounds = this._worldToCommonBounds(worldBounds, {
useLayerCoordinateSystem: true
});

if (colorDomain && aggregation === 'SUM') {
// scale color domain to weight per pixel
const {viewport} = this.context;
const metersPerPixel =
(viewport.distanceScales.metersPerUnit[2] * (commonBounds[2] - commonBounds[0])) /
textureSize;
this.state.colorDomain = colorDomain.map(x => x * metersPerPixel * weightsScale);
} else {
this.state.colorDomain = DEFAULT_COLOR_DOMAIN;
}

const uniforms = {
radiusPixels,
commonBounds,
Expand Down

0 comments on commit 2795a88

Please sign in to comment.