Skip to content

Commit 2795a88

Browse files
Fix heatmap colorDomain (visgl#5802)
1 parent bc2bfdf commit 2795a88

File tree

3 files changed

+27
-24
lines changed

3 files changed

+27
-24
lines changed

docs/api-reference/aggregation-layers/heatmap-layer.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ Radius of the circle in pixels, to which the weight of an object is distributed.
7878

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

81-
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.
81+
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.
8282

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

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

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

103103
* Default: `null`
104104

105-
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`.
105+
Controls how weight values are mapped to the `colorRange`, as an array of two numbers [`minValue`, `maxValue`].
106+
107+
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`.
108+
109+
- If using `aggregation: 'SUM'`, values in `colorDomain` are interpreted as weight per square meter.
110+
- If using `aggregation: 'MEAN'`, values in `colorDomain` are interpreted as weight.
111+
112+
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`.
106113

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

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

111117
* Default: `'SUM'`
112118

113119
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.
114120

121+
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.
122+
115123
### Data Accessors
116124

117125
##### `getPosition` ([Function](/docs/developer-guide/using-layers.md#accessors), optional)

docs/upgrade-guide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The module entry point is now only lightly transpiled for the most commonly used
1919
- `GeoJsonLayer`'s `lineJointRounded` prop now only controls line joints. To use rounded line caps, set `lineCapRounded` to `true`.
2020
- Dashed lines via `PathStyleExtension` now draw rounded dash caps if `capRounded` is `true`.
2121
- `@deck.gl/geo-layers` now requires `@deck.gl/extensions`, due to `ClipExtension` dependency.
22+
- `HeatmapLayer`'s `colorDomain` prop has redefined the unit of its values. See updated layer documentation for details.
2223
- `MVTLayer`'s `binary` prop is now set to `true` by default.
2324

2425
### onError Callback

modules/aggregation-layers/src/heatmap-layer/heatmap-layer.js

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export default class HeatmapLayer extends AggregationLayer {
9595
return;
9696
}
9797
super.initializeState(DIMENSIONS);
98-
this.setState({supported: true});
98+
this.setState({supported: true, colorDomain: DEFAULT_COLOR_DOMAIN});
9999
this._setupTextureParams();
100100
this._setupAttributes();
101101
this._setupResources();
@@ -133,23 +133,6 @@ export default class HeatmapLayer extends AggregationLayer {
133133
this._updateColorTexture(opts);
134134
}
135135

136-
if (oldProps.colorDomain !== props.colorDomain || changeFlags.viewportChanged) {
137-
const {viewport} = this.context;
138-
const {weightsScale} = this.state;
139-
const domainScale = (viewport ? 1024 / viewport.scale : 1) * weightsScale;
140-
const colorDomain = props.colorDomain
141-
? props.colorDomain.map(x => x * domainScale)
142-
: DEFAULT_COLOR_DOMAIN;
143-
if (colorDomain[1] > 0 && weightsScale < 1) {
144-
// Hack - when low precision texture is used, aggregated weights are in the [0, 1]
145-
// range. Scale colorDomain to fit.
146-
const max = Math.min(colorDomain[1], 1);
147-
colorDomain[0] *= max / colorDomain[1];
148-
colorDomain[1] = max;
149-
}
150-
this.setState({colorDomain});
151-
}
152-
153136
if (this.state.isWeightMapDirty) {
154137
this._updateWeightmap();
155138
}
@@ -459,15 +442,26 @@ export default class HeatmapLayer extends AggregationLayer {
459442
}
460443

461444
_updateWeightmap() {
462-
const {radiusPixels} = this.props;
445+
const {radiusPixels, colorDomain, aggregation} = this.props;
463446
const {weightsTransform, worldBounds, textureSize, weightsTexture, weightsScale} = this.state;
464447
this.state.isWeightMapDirty = false;
465448

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

454+
if (colorDomain && aggregation === 'SUM') {
455+
// scale color domain to weight per pixel
456+
const {viewport} = this.context;
457+
const metersPerPixel =
458+
(viewport.distanceScales.metersPerUnit[2] * (commonBounds[2] - commonBounds[0])) /
459+
textureSize;
460+
this.state.colorDomain = colorDomain.map(x => x * metersPerPixel * weightsScale);
461+
} else {
462+
this.state.colorDomain = DEFAULT_COLOR_DOMAIN;
463+
}
464+
471465
const uniforms = {
472466
radiusPixels,
473467
commonBounds,

0 commit comments

Comments
 (0)