Skip to content

Commit

Permalink
Merge 5caff29 into 5fbc3d1
Browse files Browse the repository at this point in the history
  • Loading branch information
1chandu committed Sep 27, 2019
2 parents 5fbc3d1 + 5caff29 commit 68c1574
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 19 deletions.
18 changes: 15 additions & 3 deletions docs/layers/heatmap-layer.md
Expand Up @@ -5,12 +5,10 @@
</p>


# HeatmapLayer **Experimental**
# HeatmapLayer

`HeatmapLayer` can be used to visualize spatial distribution of data. It internally implements [Gaussian Kernel Density Estimation](https://en.wikipedia.org/wiki/Kernel_(statistics%29#Kernel_functions_in_common_use) to render heatmaps.

* NOTE: Current version of this layer is supported only for WebGL2 enabled browsers, support for WebGL1 browsers will be added in future releases.

```js
import DeckGL from '@deck.gl/react';
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
Expand Down Expand Up @@ -98,6 +96,20 @@ The `HeatmapLayer` reduces the opacity of the pixels with relatively low weight

`threshold` is defined as the ratio of the fading weight to the max weight, between `0` and `1`. For example, `0.1` affects all pixels with weight under 10% of the max.

`threshold` is ignored when `colorDomain` is specified.

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

* 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 highest color in `colorRange`.

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

NOTES:
- It is recommend to use default value for regular use cases.
- On `Windows` browsers due to an ANGLE [issue](https://github.com/uber/deck.gl/issues/3554), auto calculation of maximum weight doesn't work, hence on `Windows`, `colorDomain` should be used with a non zero maximum value.

### Data Accessors

##### `getPosition` ([Function](/docs/developer-guide/using-layers.md#accessors), optional)
Expand Down
1 change: 1 addition & 0 deletions docs/whats-new.md
Expand Up @@ -69,6 +69,7 @@ A new prop [getTooltip](/docs/api-reference/deck.md#gettooltip-function-optional
- **Customizable device pixel ratio**: `Deck`'s `useDevicePixels` prop now accepts a number as well as boolean values.
- **SimpleMeshLayer** and **ScenegraphLayer** now respect the `opacity` prop.
- **IconLayer** has added a new prop `alphaCutoff` for customizing picking behavior.
- **HeatmapLayer** is out of `Experimental` phase and can now be rendered using `WebGL1` context. A new prop `colorDomain` added for custom domain specification.


## deck.gl v7.2
Expand Down
11 changes: 9 additions & 2 deletions examples/website/heatmap/app.js
Expand Up @@ -20,7 +20,13 @@ const INITIAL_VIEW_STATE = {

export class App extends PureComponent {
_renderLayers() {
const {data = DATA_URL, intensity = 1, threshold = 0.03, radiusPixels = 30} = this.props;
const {
data = DATA_URL,
intensity = 1,
threshold = 0.03,
radiusPixels = 30,
colorDomain
} = this.props;

return [
new HeatmapLayer({
Expand All @@ -32,7 +38,8 @@ export class App extends PureComponent {
getWeight: d => d[2],
radiusPixels,
intensity,
threshold
threshold,
colorDomain
})
];
}
Expand Down
21 changes: 18 additions & 3 deletions modules/aggregation-layers/src/heatmap-layer/heatmap-layer.js
Expand Up @@ -57,14 +57,16 @@ const TEXTURE_OPTIONS = {
},
dataFormat: GL.RGBA
};
const DEFAULT_COLOR_DOMAIN = [0, 0];

const defaultProps = {
getPosition: {type: 'accessor', value: x => x.position},
getWeight: {type: 'accessor', value: 1},
intensity: {type: 'number', min: 0, value: 1},
radiusPixels: {type: 'number', min: 1, max: 100, value: 30},
colorRange: defaultColorRange,
threshold: {type: 'number', min: 0, max: 1, value: 0.05}
threshold: {type: 'number', min: 0, max: 1, value: 0.05},
colorDomain: {type: 'array', value: null, optional: true}
};

const REQUIRED_FEATURES = [
Expand Down Expand Up @@ -92,6 +94,7 @@ export default class HeatmapLayer extends CompositeLayer {
return changeFlags.somethingChanged;
}

/* eslint-disable complexity */
updateState(opts) {
if (!this.state.supported) {
return;
Expand All @@ -118,8 +121,18 @@ export default class HeatmapLayer extends CompositeLayer {
this._updateTextureRenderingBounds();
}

if (oldProps.colorDomain !== props.colorDomain || changeFlags.viewportChanged) {
const {viewport} = this.context;
const domainScale = viewport ? 1024 / viewport.scale : 1;
const colorDomain = props.colorDomain
? props.colorDomain.map(x => x * domainScale)
: DEFAULT_COLOR_DOMAIN;
this.setState({colorDomain});
}

this.setState({zoom: opts.context.viewport.zoom});
}
/* eslint-enable complexity */

renderLayers() {
if (!this.state.supported) {
Expand All @@ -130,7 +143,8 @@ export default class HeatmapLayer extends CompositeLayer {
triPositionBuffer,
triTexCoordBuffer,
maxWeightsTexture,
colorTexture
colorTexture,
colorDomain
} = this.state;
const {updateTriggers, intensity, threshold} = this.props;

Expand All @@ -152,7 +166,8 @@ export default class HeatmapLayer extends CompositeLayer {
colorTexture,
texture: weightsTexture,
intensity,
threshold
threshold,
colorDomain
}
);
}
Expand Down
Expand Up @@ -27,23 +27,25 @@ uniform float opacity;
uniform sampler2D texture;
varying vec2 vTexCoords;
uniform sampler2D colorTexture;
uniform float threshold;
varying float vIntensity;
varying float vIntensityMin;
varying float vIntensityMax;
vec4 getLinearColor(float value) {
float factor = clamp(value, 0., 1.);
float factor = clamp(value * vIntensityMax, 0., 1.);
vec4 color = texture2D(colorTexture, vec2(factor, 0.5));
color.a *= min(value / threshold, 1.0);
color.a *= min(value * vIntensityMin, 1.0);
return color;
}
void main(void) {
float weight = texture2D(texture, vTexCoords).r;
if (weight == 0.) {
// discard pixels with 0 weight.
if (weight <= 0.) {
discard;
}
vec4 linearColor = getLinearColor(weight * vIntensity);
vec4 linearColor = getLinearColor(weight);
linearColor.a *= opacity;
gl_FragColor =linearColor;
}
Expand Down
Expand Up @@ -25,17 +25,27 @@ export default `\
uniform sampler2D maxTexture;
uniform float intensity;
uniform vec2 colorDomain;
uniform float threshold;
attribute vec3 positions;
attribute vec2 texCoords;
varying vec2 vTexCoords;
varying float vIntensity;
varying float vIntensityMin;
varying float vIntensityMax;
void main(void) {
gl_Position = project_position_to_clipspace(positions, vec2(0.0), vec3(0.0));
vTexCoords = texCoords;
float maxValue = texture2D(maxTexture, vec2(0.5)).r;
vIntensity = intensity / maxValue;
float minValue = maxValue * threshold;
if (colorDomain[1] > 0.) {
// if user specified custom domain use it.
maxValue = colorDomain[1];
minValue = colorDomain[0];
}
vIntensityMax = intensity / maxValue;
vIntensityMin = intensity / minValue;
}
`;
13 changes: 11 additions & 2 deletions modules/aggregation-layers/src/heatmap-layer/triangle-layer.js
Expand Up @@ -63,9 +63,18 @@ export default class TriangleLayer extends Layer {

draw({uniforms}) {
const {model} = this.state;
const {texture, maxTexture, colorTexture, intensity, threshold} = this.props;

const {texture, maxTexture, colorTexture, intensity, threshold, colorDomain} = this.props;
model
.setUniforms({...uniforms, texture, maxTexture, colorTexture, intensity, threshold})
.setUniforms({
...uniforms,
texture,
maxTexture,
colorTexture,
intensity,
threshold,
colorDomain
})
.draw();
}
}
Expand Down
20 changes: 19 additions & 1 deletion website/src/components/demos/heatmap-demo.js
Expand Up @@ -15,7 +15,23 @@ export default class HeatmapDemo extends Component {
return {
radius: {displayName: 'Radius', type: 'range', value: 5, step: 1, min: 1, max: 50},
intensity: {displayName: 'Intensity', type: 'range', value: 1, step: 0.1, min: 0, max: 5},
threshold: {displayName: 'Threshold', type: 'range', value: 0.03, step: 0.01, min: 0, max: 1}
threshold: {displayName: 'Threshold', type: 'range', value: 0.03, step: 0.01, min: 0, max: 1},
minWeight: {
displayName: 'Minimum Weight',
type: 'range',
value: 0,
step: 10,
min: 0,
max: 50000
},
maxWeight: {
displayName: 'Maximum Weight',
type: 'range',
value: 0,
step: 10,
min: 0,
max: 50000
}
};
}

Expand Down Expand Up @@ -54,6 +70,7 @@ export default class HeatmapDemo extends Component {
const radiusPixels = params.radius.value;
const intensity = params.intensity.value;
const threshold = params.threshold.value;
const colorDomain = [params.minWeight.value, params.maxWeight.value];

return (
<App
Expand All @@ -62,6 +79,7 @@ export default class HeatmapDemo extends Component {
intensity={intensity}
threshold={threshold}
radiusPixels={radiusPixels}
colorDomain={colorDomain}
/>
);
}
Expand Down

0 comments on commit 68c1574

Please sign in to comment.