diff --git a/docs/api-reference/aggregation-layers/aggregation-layer.md b/docs/api-reference/aggregation-layers/aggregation-layer.md index 0f0961aefca..4748221b5b6 100644 --- a/docs/api-reference/aggregation-layers/aggregation-layer.md +++ b/docs/api-reference/aggregation-layers/aggregation-layer.md @@ -1,54 +1,38 @@ -# AggregationLayer +# AggregationLayer (Experimental) -All of the layers in `@deck.gl/aggregation-layers` module perform some sort of data aggregation. All these layers perform aggregation with different parameters (CPU vs GPU, aggregation to rectangular bins vs hexagon bins, world space vs screen space, aggregation of single weight vs multiple weights etc). +`AggregationLayer` is the base class for all layers in `@deck.gl/aggregation-layers` module. It implements the most common tasks for aggregation with flexibility of customizations. -`AggregationLayer` and `GridAggregationLayer` perform most of the common tasks for aggregation with flexibility of customizations. This document describes what `AggregationLayer` does and how to use it in other aggregation layers. +`AggregationLayer` extends [CompositeLayer](../core/composite-layer.md). +## Methods -`AggregationLayer` is subclassed form `CompositeLayer` and all layers in `@deck.gl/aggregation-layers` are subclassed from this Layer. +Any layer subclassing the `AggregationLayer` must implement the following methods: -## Integration with `AttributeManager` +#### `getAggregatorType` {#getaggregatortype} -This layer creates `AttributeManager` and makes it available for its subclasses. Any aggregation layer can add attributes to the `AttributeManager` and retrieve them using `getAttributes` method. This enables using `AttributeManager`'s features and optimization for using attributes. Also manual iteration of `data` prop can be removed and attributes can be directly set on GPU aggregation models or accessed directly for CPU aggregation. +Returns a string that indicates the type of aggregator that this layer uses, for example `'gpu'`. The aggregator type is re-evaluated every time the layer updates (usually due to props or state change). If the type string does not match its previous value, any existing aggregator will be disposed,and `createAggregator` is called to create a new instance. -Example: Adding attributes to an aggregation layer +#### `createAggregator` {#createaggregator} -``` -const attributeManager = this.getAttributeManager(); -attributeManager.add({ - positions: {size: 3, accessor: 'getPosition'}, - color: {size: 3, accessor: 'getColorWeight'}, - elevation: {size: 3, accessor: 'getElevationWeight'} -}); -``` +Arguments: +- `type` (string) - return value from `getAggregatorType()` -## updateState() +Returns a [Aggregator](./aggregator.md) instance. This instance will be accessible via `this.state.aggregator`. -During update state, Subclasses of `AggregationLayer` must first call 'super.updateState()', which calls +#### `onAttributeChange` {#onattributechange} -- `updateShaders(shaders)` : Subclasses can override this if they need to update shaders, for example, when performing GPU aggregation, aggregation shaders must be merged with argument of this function to correctly apply `extensions`. +Arguments: +- `attributeId` (string) - the id of an attribute that has been updated -- `_updateAttributes`: This checks and updates attributes based on updated props. +This event handler should be used to update the props of the aggregator, if needed, and call `aggregator.setNeedsUpdate` to request an update. -## Checking if aggregation is dirty +#### `renderLayers` {#renderlayers} -### Dimensions +Returns a list of sub layers. -Typical aggregation, involves : -1. Group the input data points into bins -2. Compute the aggregated value for each bin +Aggregation results can be obtained here with `aggregator.getBins`, `aggregator.getResult` and `aggregator.getResultDomain`. -For example, when `cellSize` or `data` is changed, layer needs to perform both `1` and `2` steps, when a parameter affecting a bin's value is changed (like `getWeight` accessor), layer only need to perform step `2`. -When doing CPU Aggregation, both above steps are performed individually. But for GPU aggregation, both are merged into single render call. +## Source -To support what state is dirty, constructor takes `dimensions` object, which contains, several keyed dimensions. It must contain `data` dimension that defines, when re-aggregation needs to be performed. - -### isAggregationDirty() - -This helper can be used if a dimension is changed. Sublayers can defined custom dimensions and call this method to check if a dimension is changed. - - -### isAttributeChanged() - -`AggregationLayer` tracks what attributes are changed in each update cycle. Super classes can use `isAttributeChanged()` method to check if a specific attribute is changed or any attribute is changed. +[modules/aggregation-layers/src/common/aggregation-layer.ts](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/common/aggregation-layer.ts) diff --git a/docs/api-reference/aggregation-layers/aggregator.md b/docs/api-reference/aggregation-layers/aggregator.md new file mode 100644 index 00000000000..d91c442b6af --- /dev/null +++ b/docs/api-reference/aggregation-layers/aggregator.md @@ -0,0 +1,168 @@ +# Aggregator Interface + +The `Aggregator` interface describes a type of class that performs aggregation. + +## Terminology + +_Aggregation_ is a 2-step process: + +1. **Sort**: Group a collection of _data points_ by some property into _bins_. +2. **Aggregate**: for each _bin_, calculate a numeric output (_result_) from some metrics (_values_) from all its members. Multiple results can be obtained independently (_channels_). + +An implementation of the _Aggregator_ interface takes the following inputs: +- The number of data points +- The group that each data point belongs to, by mapping each data point to a _binId_ (array of integers) +- The values to aggregate, by mapping each data point in each channel to one _value_ (number) +- The method (_operation_) to reduce a list of values to one number, such as SUM + +And yields the following outputs: +- A list of _binIds_ that data points get sorted into +- The aggregated values (_result_) as a list of numbers, comprised of one number per bin per channel +- The [min, max] among all aggregated values (_domain_) for each channel + +### Example + +Consider the task of making a [histogram](https://en.wikipedia.org/wiki/Histogram) that shows the result of a survey by age distribution. + +1. The _data points_ are the list of participants, and we know the age of each person. +2. Suppose we want to group them by 5-year intervals. A 21-year-old participant is assigned to the bin of age 20-25, with _binId_ `[20]`. A 35-year-old participant is assigned to the bin of age 35-40, with _binId_ `[35]`, and so on. +3. For each bin (i.e. age group), we calculate 2 _values_: + + The first _channel_ is "number of participants". Each participant in this group yields a _value_ of 1, and the result equals all values added together (_operation_: SUM). + + The second _channel_ is "average score". Each participant in this group yields a _value_ that is their test score, and the result equals the sum of all scores divided by the number of participants (_operation_: MEAN). +4. As the outcome of the aggregation, we have: + + Bins: `[15, 20, 25, 30, 35, 40]` + + Channel 0 result: `[1, 5, 12, 10, 8, 3]` + + Channel 0 domain: `[1, 12]` + + Channel 1 result: `[6, 8.2, 8.5, 7.9, 7.75, 8]` + + Channel 1 domain: `[6, 8.5]` + + +## Methods + +An implementation of `Aggregator` should expose the following methods: + +#### `setProps` {#setprops} + +Set runtime properties of the aggregation. + +```ts +aggregator.setProps({ + pointCount: 10000, + attributes: {...}, + operations: ['SUM', 'MEAN'], + binOptions: {groupSize: 5} +}); +``` + +Arguments: +- `pointCount` (number) - number of data points. +- `attributes` ([Attribute](../core/attribute.md)[]) - the input data. +- `operations` (string[]) - How to aggregate the values inside a bin, defined per channel. +- `binOptions` (object) - arbitrary settings that affect bin sorting. +- `onUpdate` (Function) - callback when a channel has been recalculated. Receives the following arguments: + + `channel` (number) - the channel that just updated + +#### `setNeedsUpdate` {#setneedsupdate} + +Flags a channel to need update. This could be a result of change in the input data or bin options. + +```ts +aggregator.setNeedsUpdate(0); +``` + +Arguments: +- `channel` (number, optional) - mark the given channel as dirty. If not provided, all channels will be updated. + +#### `update` {#update} + +Called after all props are set and before results are accessed. The aggregator should allocate resources and redo aggregations if needed at this stage. + +```ts +aggregator.update(); +``` + +#### `preDraw` {#predraw} + +Called before the result buffers are drawn to screen. Certain types of aggregations are dependent on render time context and this is alternative opportunity to update just-in-time. + +```ts +aggregator.preDraw({moduleSettings: ...}); +``` + +#### `getBin` {#getbin} + +Get the information of a given bin. + +```ts +const bin = aggregator.getBin(100); +``` + +Arguments: +- `index` (number) - index of the bin to locate it in `getBins()` + +Returns: +- `id` (number[]) - Unique bin ID. +- `value` (number[]) - Aggregated values by channel. +- `count` (number) - Number of data points in this bin. +- `pointIndices` (number[] | undefined) - Indices of data points in this bin if available. This field may not be populated when using GPU-based implementations. + +#### `getBins` {#getbins} + +Get an accessor to all bin IDs. + +```ts +const binIdsAttribute = aggregator.getBins(); +``` + +Returns: +- A [binary attribute](../core/layer.md#dataattributes) of the output bin IDs, or +- null, if `update` has never been called + +#### `getResult` {#getresult} + +Get an accessor to the aggregated values of a given channel. + +```ts +const resultAttribute = aggregator.getResult(0); +``` + +Arguments: +- `channel` (number) - the channel to retrieve results from + +Returns: +- A [binary attribute](../core/layer.md#dataattributes) of the output values of the given channel, or +- null, if `update` has never been called + +#### `getResultDomain` {#getresultdomain} + +Get the [min, max] of aggregated values of a given channel. + +```ts +const [min, max] = aggregator.getResultDomain(0); +``` + +Arguments: +- `channel` (number) - the channel to retrieve results from + +Returns the domain ([number, number]) of the aggregated values of the given channel. + +#### `destroy` {#destroy} + +Dispose all allocated resources. + +```ts +aggregator.destroy(); +``` + + +## Members + +An implementation of `Aggregator` should expose the following members: + +#### `binCount` (number) {#bincount} + +The number of bins in the aggregated result. + +## Source + +[modules/aggregation-layers/src/common/aggregator/aggregator.ts](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/common/aggregator/aggregator.ts) diff --git a/docs/api-reference/aggregation-layers/contour-layer.md b/docs/api-reference/aggregation-layers/contour-layer.md index 86dcaf8e199..2aef59b9697 100644 --- a/docs/api-reference/aggregation-layers/contour-layer.md +++ b/docs/api-reference/aggregation-layers/contour-layer.md @@ -147,7 +147,7 @@ npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers ```ts import {ContourLayer} from '@deck.gl/aggregation-layers'; -import type {ContourLayerProps} from '@deck.gl/aggregation-layers'; +import type {ContourLayerProps, ContourLayerPickingInfo} from '@deck.gl/aggregation-layers'; new ContourLayer(...props: ContourLayerProps[]); ``` @@ -171,7 +171,7 @@ new deck.ContourLayer({}); Inherits from all [Base Layer](../core/layer.md) properties. -### Render Options +### Aggregation Options #### `cellSize` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellsize} @@ -183,19 +183,27 @@ Size of each cell in meters * Default: true -When set to true and browser supports GPU aggregation, aggregation is performed on GPU. GPU aggregation can be 2 to 3 times faster depending upon number of points and number of cells. +When set to `true` and the browser supports it, aggregation is performed on GPU. + +In the right context, enabling GPU aggregation can significantly speed up your application. However, depending on the nature of input data and required application features, there are pros and cons in leveraging this functionality. See [CPU vs GPU aggregation](./overview.md#cpu-vs-gpu-aggregation) for an in-depth discussion. + #### `aggregation` (string, optional) {#aggregation} -* Default: 'SUM' +* Default: `'SUM'` + +Defines the operation used to aggregate all data object weights to calculate a cell's value. Valid values are: + +- `'SUM'`: The sum of weights across all points that fall into a cell. +- `'MEAN'`: The mean weight across all points that fall into a cell. +- `'MIN'`: The minimum weight across all points that fall into a cell. +- `'MAX'`: The maximum weight across all points that fall into a cell. +- `'COUNT'`: The number of points that fall into a cell. -Defines the type of aggregation operation, valid values are 'SUM', 'MEAN', 'MIN' and 'MAX'. When no value or an invalid value is set, 'SUM' is used as aggregation. +`getWeight` and `aggregation` together determine the elevation value of each cell. -* SUM : Grid cell contains sum of all weights that fall into it. -* MEAN : Grid cell contains mean of all weights that fall into it. -* MIN : Grid cell contains minimum of all weights that fall into it. -* MAX : Grid cell contains maximum of all weights that fall into it. +### Render Options #### `contours` (object[], optional) {#contours} @@ -240,11 +248,18 @@ The weight of each object. * If a function is provided, it is called on each object to retrieve its weight. +## Picking + +The [PickingInfo.object](../../developer-guide/interactivity.md#the-pickinginfo-object) field returned by hover/click events of this layer represents a path (isoline) or a polygon (isoband). The object contains the following fields: + +- `contour` (object) - one of the contour configurations passed to the `contours` prop. + + ## Sub Layers The `ContourLayer` renders the following sublayers: -* `lines` - For Isolines, rendered by [LineLayer](../layers/line-layer.md) +* `lines` - For Isolines, rendered by [PathLayer](../layers/path-layer.md) * `bands` - For Isobands, rendered by [SolidPolygonLayer](../layers/solid-polygon-layer.md) diff --git a/docs/api-reference/aggregation-layers/cpu-aggregator.md b/docs/api-reference/aggregation-layers/cpu-aggregator.md new file mode 100644 index 00000000000..a2ff9c27a0e --- /dev/null +++ b/docs/api-reference/aggregation-layers/cpu-aggregator.md @@ -0,0 +1,92 @@ +# CPUAggregator + +The `CPUAggregator` implements the [Aggregator](./aggregator.md) interface by performing aggregation on the CPU. + +## Example + +This example implements an aggregator that makes a [histogram](https://en.wikipedia.org/wiki/Histogram) that calculates "weight" distribution by "position". + +```ts +import {CPUAggregator} from '@deck.gl/aggregation-layers'; + +const aggregator = new CPUAggregator({ + dimensions: 1, + getBin: { + sources: ['position'], + getValue: (data: {position: number}, index: number, options: {binSize: number}) => + [Math.floor(data.position / options.binSize)] + }, + getValue: [ + { + sources: ['weight'], + getValue: (data: {weight: number}) => data.weight + } + ] +}); + +const position = new Attribute(device, {id: 'position', size: 1}); +position.setData({value: new Float32Array(...)}); +const weight = new Attribute(device, {id: 'weight', size: 1}); +position.setData({value: new Float32Array(...)}); + +aggregator.setProps({ + pointCount: data.length, + operations: ['SUM'], + binOptions: { + binSize: 1 + }, + attributes: {position, weight} +}); + +aggregator.update(); +``` + +## Constructor + +```ts +new CPUAggregator(props); +``` + +Arguments: + +- `dimensions` (number) - size of bin IDs, either 1 or 2 +- `getBin` (VertexAccessor) - accessor to map each data point to a bin ID + + `sources` (string[]) - attribute names needed for the calculation + + `getValue` (`(data: object, index: number, options: object) => number[] | null`) - callback to retrieve the bin ID for each data point. + Bin ID should be an array with [dimensions] elements; or null if the data point should be skipped +- `getValue` (VertexAccessor[]) - accessor to map each data point to a weight value, defined per channel. Each accsor should contain these fields: + + `sources` (string[]) - attribute names needed for the calculation + + `getValue` (`(data: object, index: number, options: object) => number`) - callback to retrieve the value for each data point. + +## Props + +Requires all [Aggregator](./aggregator.md#setprops) props, and the following: + +#### `customOperations` (Function[]) {#customoperations} + +Overrides built-in aggregation operation with a custom reducer. +Each element can optionally be a callback with the following signature: + +```ts +(pointIndices: number[], getValue: (index: number) => number) => number; +``` + +If a custom operation is defined, the corresponding element in the `operations` array will be ignored. + +Example to calculate the median for channel 1: + +```ts +function median(pointIndices: number[], getValue: (index: number) => number) { + const values = pointIndices.map(getValue); + values.sort((a, b) => a - b); + return values[values.length >> 1]; +} + +aggregator.setProps({ + customOperations: [null, median, null] +}); +``` + +## Source + +[modules/aggregation-layers/src/common/aggregator/cpu-aggregator/cpu-aggregator.ts](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/common/aggregator/cpu-aggregator/cpu-aggregator.ts) diff --git a/docs/api-reference/aggregation-layers/cpu-grid-layer.md b/docs/api-reference/aggregation-layers/cpu-grid-layer.md deleted file mode 100644 index 132a5d1b1cb..00000000000 --- a/docs/api-reference/aggregation-layers/cpu-grid-layer.md +++ /dev/null @@ -1,447 +0,0 @@ -# CPUGridLayer - -import {CPUGridLayerDemo} from '@site/src/doc-demos/aggregation-layers'; - - - -The `CPUGridLayer` aggregates data into a grid-based heatmap. The color and height of a cell are determined based on the objects it contains. Aggregation is performed on CPU. - -`CPUGridLayer` is one of the sublayers for [GridLayer](./grid-layer.md), and is provided to customize CPU Aggregation for advanced use cases. For any regular use case, [GridLayer](./grid-layer.md) is recommended. - -`CPUGridLayer` is a [CompositeLayer](../core/composite-layer.md). - - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - -```js -import {Deck} from '@deck.gl/core'; -import {CPUGridLayer} from '@deck.gl/aggregation-layers'; - -const layer = new CPUGridLayer({ - id: 'CPUGridLayer', - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', - - extruded: true, - getPosition: d => d.COORDINATES, - getColorWeight: d => d.SPACES, - getElevationWeight: d => d.SPACES, - elevationScale: 4, - cellSize: 200, - pickable: true -}); - -new Deck({ - initialViewState: { - longitude: -122.4, - latitude: 37.74, - zoom: 11 - }, - controller: true, - getTooltip: ({object}) => object && `Count: ${object.elevationValue}`, - layers: [layer] -}); -``` - - - - -```ts -import {Deck, PickingInfo} from '@deck.gl/core'; -import {CPUGridLayer} from '@deck.gl/aggregation-layers'; - -type BikeRack = { - ADDRESS: string; - SPACES: number; - COORDINATES: [longitude: number, latitude: number]; -}; - -const layer = new CPUGridLayer({ - id: 'CPUGridLayer', - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', - - extruded: true, - getPosition: (d: BikeRack) => d.COORDINATES, - getColorWeight: (d: BikeRack) => d.SPACES, - getElevationWeight: (d: BikeRack) => d.SPACES, - elevationScale: 4, - cellSize: 200, - pickable: true -}); - -new Deck({ - initialViewState: { - longitude: -122.4, - latitude: 37.74, - zoom: 11 - }, - controller: true, - getTooltip: ({object}: PickingInfo) => object && `Count: ${object.elevationValue}`, - layers: [layer] -}); -``` - - - - -```tsx -import React from 'react'; -import DeckGL from '@deck.gl/react'; -import {CPUGridLayer} from '@deck.gl/aggregation-layers'; -import type {PickingInfo} from '@deck.gl/core'; - -type BikeRack = { - ADDRESS: string; - SPACES: number; - COORDINATES: [longitude: number, latitude: number]; -}; - -function App() { - const layer = new CPUGridLayer({ - id: 'CPUGridLayer', - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', - - extruded: true, - getPosition: (d: BikeRack) => d.COORDINATES, - getColorWeight: (d: BikeRack) => d.SPACES, - getElevationWeight: (d: BikeRack) => d.SPACES, - elevationScale: 4, - cellSize: 200, - pickable: true - }); - - return ) => object && `Count: ${object.elevationValue}`} - layers={[layer]} - />; -} -``` - - - - -**Note:** The `CPUGridLayer` at the moment only works with `COORDINATE_SYSTEM.LNGLAT`. - - -## Installation - -To install the dependencies from NPM: - -```bash -npm install deck.gl -# or -npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers -``` - -```ts -import {CPUGridLayer} from '@deck.gl/aggregation-layers'; -import type {CPUGridLayerProps} from '@deck.gl/aggregation-layers'; - -new CPUGridLayer(...props: CPUGridLayerProps[]); -``` - -To use pre-bundled scripts: - -```html - - - - - -``` - -```js -new deck.CPUGridLayer({}); -``` - - -## Properties - -Inherits from all [Base Layer](../core/layer.md) and [CompositeLayer](../core/composite-layer.md) properties. - -### Render Options - -#### `cellSize` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellsize} - -* Default: `1000` - -Size of each cell in meters - -#### `colorDomain` (number[2], optional) {#colordomain} - -* Default: `[min(colorWeight), max(colorWeight)]` - -Color scale domain, default is set to the extent of aggregated weights in each cell. -You can control how the colors of cells are mapped to weights by passing in an arbitrary color domain. -This is useful when you want to render different data input with the same color mapping for comparison. - -#### `colorRange` (Color[], optional) {#colorrange} - -* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` - -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. - -`colorDomain` is divided into `colorRange.length` equal segments, each mapped to one color in `colorRange`. - -#### `coverage` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#coverage} - -* Default: `1` - -Cell size multiplier, clamped between 0 - 1. The displayed size of cell is calculated by `coverage * cellSize`. -Note: coverage does not affect how objects are binned. - -#### `elevationDomain` (number[2], optional) {#elevationdomain} - -* Default: `[0, max(elevationWeight)]` - -Elevation scale input domain, default is set to between 0 and the max of aggregated weights in each cell. -You can control how the elevations of cells are mapped to weights by passing in an arbitrary elevation domain. -This is useful when you want to render different data input with the same elevation scale for comparison. - -#### `elevationRange` (number[2], optional) {#elevationrange} - -* Default: `[0, 1000]` - -Elevation scale output range - -#### `elevationScale` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationscale} - -* Default: `1` - -Cell elevation multiplier. -This is a handy property to scale all cells without updating the data. - -#### `extruded` (boolean, optional) {#extruded} - -* Default: `true` - -Whether to enable cell elevation. If set to false, all cell will be flat. - -#### `upperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#upperpercentile} - -* Default: `100` - -Filter cells and re-calculate color by `upperPercentile`. Cells with value -larger than the upperPercentile will be hidden. - -#### `lowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#lowerpercentile} - -* Default: `0` - -Filter cells and re-calculate color by `lowerPercentile`. Cells with value -smaller than the lowerPercentile will be hidden. - -#### `elevationUpperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationupperpercentile} - -* Default: `100` - -Filter cells and re-calculate elevation by `elevationUpperPercentile`. Cells with elevation value -larger than the elevationUpperPercentile will be hidden. - -#### `elevationLowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationlowerpercentile} - -* Default: `0` - -Filter cells and re-calculate elevation by `elevationLowerPercentile`. Cells with elevation value -smaller than the elevationLowerPercentile will be hidden. - -#### `colorScaleType` (string, optional) {#colorscaletype} - -* Default: 'quantize' - -Scaling function used to determine the color of the grid cell, default value is 'quantize'. Supported Values are 'quantize', 'linear', 'quantile' and 'ordinal'. - -#### `material` (Material, optional) {#material} - -* Default: `true` - -This is an object that contains material props for [lighting effect](../core/lighting-effect.md) applied on extruded polygons. -Check [the lighting guide](../../developer-guide/using-effects.md#material-settings) for configurable settings. - - -#### `colorAggregation` (string, optional) {#coloraggregation} - -* Default: `'SUM'` - -Defines the operation used to aggregate all data object weights to calculate a bin's color value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. - -`getColorWeight` and `colorAggregation` together determine the elevation value of each cell. If the `getColorValue` prop is supplied, they will be ignored. - -##### Example: Color by the count of data elements - -```ts title="Option A: use getColorValue" -const layer = new CPUGridLayer({ - // ... - getColorValue: (points: BikeRack[]) => points.length, -}); -``` - -```ts title="Option B: use getColorWeight and colorAggregation" -const layer = new CPUGridLayer({ - // ... - getColorWeight: (d: BikeRack) => 1, - colorAggregation: 'SUM' -}); -``` - -##### Example: Color by the mean value of 'SPACES' field - -```ts title="Option A: use getColorValue" -const layer = new CPUGridLayer({ - // ... - getColorValue: (points: BikeRack[]) => { - // Calculate mean value - return points.reduce((sum: number, p: BikeRack) => sum += p.SPACES, 0) / points.length; - } -}); -``` - -```ts title="Option B: use getColorWeight and colorAggregation" -const layer = new CPUGridLayer({ - // ... - getColorWeight: (point: BikeRack) => point.SPACES, - colorAggregation: 'SUM' -}); -``` - -If your use case requires aggregating using an operation that is not one of 'SUM', 'MEAN', 'MAX' and 'MIN', `getColorValue` should be used to define such custom aggregation function. - - -#### `elevationAggregation` (string, optional) {#elevationaggregation} - -* Default: `'SUM'` - -Defines the operation used to aggregate all data object weights to calculate a bin's elevation value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. - -`getElevationWeight` and `elevationAggregation` together determine the elevation value of each cell. If the `getElevationValue` prop is supplied, they will be ignored. - -##### Example: Elevation by the count of data elements - -```ts title="Option A: use getElevationValue" -const layer = new CPUGridLayer({ - // ... - getElevationValue: (points: BikeRack[]) => points.length -}); -``` - -```ts title="Option B: use getElevationWeight and elevationAggregation" -const layer = new CPUGridLayer({ - // ... - getElevationWeight: (point: BikeRack) => 1, - elevationAggregation: 'SUM' -}); -``` - -##### Example: Elevation by the maximum value of 'SPACES' field - -```ts title="Option A: use getElevationValue" -const layer = new CPUGridLayer({ - // ... - getElevationValue: (points: BikeRack[]) => { - // Calculate max value - return points.reduce((max: number, p: BikeRack) => p.SPACES > max ? p.SPACES : max, -Infinity); - } -}); -``` - -```ts title="Option B: use getElevationWeight and elevationAggregation" -const layer = new CPUGridLayer({ - // ... - getElevationWeight: (point: BikeRack) => point.SPACES, - elevationAggregation: 'MAX' -}); -``` - -If your use case requires aggregating using an operation that is not one of 'SUM', 'MEAN', 'MAX' and 'MIN', `getElevationValue` should be used to define such custom aggregation function. - - -### Data Accessors - -#### `getPosition` ([Accessor<Position>](../../developer-guide/using-layers.md#accessors), optional) {#getposition} - -* Default: `object => object.position` - -Method called to retrieve the position of each object. - - -#### `getColorWeight` ([Accessor<number>](../../developer-guide/using-layers.md#accessors), optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getcolorweight} - -* Default: `1` - -The weight of a data object used to calculate the color value for a cell. - -* If a number is provided, it is used as the weight for all objects. -* If a function is provided, it is called on each object to retrieve its weight. - - -#### `getColorValue` (Function, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getcolorvalue} - -* Default: `null` - -After data objects are aggregated into cells, this accessor is called on each cell to get the value that its color is based on. If supplied, this will override the effect of `getColorWeight` and `colorAggregation` props. - -Arguments: - -- `objects` (DataT[]) - a list of objects whose positions fall inside this cell. -- `objectInfo` (object) - contains the following fields: - + `indices` (number[]) - the indices of `objects` in the original data - + `data` - the value of the `data` prop. - - -#### `getElevationWeight` ([Accessor<number>](../../developer-guide/using-layers.md#accessors), optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getelevationweight} - -* Default: `1` - -The weight of a data object used to calculate the elevation value for a cell. - -* If a number is provided, it is used as the weight for all objects. -* If a function is provided, it is called on each object to retrieve its weight. - - -#### `getElevationValue` (Function, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getelevationvalue} - -* Default: `null` - -After data objects are aggregated into cells, this accessor is called on each cell to get the value that its elevation is based on. If supplied, this will override the effect of `getElevationWeight` and `elevationAggregation` props. - -Arguments: - -- `objects` (DataT[]) - a list of objects whose positions fall inside this cell. -- `objectInfo` (object) - contains the following fields: - + `indices` (number[]) - the indices of `objects` in the original data - + `data` - the value of the `data` prop. - - -### Callbacks - -#### `onSetColorDomain` (Function, optional) {#onsetcolordomain} - -* Default: `([min, max]) => {}` - -This callback will be called when cell color domain has been calculated. - -#### `onSetElevationDomain` (Function, optional) {#onsetelevationdomain} - -* Default: `([min, max]) => {}` - -This callback will be called when cell elevation domain has been calculated. - - -## Sub Layers - -The CPUGridLayer renders the following sublayers: - -* `grid-cell` - a [GridCellLayer](../layers/grid-cell-layer.md) rendering the aggregated columns. - -## Source - -[modules/aggregation-layers/src/cpu-grid-layer](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/cpu-grid-layer) diff --git a/docs/api-reference/aggregation-layers/gpu-grid-layer.md b/docs/api-reference/aggregation-layers/gpu-grid-layer.md deleted file mode 100644 index 080f2a96dac..00000000000 --- a/docs/api-reference/aggregation-layers/gpu-grid-layer.md +++ /dev/null @@ -1,308 +0,0 @@ -# GPUGridLayer - -import {GPUGridLayerDemo} from '@site/src/doc-demos/aggregation-layers'; - - - -The `GPUGridLayer` aggregates data into a grid-based heatmap. The color and height of a cell are determined based on the objects it contains. - -`GPUGridLayer` is one of the sublayers for [GridLayer](./grid-layer.md). It is provided to customize GPU Aggregation for advanced use cases. For any regular use case, [GridLayer](./grid-layer.md) is recommended. - -`GPUGridLayer` is a [CompositeLayer](../core/composite-layer.md). - - - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - -```js -import {Deck} from '@deck.gl/core'; -import {GPUGridLayer} from '@deck.gl/aggregation-layers'; - -const layer = new GPUGridLayer({ - id: 'GPUGridLayer', - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', - - extruded: true, - getPosition: d => d.COORDINATES, - getColorWeight: d => d.SPACES, - getElevationWeight: d => d.SPACES, - elevationScale: 4, - cellSize: 200, - pickable: true -}); - -new Deck({ - initialViewState: { - longitude: -122.4, - latitude: 37.74, - zoom: 11 - }, - controller: true, - getTooltip: ({object}) => object && `Count: ${object.elevationValue}`, - layers: [layer] -}); -``` - - - - -```ts -import {Deck, PickingInfo} from '@deck.gl/core'; -import {GPUGridLayer} from '@deck.gl/aggregation-layers'; - -type BikeRack = { - ADDRESS: string; - SPACES: number; - COORDINATES: [longitude: number, latitude: number]; -}; - -const layer = new GPUGridLayer({ - id: 'GPUGridLayer', - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', - - extruded: true, - getPosition: (d: BikeRack) => d.COORDINATES, - getColorWeight: (d: BikeRack) => d.SPACES, - getElevationWeight: (d: BikeRack) => d.SPACES, - elevationScale: 4, - cellSize: 200, - pickable: true -}); - -new Deck({ - initialViewState: { - longitude: -122.4, - latitude: 37.74, - zoom: 11 - }, - controller: true, - getTooltip: ({object}: PickingInfo) => object && `Count: ${object.elevationValue}`, - layers: [layer] -}); -``` - - - - -```tsx -import React from 'react'; -import DeckGL from '@deck.gl/react'; -import {GPUGridLayer} from '@deck.gl/aggregation-layers'; -import type {PickingInfo} from '@deck.gl/core'; - -type BikeRack = { - ADDRESS: string; - SPACES: number; - COORDINATES: [longitude: number, latitude: number]; -}; - -function App() { - const layer = new GPUGridLayer({ - id: 'GPUGridLayer', - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', - - extruded: true, - getPosition: (d: BikeRack) => d.COORDINATES, - getColorWeight: (d: BikeRack) => d.SPACES, - getElevationWeight: (d: BikeRack) => d.SPACES, - elevationScale: 4, - cellSize: 200, - pickable: true - }); - - return ) => object && `Count: ${object.elevationValue}`} - layers={[layer]} - />; -} -``` - - - - - -**Note:** The `GPUGridLayer` at the moment only works with `COORDINATE_SYSTEM.LNGLAT`. - -**Note:** GPU Aggregation is faster only when using large data sets (data size is more than 500K), for smaller data sets GPU Aggregation could be potentially slower than CPU Aggregation. - -**Note:** This layer is similar to [CPUGridLayer](./cpu-grid-layer.md) but performs aggregation on GPU. Check below for more detailed differences of this layer compared to `CPUGridLayer`. - - -## Installation - -To install the dependencies from NPM: - -```bash -npm install deck.gl -# or -npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers -``` - -```ts -import {GPUGridLayer} from '@deck.gl/aggregation-layers'; -import type {GPUGridLayerProps} from '@deck.gl/aggregation-layers'; - -new GPUGridLayer(...props: GPUGridLayerProps[]); -``` - -To use pre-bundled scripts: - -```html - - - - - -``` - -```js -new deck._GPUGridLayer({}); -``` - - -## Properties - -Inherits from all [Base Layer](../core/layer.md) and [CompositeLayer](../core/composite-layer.md) properties. - -### Render Options - -#### `cellSize` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellsize} - -* Default: `1000` - -Size of each cell in meters. Must be greater than `0`. - -#### `colorDomain` (number[2], optional) {#colordomain} - -* Default: `[min(colorWeight), max(colorWeight)]` - -Color scale domain, default is set to the extent of aggregated weights in each cell. -You can control how the colors of cells are mapped to weights by passing in an arbitrary color domain. -This is useful when you want to render different data input with the same color mapping for comparison. - - -#### `colorRange` (Color[], optional) {#colorrange} - -* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` - -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. - -`colorDomain` is divided into `colorRange.length` equal segments, each mapped to one color in `colorRange`. - -#### `coverage` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#coverage} - -* Default: `1` - -Cell size multiplier, clamped between 0 - 1. The displayed size of cell is calculated by `coverage * cellSize`. -Note: coverage does not affect how objects are binned. - -#### `elevationDomain` (number[2], optional) {#elevationdomain} - -* Default: `[0, max(elevationWeight)]` - -Elevation scale input domain, default is set to between 0 and the max of aggregated weights in each cell. -You can control how the elevations of cells are mapped to weights by passing in an arbitrary elevation domain. -This is useful when you want to render different data input with the same elevation scale for comparison. - -#### `elevationRange` (number[2], optional) {#elevationrange} - -* Default: `[0, 1000]` - -Elevation scale output range - -#### `elevationScale` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationscale} - -* Default: `1` - -Cell elevation multiplier. -This is a handy property to scale the height of all cells without updating the data. - -#### `extruded` (boolean, optional) {#extruded} - -* Default: `true` - -Whether to enable cell elevation. If set to false, all cell will be flat. - -#### `material` (Material, optional) {#material} - -* Default: `true` - -This is an object that contains material props for [lighting effect](../core/lighting-effect.md) applied on extruded polygons. -Check [the lighting guide](../../developer-guide/using-effects.md#material-settings) for configurable settings. - - - -#### `colorAggregation` (string, optional) {#coloraggregation} - -* Default: `'SUM'` - -Defines the operation used to aggregate all data object weights to calculate a bin's color value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. - -`getColorWeight` and `colorAggregation` together determine the elevation value of each cell. - -#### `elevationAggregation` (string, optional) {#elevationaggregation} - -* Default: `'SUM'` - -Defines the operation used to aggregate all data object weights to calculate a bin's elevation value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. - -`getElevationWeight` and `elevationAggregation` together determine the elevation value of each cell. - - -### Data Accessors - -#### `getPosition` ([Accessor<Position>](../../developer-guide/using-layers.md#accessors), optional) {#getposition} - -* Default: `object => object.position` - -Method called to retrieve the position of each object. - - -#### `getColorWeight` ([Accessor<number>](../../developer-guide/using-layers.md#accessors), optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getcolorweight} - -* Default: `1` - -The weight of a data object used to calculate the color value for a cell. - -* If a number is provided, it is used as the weight for all objects. -* If a function is provided, it is called on each object to retrieve its weight. - - -#### `getElevationWeight` ([Accessor<number>](../../developer-guide/using-layers.md#accessors), optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getelevationweight} - -* Default: `1` - -The weight of a data object used to calculate the elevation value for a cell. - -* If a number is provided, it is used as the weight for all objects. -* If a function is provided, it is called on each object to retrieve its weight. - - -## Differences compared to CPUGridLayer - -### Unsupported props - -Due to the nature of GPU Aggregation implementation, the following `CPUGridLayer` props are not supported by this layer. - -`upperPercentile` `lowerPercentile` `elevationUpperPercentile`, `elevationLowerPercentile`, `getColorValue`, `getElevationValue`, `onSetColorDomain` and `onSetElevationDomain` - -Instead of `getColorValue`, `getColorWeight` and `colorAggregation` should be used. Instead of `getElevationValue`, `getElevationWeight` and `elevationAggregation` should be used. There is no alternate for all other unsupported props, if they are needed `CPUGridLayer` should be used instead of this layer. - -### Picking - -When picking mode is `hover`, only the elevation value, color value of selected cell are included in picking result. Array of all objects that aggregated into that cell is not provided. For all other modes, picking results match with `CPUGridLayer`, for these cases data is aggregated on CPU to provide array of all objects that aggregated to the cell. - - -## Source - -[modules/aggregation-layers/src/gpu-grid-layer](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/gpu-grid-layer) diff --git a/docs/api-reference/aggregation-layers/grid-aggregation-layer.md b/docs/api-reference/aggregation-layers/grid-aggregation-layer.md deleted file mode 100644 index 0f876f2090d..00000000000 --- a/docs/api-reference/aggregation-layers/grid-aggregation-layer.md +++ /dev/null @@ -1,46 +0,0 @@ -# GridAggregationLayer - -This layer performs some common tasks required to perform aggregation to grid cells, especially it takes care of deciding CPU vs GPU aggregation, allocating resources for GPU aggregation and uploading results. - -This in an abstract layer, subclassed form `AggregationLayer`, `GPUGridLayer`, `ScreenGridLayer` and `ContourLayer` are subclassed from this layer. - -## updateState() - -During `updateState()`, it calls `updateAggregationState()` which sub classes must implement. During this method, sub classes must set following aggregation flags and aggregation params. - -### Aggregation Flags - -* `gpuAggregation`: When `true` aggregation happens on GPU, otherwise on CPU. -* `aggregationDataDirty` : When `true` data is re-aggregated. -* `aggregationWeightsDirty` : This flag is applicable only for CPU aggregation. When `true`, bin's aggregated values are re computed. - -## Aggregation Parameters - -* `gridOffset` : Grid's cell size in the format {xOffset, yOffset}. -* `projectPoints` : Should be `true` when doing screen space aggregation, when `false` it implies world space aggregation. -* `attributes` : Layer's current set of attributes which provide position and weights to CPU/GPU aggregators. -* `viewport` : current viewport object. -* `posOffset` : Offset to be added to object's position before aggregating. -* `boundingBox` : Bounding box of the input data. -Following are applicable for GPU aggregation only: -* `translation` : [xTranslation, yTranslation], position translation to be applied on positions. -* `scaling` : [xScale, yScale, flag], scaling to be applied on positions. When scaling not needed `flag` should be set to `0`. -* `vertexCount` : Number of objects to be aggregated. -* `moduleSettings` : Object with set of fields required for applying shader modules. - - -## updateResults() - -When aggregation performed on CPU, aggregation result is in JS Array objects. Subclasses can override this method to consume aggregation data. This method is called with an object with following fields: - * `aggregationData` (*Float32Array*) - Array containing aggregation data per grid cell. Four elements per grid cell in the format `[value, 0, 0, count]`, where `value` is the aggregated weight value, up to 3 different weights. `count` is the number of objects aggregated to the grid cell. - * `maxMinData` (*Float32Array*) - Array with four values in format, `[maxValue, 0, 0, minValue]`, where `maxValue` is max of all aggregated cells. - * `maxData` (*Float32Array*) - Array with four values in format, `[maxValue, 0, 0, count]`, where `maxValue` is max of all aggregated cells and `count` is total number aggregated objects. - * `minData` (*Float32Array*) - Array with four values in format, `[minValue, 0, 0, count]`, where `minValue` is min of all aggregated cells and `count` is total number aggregated objects. - - NOTE: The goal is to match format of CPU aggregation results to that of GPU aggregation, so consumers of this data (Sublayers) don't have to change. - -## allocateResources() - -Called with following arguments to allocated resources required to hold aggregation results. - * `numRow` (number) - Number of rows in the grid. - * `numCol` (number) - Number of columns in the grid. diff --git a/docs/api-reference/aggregation-layers/grid-layer.md b/docs/api-reference/aggregation-layers/grid-layer.md index 3025d2c69e6..ba9aea839fb 100644 --- a/docs/api-reference/aggregation-layers/grid-layer.md +++ b/docs/api-reference/aggregation-layers/grid-layer.md @@ -4,9 +4,7 @@ import {GridLayerDemo} from '@site/src/doc-demos/aggregation-layers'; -The `GridLayer` aggregates data into a grid-based heatmap. The color and height of a cell are determined based on the objects it contains. - -This layer renders either a [GPUGridLayer](./gpu-grid-layer.md) or a [CPUGridLayer](./cpu-grid-layer.md), depending on its props and whether GPU aggregation is supported. For more details check the `GPU Aggregation` section below. +The `GridLayer` aggregates data into a grid-based heatmap. The color and height of a grid cell are determined based on the objects it contains. `GridLayer` is a [CompositeLayer](../core/composite-layer.md). @@ -25,6 +23,7 @@ const layer = new GridLayer({ id: 'GridLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, extruded: true, getPosition: d => d.COORDINATES, getColorWeight: d => d.SPACES, @@ -50,8 +49,8 @@ new Deck({ ```ts -import {Deck, PickingInfo} from '@deck.gl/core'; -import {GridLayer} from '@deck.gl/aggregation-layers'; +import {Deck} from '@deck.gl/core'; +import {GridLayer, GridLayerPickingInfo} from '@deck.gl/aggregation-layers'; type BikeRack = { ADDRESS: string; @@ -63,6 +62,7 @@ const layer = new GridLayer({ id: 'GridLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, extruded: true, getPosition: (d: BikeRack) => d.COORDINATES, getColorWeight: (d: BikeRack) => d.SPACES, @@ -79,7 +79,7 @@ new Deck({ zoom: 11 }, controller: true, - getTooltip: ({object}: PickingInfo) => object && `Count: ${object.elevationValue}`, + getTooltip: ({object}: GridLayerPickingInfo) => object && `Count: ${object.elevationValue}`, layers: [layer] }); ``` @@ -90,8 +90,7 @@ new Deck({ ```tsx import React from 'react'; import DeckGL from '@deck.gl/react'; -import {GridLayer} from '@deck.gl/aggregation-layers'; -import type {PickingInfo} from '@deck.gl/core'; +import {GridLayer, GridLayerPickingInfo} from '@deck.gl/aggregation-layers'; type BikeRack = { ADDRESS: string; @@ -104,6 +103,7 @@ function App() { id: 'GridLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, extruded: true, getPosition: (d: BikeRack) => d.COORDINATES, getColorWeight: (d: BikeRack) => d.SPACES, @@ -120,7 +120,7 @@ function App() { zoom: 11 }} controller - getTooltip={({object}: PickingInfo) => object && `Count: ${object.elevationValue}`} + getTooltip={({object}: GridLayerPickingInfo) => object && `Count: ${object.elevationValue}`} layers={[layer]} />; } @@ -129,8 +129,6 @@ function App() { -**Note:** The `GridLayer` at the moment only works with `COORDINATE_SYSTEM.LNGLAT`. - ## Installation @@ -144,7 +142,7 @@ npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers ```ts import {GridLayer} from '@deck.gl/aggregation-layers'; -import type {GridLayerProps} from '@deck.gl/aggregation-layers'; +import type {GridLayerProps, GridLayerPickingInfo} from '@deck.gl/aggregation-layers'; new GridLayer(...props: GridLayerProps[]); ``` @@ -168,149 +166,67 @@ new deck.GridLayer({}); Inherits from all [Base Layer](../core/layer.md) and [CompositeLayer](../core/composite-layer.md) properties. -### Render Options - -#### `cellSize` (number, optional) {#cellsize} - -* Default: `1000` - -Size of each cell in meters - -#### `colorDomain` (number[2], optional) {#colordomain} - -* Default: `[min(colorWeight), max(colorWeight)]` - -Color scale domain, default is set to the extent of aggregated weights in each cell. -You can control how the colors of cells are mapped to weights by passing in an arbitrary color domain. -This is useful when you want to render different data input with the same color mapping for comparison. - -#### `colorRange` (Color[], optional) {#colorrange} - -* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` - -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. - -`colorDomain` is divided into `colorRange.length` equal segments, each mapped to one color in `colorRange`. - -#### `coverage` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#coverage} - -* Default: `1` - -Cell size multiplier, clamped between 0 - 1. The displayed size of cell is calculated by `coverage * cellSize`. -Note: coverage does not affect how objects are binned. - -#### `elevationDomain` (number[2], optional) {#elevationdomain} - -* Default: `[0, max(elevationWeight)]` - -Elevation scale input domain, default is set to between 0 and the max of aggregated weights in each cell. -You can control how the elevations of cells are mapped to weights by passing in an arbitrary elevation domain. -This is useful when you want to render different data input with the same elevation scale for comparison. - -#### `elevationRange` (number[2], optional) {#elevationrange} - -* Default: `[0, 1000]` - -Elevation scale output range - -#### `elevationScale` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationscale} - -* Default: `1` - -Cell elevation multiplier. -This is a handy property to scale all cells without updating the data. - -#### `extruded` (boolean, optional) {#extruded} - -* Default: `true` - -Whether to enable cell elevation.If set to false, all cell will be flat. - -#### `upperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#upperpercentile} - -* Default: `100` - -Filter cells and re-calculate color by `upperPercentile`. Cells with value -larger than the upperPercentile will be hidden. - -#### `lowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#lowerpercentile} - -* Default: `0` - -Filter cells and re-calculate color by `lowerPercentile`. Cells with value -smaller than the lowerPercentile will be hidden. - -#### `elevationUpperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationupperpercentile} - -* Default: `100` - -Filter cells and re-calculate elevation by `elevationUpperPercentile`. Cells with elevation value -larger than the elevationUpperPercentile will be hidden. +### Aggregation Options -#### `elevationLowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationlowerpercentile} - -* Default: `0` - -Filter cells and re-calculate elevation by `elevationLowerPercentile`. Cells with elevation value -smaller than the elevationLowerPercentile will be hidden. - -#### `colorScaleType` (string, optional) {#colorscaletype} - -* Default: 'quantize' - -Scaling function used to determine the color of the grid cell, default value is 'quantize'. Supported Values are 'quantize', 'linear', 'quantile' and 'ordinal'. - -#### `fp64` (boolean, optional) {#fp64} +#### `gpuAggregation` (boolean, optional) {#gpuaggregation} * Default: `false` -Whether the aggregation should be performed in high-precision 64-bit mode. Note that since deck.gl v6.1, the default 32-bit projection uses a hybrid mode that matches 64-bit precision with significantly better performance. +When set to `true`, aggregation is performed on the GPU. -#### `gpuAggregation` (boolean, optional) {#gpuaggregation} +In the right context, enabling GPU aggregation can significantly speed up your application. However, depending on the nature of input data and required application features, there are pros and cons in leveraging this functionality. See [CPU vs GPU aggregation](./overview.md#cpu-vs-gpu-aggregation) for an in-depth discussion. -* Default: `false` +CPU aggregation is used as fallback in the following cases: -When set to true, aggregation is performed on GPU, provided other conditions are met, for more details check the `GPU Aggregation` section below. GPU aggregation can be a lot faster than CPU depending upon the number of objects and number of cells. +- The current browser does not support GPU aggregation +- `gridAggregator` is defined +- `getColorValue` is defined +- `getElevationValue` is defined -**Note:** GPU Aggregation is faster only when using large data sets. For smaller data sets GPU Aggregation could be potentially slower than CPU Aggregation. -#### `material` (Material, optional) {#material} +#### `cellSize` (number, optional) {#cellsize} -* Default: `true` +* Default: `1000` -This is an object that contains material props for [lighting effect](../core/lighting-effect.md) applied on extruded polygons. -Check [the lighting guide](../../developer-guide/using-effects.md#material-settings) for configurable settings. +Size of each cell in meters. #### `colorAggregation` (string, optional) {#coloraggregation} * Default: `'SUM'` -Defines the operation used to aggregate all data object weights to calculate a bin's color value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. +Defines the operation used to aggregate all data object weights to calculate a cell's color value. Valid values are: + +- `'SUM'`: The sum of weights across all points that fall into a cell. +- `'MEAN'`: The mean weight across all points that fall into a cell. +- `'MIN'`: The minimum weight across all points that fall into a cell. +- `'MAX'`: The maximum weight across all points that fall into a cell. +- `'COUNT'`: The number of points that fall into a cell. + +`getColorWeight` and `colorAggregation` together determine the color value of each cell. If the `getColorValue` prop is supplied, they will be ignored. -`getColorWeight` and `colorAggregation` together determine the elevation value of each cell. If the `getColorValue` prop is supplied, they will be ignored. Note that supplying `getColorValue` disables GPU aggregation. ##### Example: Color by the count of data elements ```ts title="Option A: use getColorValue (CPU only)" -const layer = new GridLayer({ - // ... - getColorValue: (points: BikeRack[]) => points.length, +const layer = new HexagonLayer({ + //... + getColorValue: (points: BikeRack[]) => points.length }); ``` -```ts title="Option B: use getColorWeight and colorAggregation (GPU or CPU)" -const layer = new GridLayer({ +```ts title="Option B: use getColorWeight and colorAggregation (CPU or GPU)" +const layer = new HexagonLayer({ // ... - getColorWeight: (d: BikeRack) => 1, - colorAggregation: 'SUM' + getColorWeight: 1, + colorAggregation: 'COUNT' }); ``` ##### Example: Color by the mean value of 'SPACES' field ```ts title="Option A: use getColorValue (CPU only)" -const layer = new GridLayer({ +const layer = new HexagonLayer({ // ... getColorValue: (points: BikeRack[]) => { // Calculate mean value @@ -319,46 +235,52 @@ const layer = new GridLayer({ }); ``` -```ts title="Option B: use getColorWeight and colorAggregation (GPU or CPU)" -const layer = new GridLayer({ +```ts title="Option B: use getColorWeight and colorAggregation (CPU or GPU)" +const layer = new HexagonLayer({ // ... getColorWeight: (point: BikeRack) => point.SPACES, - colorAggregation: 'SUM' + colorAggregation: 'MEAN' }); ``` -If your use case requires aggregating using an operation that is not one of 'SUM', 'MEAN', 'MAX' and 'MIN', `getColorValue` should be used to define such custom aggregation function. In those cases GPU aggregation is not supported. +If your use case requires aggregating using an operation other than the built-in `colorAggregation` values, `getColorValue` should be used to define such custom aggregation function. #### `elevationAggregation` (string, optional) {#elevationaggregation} * Default: `'SUM'` -Defines the operation used to aggregate all data object weights to calculate a bin's elevation value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. +Defines the operation used to aggregate all data object weights to calculate a cell's elevation value. Valid values are: -`getElevationWeight` and `elevationAggregation` together determine the elevation value of each cell. If the `getElevationValue` prop is supplied, they will be ignored. Note that supplying `getElevationValue` disables GPU aggregation. +- `'SUM'`: The sum of weights across all points that fall into a cell. +- `'MEAN'`: The mean weight across all points that fall into a cell. +- `'MIN'`: The minimum weight across all points that fall into a cell. +- `'MAX'`: The maximum weight across all points that fall into a cell. +- `'COUNT'`: The number of points that fall into a cell. + +`getElevationWeight` and `elevationAggregation` together determine the elevation value of each cell. If the `getElevationValue` prop is supplied, they will be ignored. ##### Example: Elevation by the count of data elements ```ts title="Option A: use getElevationValue (CPU only)" -const layer = new GridLayer({ +const layer = new HexagonLayer({ // ... getElevationValue: (points: BikeRack[]) => points.length }); ``` -```ts title="Option B: use getElevationWeight and elevationAggregation (GPU or CPU)" -const layer = new GridLayer({ +```ts title="Option B: use getElevationWeight and elevationAggregation (CPU or GPU)" +const layer = new HexagonLayer({ // ... - getElevationWeight: (point: BikeRack) => 1, - elevationAggregation: 'SUM' + getElevationWeight: 1, + elevationAggregation: 'COUNT' }); ``` ##### Example: Elevation by the maximum value of 'SPACES' field ```ts title="Option A: use getElevationValue (CPU only)" -const layer = new GridLayer({ +const layer = new HexagonLayer({ // ... getElevationValue: (points: BikeRack[]) => { // Calculate max value @@ -367,28 +289,146 @@ const layer = new GridLayer({ }); ``` -```ts title="Option B: use getElevationWeight and elevationAggregation (GPU or CPU)" -const layer = new GridLayer({ +```ts title="Option B: use getElevationWeight and elevationAggregation (CPU or GPU)" +const layer = new HexagonLayer({ // ... getElevationWeight: (point: BikeRack) => point.SPACES, elevationAggregation: 'MAX' }); ``` -If your use case requires aggregating using an operation that is not one of 'SUM', 'MEAN', 'MAX' and 'MIN', `getElevationValue` should be used to define such custom aggregation function. In those cases GPU aggregation is not supported. +If your use case requires aggregating using an operation other than the built-in `elevationAggregation` values, `getElevationValue` should be used to define such custom aggregation function. -#### `getElevationValue` (Function, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getelevationvalue} + +#### `gridAggregator` (Function, optional) {#gridaggregator} * Default: `null` -After data objects are aggregated into cells, this accessor is called on each cell to get the value that its elevation is based on. If supplied, this will override the effect of `getElevationWeight` and `elevationAggregation` props. Note that supplying this prop disables GPU aggregation. +A custom function to override how points are grouped into grid cells. +If this prop is supplied, GPU aggregation will be disabled regardless of the `gpuAggregation` setting. -Arguments: +This function will be called with the following arguments: +- `position` (number[]) - the position of a data point +- `cellSize` (number) - value of the `cellSize` prop -- `objects` (DataT[]) - a list of objects whose positions fall inside this cell. -- `objectInfo` (object) - contains the following fields: - + `indices` (number[]) - the indices of `objects` in the original data - + `data` - the value of the `data` prop. +It is expected to return an array of 2 integers that represent a cell ID. Points that resolve to the same cell ID are grouped together. + +### Render Options + +#### `coverage` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#coverage} + +* Default: `1` + +Cell size multiplier, clamped between 0 - 1. The displayed size of cell is calculated by `coverage * cellSize`. +Note: coverage does not affect how objects are binned. + +#### `extruded` (boolean, optional) {#extruded} + +* Default: `true` + +Whether to enable cell elevation.If set to false, all cell will be flat. + +#### `colorScaleType` (string, optional) {#colorscaletype} + +* Default: `'quantize'` + +The color scale converts from a continuous numeric stretch (`colorDomain`) into a list of colors (`colorRange`). Cells with value of `colorDomain[0]` will be rendered with the color of `colorRange[0]`, and cells with value of `colorDomain[1]` will be rendered with the color of `colorRange[colorRange.length - 1]`. + +`colorScaleType` determines how a numeric value in domain is mapped to a color in range. Supported values are: +- `'linear'`: `colorRange` is linearly interpolated based on where the value lies in `colorDomain`. +- `'quantize'`: `colorDomain` is divided into `colorRange.length` equal segments, each mapped to one discrete color in `colorRange`. +- `'quantile'`: input values are divided into `colorRange.length` equal-size groups, each mapped to one discrete color in `colorRange`. +- `'ordinal'`: each unique value is mapped to one discrete color in `colorRange`. + +Note that using "quantile" or "ordinal" scale with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `colorDomain` (number[2], optional) {#colordomain} + +* Default: `null` (auto) + +If not provided, the layer will set `colorDomain` to the +actual min, max values from all cells at run time. + +By providing a `colorDomain`, you can control how a value is represented by color. This is useful when you want to render different data input with the same color mapping for comparison. + +#### `colorRange` (Color[], optional) {#colorrange} + +* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` + +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 is omitted a value of 255 is used. + + +#### `elevationScaleType` (string, optional) {#elevationscaletype} + +* Default: `'linear'` + +The elevation scale converts from a continuous numeric stretch (`elevationDomain`) into another continuous numeric stretch (`elevationRange`). Cells with value of `elevationDomain[0]` will be rendered with the elevation of `elevationRange[0]`, and cells with value of `elevationDomain[1]` will be rendered with the elevation of `elevationRange[1]`. + +`elevationScaleType` determines how a numeric value in domain is mapped to a elevation in range. Supported values are: +- `'linear'`: `elevationRange` is linearly interpolated based on where the value lies in `elevationDomain`. +- `'quantile'`: input values are divided into percentile groups, each mapped to one discrete elevation in `elevationRange`. + +#### `elevationDomain` (number[2], optional) {#elevationdomain} + +* Default: `null` (auto) + +If not provided, the layer will set `elevationDomain` to the +actual min, max values from all cells at run time. + +By providing a `elevationDomain`, you can control how a value is represented by elevation. This is useful when you want to render different data input with the same elevation mapping for comparison. + +#### `elevationRange` (number[2], optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationrange} + +* Default: `[0, 1000]` + +Elevation scale output range + +#### `elevationScale` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationscale} + +* Default: `1` + +Cell elevation multiplier. +This is a handy property to scale all cells without updating the data. + + +#### `upperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#upperpercentile} + +* Default: `100` + +Filter cells and re-calculate color by `upperPercentile`. Cells with value larger than the upperPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `lowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#lowerpercentile} + +* Default: `0` + +Filter cells and re-calculate color by `lowerPercentile`. Cells with value smaller than the lowerPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `elevationUpperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationupperpercentile} + +* Default: `100` + +Filter cells and re-calculate elevation by `elevationUpperPercentile`. Cells with elevation value larger than the elevationUpperPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `elevationLowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationlowerpercentile} + +* Default: `0` + +Filter cells and re-calculate elevation by `elevationLowerPercentile`. Cells with elevation value smaller than the elevationLowerPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `material` (Material, optional) {#material} + +* Default: `true` + +This is an object that contains material props for [lighting effect](../core/lighting-effect.md) applied on extruded polygons. +Check [the lighting guide](../../developer-guide/using-effects.md#material-settings) for configurable settings. ### Data Accessors @@ -434,6 +474,20 @@ The weight of a data object used to calculate the elevation value for a cell. * If a function is provided, it is called on each object to retrieve its weight. +#### `getElevationValue` (Function, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#getelevationvalue} + +* Default: `null` + +After data objects are aggregated into cells, this accessor is called on each cell to get the value that its elevation is based on. If supplied, this will override the effect of `getElevationWeight` and `elevationAggregation` props. + +Arguments: + +- `objects` (DataT[]) - a list of objects whose positions fall inside this cell. +- `objectInfo` (object) - contains the following fields: + + `indices` (number[]) - the indices of `objects` in the original data + + `data` - the value of the `data` prop. + + ### Callbacks #### `onSetColorDomain` (Function, optional) {#onsetcolordomain} @@ -449,52 +503,24 @@ This callback will be called when cell color domain has been calculated. This callback will be called when cell elevation domain has been calculated. -## GPU Aggregation - -### Performance Metrics - -The following table compares the performance between CPU and GPU aggregations using random data: +## Picking -| #objects | CPU #iterations/sec | GPU #iterations/sec | Notes | -| ---- | --- | --- | --- | -| 25K | 535 | 359 | GPU is 33% slower | -| 100K | 119 | 437 | GPU is 267% faster | -| 1M | 12.7 | 158 | GPU is 1144% faster | +The [PickingInfo.object](../../developer-guide/interactivity.md#the-pickinginfo-object) field returned by hover/click events of this layer represents an aggregated cell. The object contains the following fields: -*Numbers are collected on a 2016 15-inch Macbook Pro (CPU: 2.8 GHz Intel Core i7 and GPU: AMD Radeon R9 M370X 2 GB)* - -### Fallback Cases - -This layer performs aggregation on GPU when the `gpuAggregation` prop is set to `true`, but will fallback to CPU in the following cases: - -#### Percentile Props - -When following percentile props are set, it requires sorting of aggregated values, which cannot be supported when aggregating on GPU. - -* `lowerPercentile`, `upperPercentile`, `elevationLowerPercentile` and `elevationUpperPercentile`. - -#### Color and Elevation Props - -When `colorScaleType` props is set to a 'quantile' or 'ordinal', aggregation will fallback to CPU. For GPU Aggregation, use 'quantize', 'linear'. - -#### Color Scale Type Props - -When following percentile props are set, it requires sorting of aggregated values, which cannot be supported when aggregating on GPU. - -* `lowerPercentile`, `upperPercentile`, `elevationLowerPercentile` and `elevationUpperPercentile`. - -### Domain setting callbacks - -When using GPU Aggregation, `onSetColorDomain` and `onSetElevationDomain` are not fired. +- `col` (number) - Column index of the picked cell. +- `row` (number) - Row index of the picked cell. +- `colorValue` (number) - Aggregated color value, as determined by `getColorWeight` and `colorAggregation` +- `elevationValue` (number) - Aggregated elevation value, as determined by `getElevationWeight` and `elevationAggregation` +- `count` (number) - Number of data points in the picked cell +- `pointIndices` (number[]) - Indices of the data objects in the picked cell. Only available if using CPU aggregation. +- `points` (object[]) - The data objects in the picked cell. Only available if using CPU aggregation and layer data is an array. ## Sub Layers The GridLayer renders the following sublayers: -* `CPU` - a [CPUGridLayer](./cpu-grid-layer.md) when using CPU aggregation. - -* `GPU` - a [GPUGridLayer](./gpu-grid-layer.md) when using GPU aggregation. +* `cells` - a custom layer that extends the [ColumnLayer](../layers/column-layer.md). ## Source diff --git a/docs/api-reference/aggregation-layers/hexagon-layer.md b/docs/api-reference/aggregation-layers/hexagon-layer.md index 5107ba850f9..bb51e5988a0 100644 --- a/docs/api-reference/aggregation-layers/hexagon-layer.md +++ b/docs/api-reference/aggregation-layers/hexagon-layer.md @@ -6,7 +6,7 @@ import {HexagonLayerDemo} from '@site/src/doc-demos/aggregation-layers'; The `HexagonLayer` aggregates data into a hexagon-based heatmap. The color and height of a hexagon are determined based on the objects it contains. -HexagonLayer is a [CompositeLayer](../core/composite-layer.md) and at the moment only works with `COORDINATE_SYSTEM.LNGLAT`. +HexagonLayer is a [CompositeLayer](../core/composite-layer.md). import Tabs from '@theme/Tabs'; @@ -23,6 +23,7 @@ const layer = new HexagonLayer({ id: 'HexagonLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, extruded: true, getPosition: d => d.COORDINATES, getColorWeight: d => d.SPACES, @@ -48,8 +49,8 @@ new Deck({ ```ts -import {Deck, PickingInfo} from '@deck.gl/core'; -import {HexagonLayer} from '@deck.gl/aggregation-layers'; +import {Deck} from '@deck.gl/core'; +import {HexagonLayer, HexagonLayerPickingInfo} from '@deck.gl/aggregation-layers'; type BikeRack = { ADDRESS: string; @@ -61,6 +62,7 @@ const layer = new HexagonLayer({ id: 'HexagonLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, extruded: true, getPosition: (d: BikeRack) => d.COORDINATES, getColorWeight: (d: BikeRack) => d.SPACES, @@ -77,7 +79,7 @@ new Deck({ zoom: 11 }, controller: true, - getTooltip: ({object}: PickingInfo) => object && `Count: ${object.elevationValue}`, + getTooltip: ({object}: HexagonLayerPickingInfo) => object && `Count: ${object.elevationValue}`, layers: [layer] }); ``` @@ -88,8 +90,7 @@ new Deck({ ```tsx import React from 'react'; import DeckGL from '@deck.gl/react'; -import {HexagonLayer} from '@deck.gl/aggregation-layers'; -import type {PickingInfo} from '@deck.gl/core'; +import {HexagonLayer, HexagonLayerPickingInfo} from '@deck.gl/aggregation-layers'; type BikeRack = { ADDRESS: string; @@ -102,6 +103,7 @@ function App() { id: 'HexagonLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, extruded: true, getPosition: (d: BikeRack) => d.COORDINATES, getColorWeight: (d: BikeRack) => d.SPACES, @@ -118,7 +120,7 @@ function App() { zoom: 11 }} controller - getTooltip={({object}: PickingInfo) => object && `Count: ${object.elevationValue}`} + getTooltip={({object}: HexagonLayerPickingInfo) => object && `Count: ${object.elevationValue}`} layers={[layer]} />; } @@ -140,7 +142,7 @@ npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers ```ts import {HexagonLayer} from '@deck.gl/aggregation-layers'; -import type {HexagonLayerProps} from '@deck.gl/aggregation-layers'; +import type {HexagonLayerProps, HexagonLayerPickingInfo} from '@deck.gl/aggregation-layers'; new HexagonLayer(...props: HexagonLayerProps[]); ``` @@ -164,155 +166,65 @@ new deck.HexagonLayer({}); Inherits from all [Base Layer](../core/layer.md) and [CompositeLayer](../core/composite-layer.md) properties. -### Render Options - -#### `radius` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#radius} - -* Default: `1000` - -Radius of hexagon bin in meters. The hexagons are pointy-topped (rather than flat-topped). - -#### `hexagonAggregator` (Function, optional) {#hexagonaggregator} - -* Default: `d3-hexbin` - -`hexagonAggregator` is a function to aggregate data into hexagonal bins. -The `hexagonAggregator` takes props of the layer and current viewport as arguments. -The output should be `{hexagons: [], hexagonVertices: []}`. `hexagons` is -an array of `{centroid: [], points: []}`, where `centroid` is the -center of the hexagon, and `points` is an array of points that contained by it. `hexagonVertices` -(optional) is an array of points define the primitive hexagon geometry. - -By default, the `HexagonLayer` uses -[d3-hexbin](https://github.com/d3/d3-hexbin) as `hexagonAggregator`, -see `modules/aggregation-layers/src/hexagon-layer/hexagon-aggregator.ts` - -#### `colorDomain` (number[2], optional) {#colordomain} - -* Default: `[min(colorWeight), max(colorWeight)]` - -Color scale input domain. The color scale maps continues numeric domain into -discrete color range. If not provided, the layer will set `colorDomain` to the -extent of aggregated weights in each hexagon. -You can control how the colors of hexagons are mapped to weights by passing in an arbitrary color domain. -This is useful when you want to render different data input with the same color mapping for comparison. - -#### `colorRange` (Color[], optional) {#colorrange} - -* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` - -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. - -`colorDomain` is divided into `colorRange.length` equal segments, each mapped to one color in `colorRange`. - -#### `coverage` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#coverage} - -* Default: `1` - -Hexagon radius multiplier, clamped between 0 - 1. The displayed radius of hexagon is calculated by `coverage * radius`. -Note: coverage does not affect how objects are binned. - -#### `elevationDomain` (number[2], optional) {#elevationdomain} - -* Default: `[0, max(elevationWeight)]` - -Elevation scale input domain. The elevation scale is a linear scale that -maps number of counts to elevation. By default it is set to between -0 and the max of aggregated weights in each hexagon. -You can control how the elevations of hexagons are mapped to weights by passing in an arbitrary elevation domain. -This property is useful when you want to render different data input -with the same elevation scale for comparison. - -#### `elevationRange` (number[2], optional) {#elevationrange} - -* Default: `[0, 1000]` - -Elevation scale output range - -#### `elevationScale` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationscale} - -* Default: `1` - -Hexagon elevation multiplier. The actual elevation is calculated by - `elevationScale * getElevationValue(d)`. `elevationScale` is a handy property to scale -all hexagons without updating the data. +### Aggregation Options -#### `extruded` (boolean, optional) {#extruded} +#### `gpuAggregation` (boolean, optional) {#gpuaggregation} * Default: `false` -Whether to enable cell elevation. If set to false, all cells will be flat. - -#### `upperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#upperpercentile} - -* Default: `100` - -Filter bins and re-calculate color by `upperPercentile`. Hexagons with color value -larger than the upperPercentile will be hidden. - -#### `lowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#lowerpercentile} - -* Default: `0` - -Filter bins and re-calculate color by `lowerPercentile`. Hexagons with color value -smaller than the lowerPercentile will be hidden. +When set to `true`, aggregation is performed on the GPU. -#### `elevationUpperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationupperpercentile} +In the right context, enabling GPU aggregation can significantly speed up your application. However, depending on the nature of input data and required application features, there are pros and cons in leveraging this functionality. See [CPU vs GPU aggregation](./overview.md#cpu-vs-gpu-aggregation) for an in-depth discussion. -* Default: `100` +CPU aggregation is used as fallback in the following cases: -Filter bins and re-calculate elevation by `elevationUpperPercentile`. Hexagons with elevation value -larger than the elevationUpperPercentile will be hidden. +- The current browser does not support GPU aggregation +- `gridAggregator` is defined +- `getColorValue` is defined +- `getElevationValue` is defined -#### `elevationLowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationlowerpercentile} - -* Default: `0` - -Filter bins and re-calculate elevation by `elevationLowerPercentile`. Hexagons with elevation value -smaller than the elevationLowerPercentile will be hidden. - -#### `colorScaleType` (string, optional) {#colorscaletype} -* Default: 'quantize' +#### `radius` (number, optional) {#radius} -Scaling function used to determine the color of the grid cell, default value is 'quantize'. Supported Values are 'quantize', 'quantile' and 'ordinal'. - -#### `material` (Material, optional) {#material} - -* Default: `true` - -This is an object that contains material props for [lighting effect](../core/lighting-effect.md) applied on extruded polygons. -Check [the lighting guide](../../developer-guide/using-effects.md#material-settings) for configurable settings. +* Default: `1000` +Radius of hexagon in meters. The hexagons are pointy-topped (rather than flat-topped). #### `colorAggregation` (string, optional) {#coloraggregation} * Default: `'SUM'` -Defines the operation used to aggregate all data object weights to calculate a bin's color value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. +Defines the operation used to aggregate all data object weights to calculate a hexagon's color value. Valid values are: + +- `'SUM'`: The sum of weights across all points that fall into a hexagon. +- `'MEAN'`: The mean weight across all points that fall into a hexagon. +- `'MIN'`: The minimum weight across all points that fall into a hexagon. +- `'MAX'`: The maximum weight across all points that fall into a hexagon. +- `'COUNT'`: The number of points that fall into a hexagon. + +`getColorWeight` and `colorAggregation` together determine the color value of each hexagon. If the `getColorValue` prop is supplied, they will be ignored. -`getColorWeight` and `colorAggregation` together determine the elevation value of each bin. If the `getColorValue` prop is supplied, they will be ignored. ##### Example: Color by the count of data elements -```ts title="Option A: use getColorValue" +```ts title="Option A: use getColorValue (CPU only)" const layer = new HexagonLayer({ //... getColorValue: (points: BikeRack[]) => points.length }); ``` -```ts title="Option B: use getColorWeight and colorAggregation" +```ts title="Option B: use getColorWeight and colorAggregation (CPU or GPU)" const layer = new HexagonLayer({ // ... - getColorWeight: (d: BikeRack) => 1, - colorAggregation: 'SUM' + getColorWeight: 1, + colorAggregation: 'COUNT' }); ``` ##### Example: Color by the mean value of 'SPACES' field -```ts title="Option A: use getColorValue" +```ts title="Option A: use getColorValue (CPU only)" const layer = new HexagonLayer({ // ... getColorValue: (points: BikeRack[]) => { @@ -322,45 +234,51 @@ const layer = new HexagonLayer({ }); ``` -```ts title="Option B: use getColorWeight and colorAggregation" +```ts title="Option B: use getColorWeight and colorAggregation (CPU or GPU)" const layer = new HexagonLayer({ // ... getColorWeight: (point: BikeRack) => point.SPACES, - colorAggregation: 'SUM' + colorAggregation: 'MEAN' }); ``` -If your use case requires aggregating using an operation that is not one of 'SUM', 'MEAN', 'MAX' and 'MIN', `getColorValue` should be used to define such custom aggregation function. +If your use case requires aggregating using an operation other than the built-in `colorAggregation` values, `getColorValue` should be used to define such custom aggregation function. #### `elevationAggregation` (string, optional) {#elevationaggregation} * Default: `'SUM'` -Defines the operation used to aggregate all data object weights to calculate a bin's elevation value. Valid values are `'SUM'`, `'MEAN'`, `'MIN'` and `'MAX'`. `'SUM'` is used when an invalid value is provided. +Defines the operation used to aggregate all data object weights to calculate a hexagon's elevation value. Valid values are: + +- `'SUM'`: The sum of weights across all points that fall into a hexagon. +- `'MEAN'`: The mean weight across all points that fall into a hexagon. +- `'MIN'`: The minimum weight across all points that fall into a hexagon. +- `'MAX'`: The maximum weight across all points that fall into a hexagon. +- `'COUNT'`: The number of points that fall into a hexagon. -`getElevationWeight` and `elevationAggregation` together determine the elevation value of each bin. If the `getElevationValue` prop is supplied, they will be ignored. +`getElevationWeight` and `elevationAggregation` together determine the elevation value of each hexagon. If the `getElevationValue` prop is supplied, they will be ignored. ##### Example: Elevation by the count of data elements -```ts title="Option A: use getElevationValue" +```ts title="Option A: use getElevationValue (CPU only)" const layer = new HexagonLayer({ // ... getElevationValue: (points: BikeRack[]) => points.length }); ``` -```ts title="Option B: use getElevationWeight and elevationAggregation" +```ts title="Option B: use getElevationWeight and elevationAggregation (CPU or GPU)" const layer = new HexagonLayer({ // ... - getElevationWeight: (point: BikeRack) => 1, - elevationAggregation: 'SUM' + getElevationWeight: 1, + elevationAggregation: 'COUNT' }); ``` ##### Example: Elevation by the maximum value of 'SPACES' field -```ts title="Option A: use getElevationValue" +```ts title="Option A: use getElevationValue (CPU only)" const layer = new HexagonLayer({ // ... getElevationValue: (points: BikeRack[]) => { @@ -370,7 +288,7 @@ const layer = new HexagonLayer({ }); ``` -```ts title="Option B: use getElevationWeight and elevationAggregation" +```ts title="Option B: use getElevationWeight and elevationAggregation (CPU or GPU)" const layer = new HexagonLayer({ // ... getElevationWeight: (point: BikeRack) => point.SPACES, @@ -378,7 +296,142 @@ const layer = new HexagonLayer({ }); ``` -If your use case requires aggregating using an operation that is not one of 'SUM', 'MEAN', 'MAX' and 'MIN', `getElevationValue` should be used to define such custom aggregation function. +If your use case requires aggregating using an operation other than the built-in `elevationAggregation` values, `getElevationValue` should be used to define such custom aggregation function. + + +#### `hexagonAggregator` (Function, optional) {#hexagonaggregator} + +* Default: `null` + +A custom function to override how points are grouped into hexagonal bins. +If this prop is supplied, GPU aggregation will be disabled regardless of the `gpuAggregation` setting. + +This function will be called with the following arguments: +- `position` (number[]) - the position of a data point +- `radius` (number) - value of the `radius` prop + +It is expected to return an array of 2 integers that represent a hexagon ID. Points that resolve to the same hexagon ID are grouped together. + +By default, the `HexagonLayer` uses +[d3-hexbin](https://github.com/d3/d3-hexbin) as `hexagonAggregator`, +see `modules/aggregation-layers/src/hexagon-layer/hexbin.ts` + +### Render Options + +#### `coverage` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#coverage} + +* Default: `1` + +Hexagon radius multiplier, clamped between 0 - 1. The displayed radius of hexagon is calculated by `coverage * radius`. +Note: coverage does not affect how objects are binned. + +#### `extruded` (boolean, optional) {#extruded} + +* Default: `false` + +Whether to render the hexagons as 3D columns. If set to `false`, all hexagons will be flat. + +#### `colorScaleType` (string, optional) {#colorscaletype} + +* Default: `'quantize'` + +The color scale converts from a continuous numeric stretch (`colorDomain`) into a list of colors (`colorRange`). Hexagons with value of `colorDomain[0]` will be rendered with the color of `colorRange[0]`, and hexagons with value of `colorDomain[1]` will be rendered with the color of `colorRange[colorRange.length - 1]`. + +`colorScaleType` determines how a numeric value in domain is mapped to a color in range. Supported values are: +- `'linear'`: `colorRange` is linearly interpolated based on where the value lies in `colorDomain`. +- `'quantize'`: `colorDomain` is divided into `colorRange.length` equal segments, each mapped to one discrete color in `colorRange`. +- `'quantile'`: input values are divided into `colorRange.length` equal-size groups, each mapped to one discrete color in `colorRange`. +- `'ordinal'`: each unique value is mapped to one discrete color in `colorRange`. + +Note that using "quantile" or "ordinal" scale with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `colorDomain` (number[2], optional) {#colordomain} + +* Default: `null` (auto) + +If not provided, the layer will set `colorDomain` to the +actual min, max values from all hexagons at run time. + +By providing a `colorDomain`, you can control how a value is represented by color. This is useful when you want to render different data input with the same color mapping for comparison. + +#### `colorRange` (Color[], optional) {#colorrange} + +* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` + +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 is omitted a value of 255 is used. + + +#### `elevationScaleType` (string, optional) {#elevationscaletype} + +* Default: `'linear'` + +The elevation scale converts from a continuous numeric stretch (`elevationDomain`) into another continuous numeric stretch (`elevationRange`). Hexagons with value of `elevationDomain[0]` will be rendered with the elevation of `elevationRange[0]`, and hexagons with value of `elevationDomain[1]` will be rendered with the elevation of `elevationRange[1]`. + +`elevationScaleType` determines how a numeric value in domain is mapped to a elevation in range. Supported values are: +- `'linear'`: `elevationRange` is linearly interpolated based on where the value lies in `elevationDomain`. +- `'quantile'`: input values are divided into percentile groups, each mapped to one discrete elevation in `elevationRange`. + +#### `elevationDomain` (number[2], optional) {#elevationdomain} + +* Default: `null` (auto) + +If not provided, the layer will set `elevationDomain` to the +actual min, max values from all hexagons at run time. + +By providing a `elevationDomain`, you can control how a value is represented by elevation. This is useful when you want to render different data input with the same elevation mapping for comparison. + +#### `elevationRange` (number[2], optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationrange} + +* Default: `[0, 1000]` + +Elevation scale output range + +#### `elevationScale` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationscale} + +* Default: `1` + +Hexagon elevation multiplier. +This is a handy property to scale all hexagons without updating the data. + + +#### `upperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#upperpercentile} + +* Default: `100` + +Filter hexagons and re-calculate color by `upperPercentile`. Hexagons with value larger than the upperPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `lowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#lowerpercentile} + +* Default: `0` + +Filter hexagons and re-calculate color by `lowerPercentile`. Hexagons with value smaller than the lowerPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `elevationUpperPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationupperpercentile} + +* Default: `100` + +Filter hexagons and re-calculate elevation by `elevationUpperPercentile`. Hexagons with elevation value larger than the elevationUpperPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `elevationLowerPercentile` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#elevationlowerpercentile} + +* Default: `0` + +Filter bns and re-calculate elevation by `elevationLowerPercentile`. Hexagons with elevation value smaller than the elevationLowerPercentile will be hidden. + +Note that using this prop with GPU aggregation will incur a one-time cost of reading aggregated values from the GPU to the CPU. This overhead may be undesirable if the source data updates frequently. + +#### `material` (Material, optional) {#material} + +* Default: `true` + +This is an object that contains material props for [lighting effect](../core/lighting-effect.md) applied on extruded polygons. +Check [the lighting guide](../../developer-guide/using-effects.md#material-settings) for configurable settings. ### Data Accessors @@ -394,7 +447,7 @@ Method called to retrieve the position of each object. * Default: `1` -The weight of a data object used to calculate the color value for a bin. +The weight of a data object used to calculate the color value for a hexagon. * If a number is provided, it is used as the weight for all objects. * If a function is provided, it is called on each object to retrieve its weight. @@ -404,11 +457,11 @@ The weight of a data object used to calculate the color value for a bin. * Default: `null` -After data objects are aggregated into bins, this accessor is called on each bin to get the value that its color is based on. If supplied, this will override the effect of `getColorWeight` and `colorAggregation` props. +After data objects are sorted into hexagonal bins, this accessor is called on each hexagon to get the value that its color is based on. If supplied, this will override the effect of `getColorWeight` and `colorAggregation` props. Arguments: -- `objects` (DataT[]) - a list of objects whose positions fall inside this cell. +- `objects` (DataT[]) - a list of objects whose positions fall inside this hexagon. - `objectInfo` (object) - contains the following fields: + `indices` (number[]) - the indices of `objects` in the original data + `data` - the value of the `data` prop. @@ -418,7 +471,7 @@ Arguments: * Default: `1` -The weight of a data object used to calculate the elevation value for a bin. +The weight of a data object used to calculate the elevation value for a hexagon. * If a number is provided, it is used as the weight for all objects. * If a function is provided, it is called on each object to retrieve its weight. @@ -428,11 +481,11 @@ The weight of a data object used to calculate the elevation value for a bin. * Default: `null` -After data objects are aggregated into bins, this accessor is called on each bin to get the value that its elevation is based on. If supplied, this will override the effect of `getElevationWeight` and `elevationAggregation` props. +After data objects are sorted into hexagonal bins, this accessor is called on each hexagon to get the value that its elevation is based on. If supplied, this will override the effect of `getElevationWeight` and `elevationAggregation` props. Arguments: -- `objects` (DataT[]) - a list of objects whose positions fall inside this cell. +- `objects` (DataT[]) - a list of objects whose positions fall inside this hexagon. - `objectInfo` (object) - contains the following fields: + `indices` (number[]) - the indices of `objects` in the original data + `data` - the value of the `data` prop. @@ -444,21 +497,33 @@ Arguments: * Default: `([min, max]) => {}` -This callback will be called when bin color domain has been calculated. +This callback will be called when hexagon color domain has been calculated. #### `onSetElevationDomain` (Function, optional) {#onsetelevationdomain} * Default: `([min, max]) => {}` -This callback will be called when bin elevation domain has been calculated. +This callback will be called when hexagon elevation domain has been calculated. + + +## Picking + +The [PickingInfo.object](../../developer-guide/interactivity.md#the-pickinginfo-object) field returned by hover/click events of this layer represents an aggregated hexagon. The object contains the following fields: +- `col` (number) - Column index of the picked hexagon. +- `row` (number) - Row index of the picked hexagon. +- `colorValue` (number) - Aggregated color value, as determined by `getColorWeight` and `colorAggregation` +- `elevationValue` (number) - Aggregated elevation value, as determined by `getElevationWeight` and `elevationAggregation` +- `count` (number) - Number of data points in the picked hexagon +- `pointIndices` (number[]) - Indices of the data objects in the picked hexagon. Only available if using CPU aggregation. +- `points` (object[]) - The data objects in the picked hexagon. Only available if using CPU aggregation and layer data is an array. ## Sub Layers The HexagonLayer renders the following sublayers: -* `hexagon-cell` - a [ColumnLayer](../layers/column-layer.md) rendering the aggregated columns. +* `cells` - a [ColumnLayer](../layers/column-layer.md) rendering the aggregated columns. ## Source diff --git a/docs/api-reference/aggregation-layers/overview.md b/docs/api-reference/aggregation-layers/overview.md new file mode 100644 index 00000000000..68e7d1ac5d5 --- /dev/null +++ b/docs/api-reference/aggregation-layers/overview.md @@ -0,0 +1,43 @@ +# @deck.gl/aggregation-layers + +Layers that aggregate the input data and visualize them in alternative representations, such as grid and hexagon binning, contour, and heatmap. + + - [ContourLayer](./contour-layer.md) + - [GridLayer](./grid-layer.md) + - [HeatmapLayer](./heatmap-layer.md) + - [HexagonLayer](./hexagon-layer.md) + - [ScreenGridLayer](./screen-grid-layer.md) + +## CPU vs GPU Aggregation + +In the right context, enabling GPU aggregation can significantly speed up your application. This section offers in-depth insights into the performance and limitations that should be factored into leveraging this functionality. + +### Considerations + +- **Compaibility**: The client-side features required by GPU aggregation has been universally supported by evergreen browsers for a while and represent 95%+ of the global market. However, users have reported that driver discrepancies in certain devices/chips can affect the outcome. +- **Data size**: The time it takes for CPU to perform aggregation is generally linear to the size of the input data. GPU aggegation requires some up-front overhead in setting up shaders and uploading buffers, but the margin to process more data is very small. When working with large datasets (>100K) GPU is much faster than CPU. With small datasets, GPU could be slower than CPU. +- **Data distribution**: The memory needed by CPU aggregation is proportional to the number of cells that contain at least one data point. The memory needed by GPU aggregation is proportional to all possible cells, including the empty ones in between. GPU performs better with densely concentrated data points than sparse and sprawling data points. +- **Filtering**: GPU-based extentions such as [DataFilterExtension](../extensions/data-filter-extension.md), [MaskExtension](../extensions/mask-extension.md) only work with GPU aggregation. +- **Precision**: GPU shaders only support 32-bit floating numbers. While this layer implement mitigations to compensate for precision loss, it is expected if GPU aggregation does not produce identical results as the CPU. There are tests in place to ensure acceptable consistency between CPU and GPU aggregation. +- **Access to binned points**: GPU aggregation does not expose which data points are contained in a specific cell. If this is a requirement, for example, displaying a list of locations upon selecting a cell, then you need to either use CPU aggregation, or manually filter data on the fly. + +### Performance Metrics + +The following table compares the performance between CPU and GPU aggregations using random data: + +| #objects | CPU #iterations/sec | GPU #iterations/sec | Notes | +| ---- | --- | --- | --- | +| 25K | 535 | 359 | GPU is 33% slower | +| 100K | 119 | 437 | GPU is 267% faster | +| 1M | 12.7 | 158 | GPU is 1144% faster | + +*Numbers are collected on a 2016 15-inch Macbook Pro (CPU: 2.8 GHz Intel Core i7 and GPU: AMD Radeon R9 M370X 2 GB)* + +## Advanced usage + +It is possible to implement a custom aggregation layer, or even perform aggregation without layers, using the utilities from this module. + +- [AggregationLayer](./aggregation-layer.md) class +- [Aggregator](./aggregator.md) interface, implemented by + + [CPUAggregator](./cpu-aggregator.md) + + [WebGLAggregator](./webgl-aggregator.md) diff --git a/docs/api-reference/aggregation-layers/screen-grid-layer.md b/docs/api-reference/aggregation-layers/screen-grid-layer.md index 4cf70231509..ded50266958 100644 --- a/docs/api-reference/aggregation-layers/screen-grid-layer.md +++ b/docs/api-reference/aggregation-layers/screen-grid-layer.md @@ -4,7 +4,7 @@ import {ScreenGridLayerDemo} from '@site/src/doc-demos/aggregation-layers'; -The `ScreenGridLayer` aggregates data into histogram bins and renders them as a grid. By default aggregation happens on GPU, aggregation falls back to CPU when browser doesn't support GPU Aggregation or when `gpuAggregation` prop is set to 1. +The `ScreenGridLayer` aggregates data into histogram bins in screen space and renders them as a overlaid grid. import Tabs from '@theme/Tabs'; @@ -21,6 +21,7 @@ const layer = new ScreenGridLayer({ id: 'ScreenGridLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, cellSizePixels: 50, colorRange: [ [0, 25, 0, 25], @@ -42,6 +43,7 @@ new Deck({ zoom: 11 }, controller: true, + getTooltip: ({object}) => object && `Count: ${object.value}`, layers: [layer] }); ``` @@ -51,7 +53,7 @@ new Deck({ ```ts import {Deck} from '@deck.gl/core'; -import {ScreenGridLayer} from '@deck.gl/aggregation-layers'; +import {ScreenGridLayer, ScreenGridLayerPickingInfo} from '@deck.gl/aggregation-layers'; type BikeRack = { ADDRESS: string; @@ -63,6 +65,7 @@ const layer = new ScreenGridLayer({ id: 'ScreenGridLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, cellSizePixels: 50, colorRange: [ [0, 25, 0, 25], @@ -84,6 +87,7 @@ new Deck({ zoom: 11 }, controller: true, + getTooltip: ({object}: ScreenGridLayerPickingInfo) => object && `Count: ${object.value}`, layers: [layer] }); ``` @@ -94,7 +98,7 @@ new Deck({ ```tsx import React from 'react'; import DeckGL from '@deck.gl/react'; -import {ScreenGridLayer} from '@deck.gl/aggregation-layers'; +import {ScreenGridLayer, ScreenGridLayerPickingInfo} from '@deck.gl/aggregation-layers'; type BikeRack = { ADDRESS: string; @@ -107,6 +111,7 @@ function App() { id: 'ScreenGridLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', + gpuAggregation: true, cellSizePixels: 50, colorRange: [ [0, 25, 0, 25], @@ -128,6 +133,7 @@ function App() { zoom: 11 }} controller + getTooltip={({object}: ScreenGridLayerPickingInfo) => object && `Count: ${object.value}`} layers={[layer]} />; } @@ -156,7 +162,7 @@ npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers ```ts import {ScreenGridLayer} from '@deck.gl/aggregation-layers'; -import type {ScreenGridLayerProps} from '@deck.gl/aggregation-layers'; +import type {ScreenGridLayerProps, ScreenGridLayerPickingInfo} from '@deck.gl/aggregation-layers'; new ScreenGridLayer(...props: ScreenGridLayerProps[]); ``` @@ -180,66 +186,71 @@ new deck.ScreenGridLayer({}); Inherits from all [Base Layer](../core/layer.md) properties. -### Render Options +### Aggregation Options -#### `cellSizePixels` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellsizepixels} +#### `gpuAggregation` (boolean, optional) {#gpuaggregation} -* Default: `100` +* Default: `true` -Unit width/height of the bins. +When set to `true` and the browser supports it, aggregation is performed on GPU. -#### `cellMarginPixels` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellmarginpixels} +In the right context, enabling GPU aggregation can significantly speed up your application. However, depending on the nature of input data and required application features, there are pros and cons in leveraging this functionality. See [CPU vs GPU aggregation](./overview.md#cpu-vs-gpu-aggregation) for an in-depth discussion. -* Default: `2`, gets clamped to [0, 5] -Cell margin size in pixels. +#### `cellSizePixels` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellsizepixels} -#### `minColor` (number[4], optional) **DEPRECATED** {#mincolor} +* Default: `100` -* Default: `[0, 0, 0, 255]` +Unit width/height of the bins. -Expressed as an rgba array, minimal color that could be rendered by a tile. This prop is deprecated in version 5.2.0, use `colorRange` and `colorDomain` instead. +#### `aggregation` (string, optional) {#aggregation} -#### `maxColor` (number[4], optional) **DEPRECATED** {#maxcolor} +* Default: `'SUM'` -* Default: `[0, 255, 0, 255]` +Defines the operation used to aggregate all data object weights to calculate a cell's value. Valid values are: -Expressed as an rgba array, maximal color that could be rendered by a tile. This prop is deprecated in version 5.2.0, use `colorRange` and `colorDomain` instead. +- `'SUM'`: The sum of weights across all points that fall into a cell. +- `'MEAN'`: The mean weight across all points that fall into a cell. +- `'MIN'`: The minimum weight across all points that fall into a cell. +- `'MAX'`: The maximum weight across all points that fall into a cell. +- `'COUNT'`: The number of points that fall into a cell. -#### `colorDomain` (number[2], optional) {#colordomain} +`getWeight` and `aggregation` together determine the elevation value of each cell. -* Default: `[1, max(weight)]` +### Render Options -Color scale input domain. The color scale maps continues numeric domain into -discrete color range. If not provided, the layer will set `colorDomain` to [1, max-of-all-cell-weights], You can control how the color of cells mapped -to value of its weight by passing in an arbitrary color domain. This property is extremely handy when you want to render different data input with the same color mapping for comparison. +#### `cellMarginPixels` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#cellmarginpixels} -#### `colorRange` (Color[6], optional) {#colorrange} +* Default: `2`, gets clamped to [0, 5] -* Default: +Cell margin size in pixels. -Specified as an array of 6 colors [color1, color2, ... color6]. 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. By default `colorRange` is set to -[colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd`. +Note that setting this prop does not affect how points are binned. -NOTE: `minColor` and `maxColor` take precedence over `colorDomain` and `colorRange`, to use `colorDomain` and `colorRange` do not provide `minColor` and `maxColor`. +#### `colorScaleType` (string, optional) {#colorscaletype} -#### `gpuAggregation` (boolean, optional) {#gpuaggregation} +* Default: `'quantize'` -* Default: true +The color scale converts from a continuous numeric stretch (`colorDomain`) into a list of colors (`colorRange`). Cells with value of `colorDomain[0]` will be rendered with the color of `colorRange[0]`, and cells with value of `colorDomain[1]` will be rendered with the color of `colorRange[colorRange.length - 1]`. -When set to true and browser supports GPU aggregation, aggregation is performed on GPU. GPU aggregation can be 10 to 20 times faster depending upon number of points and number of cells. +`colorScaleType` determines how a numeric value in domain is mapped to a color in range. Supported values are: +- `'linear'`: `colorRange` is linearly interpolated based on where the value lies in `colorDomain`. +- `'quantize'`: `colorDomain` is divided into `colorRange.length` equal segments, each mapped to one discrete color in `colorRange`. -#### `aggregation` (string, optional) {#aggregation} +#### `colorDomain` (number[2], optional) {#colordomain} -* Default: 'SUM' +* Default: `null` (auto) -Defines the type of aggregation operation, valid values are 'SUM', 'MEAN', 'MIN' and 'MAX'. When no value or an invalid value is set, 'SUM' is used as aggregation. +If not provided, the layer will set `colorDomain` to the +actual min, max values from all cells at run time. -* SUM : Grid cell contains sum of all weights that fall into it. -* MEAN : Grid cell contains mean of all weights that fall into it. -* MIN : Grid cell contains minimum of all weights that fall into it. -* MAX : Grid cell contains maximum of all weights that fall into it. +By providing a `colorDomain`, you can control how a value is represented by color. This is useful when you want to render different data input with the same color mapping for comparison. +#### `colorRange` (Color[6], optional) {#colorrange} + +* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` + +Specified as an array of 6 colors [color1, color2, ... color6]. 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 is omitted a value of 255 is used. ### Data Accessors @@ -259,6 +270,18 @@ The weight of each object. * If a function is provided, it is called on each object to retrieve its weight. +## Picking + +The [PickingInfo.object](../../developer-guide/interactivity.md#the-pickinginfo-object) field returned by hover/click events of this layer represents an aggregated cell. The object contains the following fields: + +- `col` (number) - Column index of the picked cell, starting from 0 at the left of the viewport. +- `row` (number) - Row index of the picked cell, starting from 0 at the top of the viewport. +- `value` (number) - Aggregated value, as determined by `getWeight` and `aggregation` +- `count` (number) - Number of data points in the picked cell +- `pointIndices` (number[]) - Indices of the data objects in the picked cell. Only available if using CPU aggregation. +- `points` (object[]) - The data objects in the picked cell. Only available if using CPU aggregation and layer data is an array. + + ## Source [modules/aggregation-layers/src/screen-grid-layer](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/screen-grid-layer) diff --git a/docs/api-reference/aggregation-layers/webgl-aggregator.md b/docs/api-reference/aggregation-layers/webgl-aggregator.md new file mode 100644 index 00000000000..027fa01ce3f --- /dev/null +++ b/docs/api-reference/aggregation-layers/webgl-aggregator.md @@ -0,0 +1,83 @@ +# WebGLAggregator + +The `WebGLAggregator` implements the [Aggregator](./aggregator.md) interface by performing aggregation on the GPU. + +## Example + +This example implements an aggregator that makes a [histogram](https://en.wikipedia.org/wiki/Histogram) that calculates "weight" distribution by "position". + +```ts +import {WebGLAggregator} from '@deck.gl/aggregation-layers'; + +const aggregator = new WebGLAggregator(device, { + dimensions: 1, + channelCount: 1, + bufferLayout: [ + {name: 'position', format: 'float32'}, + {name: 'weight', format: 'float32'} + ], + vs: ` + uniform float binSize; + in float position; + in float weight; + void getBin(out int binId) { + binId = int(floor(position / binSize)); + } + void getValue(out float value) { + value = weight; + }` +}); + +const position = new Attribute(device, {id: 'position', size: 1}); +position.setData({value: new Float32Array(...)}); +const weight = new Attribute(device, {id: 'weight', size: 1}); +position.setData({value: new Float32Array(...)}); + +aggregator.setProps({ + pointCount: data.length, + binIdRange: [0, 100], + operations: ['SUM'], + binOptions: { + binSize: 1 + }, + attributes: {position, weight} +}); + +aggregator.update(); +``` + +## Constructor + +```ts +new WebGLAggregator(props); +``` + +Arguments: + +- `dimensions` (number) - size of bin IDs, either 1 or 2 +- `channelCount` (number) - number of channels, up to 3 +- `vs` (string) - vertex shader for the aggregator. Should define the following functions: + + `void getBin(out int binId)`, if dimensions=1 or + `void getBin(out ivec2 binId)`, if dimensions=2 + * `void getValue(out float value)`, if channelCount=1 or + `void getValue(out vec2 value)`, if channelCount=2 or + `void getValue(out vec3 value)`, if channelCount=3 +- `bufferLayout` (object[]) - see [ModelProps](https://github.com/visgl/luma.gl/blob/master/modules/engine/src/model/model.ts) +- `modules` (object[]) - luma.gl shader modules, see [ModelProps](https://github.com/visgl/luma.gl/blob/master/modules/engine/src/model/model.ts) +- `defines` (object) - luma.gl shader defines, see [ModelProps](https://github.com/visgl/luma.gl/blob/master/modules/engine/src/model/model.ts) + +## Props + +Requires all [Aggregator](./aggregator.md#setprops) props, and the following: + +#### `binIdRange` (number[][]) {#binidrange} + +Limits of binId defined as [start, end] for each dimension. Ids less than `start` or larger than or equal to `end` are ignored. + +#### `moduleSettings` (object) {#modulesettings} + +Mapped uniforms for shadertool modules, see [ModelProps](https://github.com/visgl/luma.gl/blob/master/modules/engine/src/model/model.ts) + +## Source + +[modules/aggregation-layers/src/common/aggregator/gpu-aggregator/webgl-aggregator.ts](https://github.com/visgl/deck.gl/tree/master/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/webgl-aggregator.ts) diff --git a/docs/api-reference/core/layer.md b/docs/api-reference/core/layer.md index 2dbef0e5a61..b1f2a52d377 100644 --- a/docs/api-reference/core/layer.md +++ b/docs/api-reference/core/layer.md @@ -62,7 +62,7 @@ deck.gl layers typically expect `data` to be one of the following types: - `Promise`: the resolved value will be used as the value of the `data` prop. - `AsyncIterable`: an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) object that yields data in batches. The default implementation expects each batch to be an array of data objects; one may change this behavior by supplying a custom `dataTransform` callback. -**data.attributes** +##### data.attributes When using a non-iterable `data` object, the object may optionally contain a field `attributes`, if the application wishes to supply binary buffers directly to the layer. This use case is discussed in detail in the [performance developer guide](../../developer-guide/performance.md#supply-attributes-directly). diff --git a/docs/api-reference/layers/README.md b/docs/api-reference/layers/README.md index 745437102e0..2237df04351 100644 --- a/docs/api-reference/layers/README.md +++ b/docs/api-reference/layers/README.md @@ -32,8 +32,6 @@ The [Aggregation Layers](https://www.npmjs.com/package/@deck.gl/aggregation-laye - [ContourLayer](../aggregation-layers/contour-layer.md) - [GridLayer](../aggregation-layers/grid-layer.md) - - [GPUGridLayer](../aggregation-layers/gpu-grid-layer.md) - - [CPUGridLayer](../aggregation-layers/cpu-grid-layer.md) - [HeatmapLayer](../aggregation-layers/heatmap-layer.md) - [HexagonLayer](../aggregation-layers/hexagon-layer.md) - [ScreenGridLayer](../aggregation-layers/screen-grid-layer.md) diff --git a/docs/table-of-contents.json b/docs/table-of-contents.json index f0a86a37ae7..bf838a26e70 100644 --- a/docs/table-of-contents.json +++ b/docs/table-of-contents.json @@ -95,10 +95,8 @@ "api-reference/layers/bitmap-layer", "api-reference/layers/column-layer", "api-reference/aggregation-layers/contour-layer", - "api-reference/aggregation-layers/cpu-grid-layer", "api-reference/geo-layers/geohash-layer", "api-reference/layers/geojson-layer", - "api-reference/aggregation-layers/gpu-grid-layer", "api-reference/geo-layers/great-circle-layer", "api-reference/layers/grid-cell-layer", "api-reference/aggregation-layers/grid-layer", @@ -210,6 +208,17 @@ "type": "category", "label": "Submodule API Reference", "items": [ + { + "type": "category", + "label": "@deck.gl/extensions", + "items": [ + "api-reference/aggregation-layers/overview", + "api-reference/aggregation-layers/aggregation-layer", + "api-reference/aggregation-layers/aggregator", + "api-reference/aggregation-layers/cpu-aggregator", + "api-reference/aggregation-layers/webgl-aggregator" + ] + }, { "type": "category", "label": "@deck.gl/arcgis", diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 61071b6c02e..634b33e7a17 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -2,6 +2,16 @@ ## Upgrading to v9.1 +### Aggregation layers + +Breaking changes: + +- `GPUGridLayer` is removed. Use `GridLayer` with `gpuAggregation: true`. +- `CPUGridLayer` is removed. Use `GridLayer` with `gpuAggregation: false`. +- If you are supplying a custom [haxagonAggregator](./api-reference/aggregation-layers/hexagon-layer.md#haxagonaggregator) to `HexagonLayer`, its function signiature has changed. +- `HexagonLayer`'s sub layer is renamed `-cells` and is no longer a vanilla `ColumnLayer`. If you were overriding the sub layer class with your own, please open a Discussion on GitHub for support. + + ### LightingEffect - `PointLight.attenuation` was previously ignored. To retain old behavior, use the default (`[1, 0, 0]`). diff --git a/docs/whats-new.md b/docs/whats-new.md index d682243cd9a..2a64994a012 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -2,6 +2,30 @@ This page contains highlights of each deck.gl release. Also check our [vis.gl blog](https://medium.com/vis-gl) for news about new releases and features in deck.gl. +## deck.gl v9.1 + +Release date: TBD (targeting September 2024) + +### WebGPU readiness + +- luma.gl v9.1 +- All layers migrated to UBO + +### Aggregation layers upgrade + +v9.1 restores the GPU aggregation functionality that was temporarily disabled in v9.0. It brings a major refactor of the aggregation layers, with full TypeScript and unit test coverage. +A new generic `Aggregator` interface makes it much easier to support custom aggregation operations. The current implementations of this interface include `CPUAggregator` and `WebGLAggregator`, with `WebGPUAggregator` on the roadmap. + +Highlights: + +- `GridLayer` now utilized both CPU and GPU aggregators in the same code path. +- `HexagonLayer` now supports GPU aggregation. Enable with `gpuAggregation: true`. +- `GridLayer` and `HexagonLayer` can use `*ScaleType`, `*UpperPercentile`, `*LowerPercentile`, `onSet*Domain` props along with GPU aggregation. +- `GridLayer` and `HexagonLayer` now support non-geospatial views. +- New picking info types for each aggregation layer. + +See [upgrade guide](./upgrade-guide.md) for more details. + ## deck.gl v9.0 Release date: March 21, 2024 diff --git a/modules/aggregation-layers/src/common/aggregator/aggregator.ts b/modules/aggregation-layers/src/common/aggregator/aggregator.ts index 6029e6343af..7899a465b58 100644 --- a/modules/aggregation-layers/src/common/aggregator/aggregator.ts +++ b/modules/aggregation-layers/src/common/aggregator/aggregator.ts @@ -41,10 +41,11 @@ export type AggregatedBin = { * - The number of data points * - The group that each data point belongs to, by mapping each data point to a _binId_ (integer or array of integers) * - The values to aggregate, by mapping each data point in each channel to one _value_ (number) - * - The method (_aggregationOperation_) to reduce a list of values to one number, such as SUM + * - The method (_operation_) to reduce a list of values to one number, such as SUM * * And yields the following outputs: - * - The aggregated values (_result_) as a list of numbers for each channel, comprised of one number per bin + * - A list of _binId_ that data points get sorted into + * - The aggregated values (_result_) as a list of numbers, comprised of one number per bin per channel * - The [min, max] among all aggregated values (_domain_) for each channel * */ diff --git a/modules/aggregation-layers/src/common/aggregator/cpu-aggregator/cpu-aggregator.ts b/modules/aggregation-layers/src/common/aggregator/cpu-aggregator/cpu-aggregator.ts index d666e31b92d..f208334bdbc 100644 --- a/modules/aggregation-layers/src/common/aggregator/cpu-aggregator/cpu-aggregator.ts +++ b/modules/aggregation-layers/src/common/aggregator/cpu-aggregator/cpu-aggregator.ts @@ -9,9 +9,7 @@ export type CPUAggregatorProps = { /** Size of bin IDs */ dimensions: number; /** Accessor to map each data point to a bin ID. - * If dimensions=1, bin ID should be a number; - * If dimensions>1, bin ID should be an array with [dimensions] elements; - * The data point will be skipped if bin ID is null. + * Bin ID should be an array with [dimensions] elements; or null if the data point should be skipped */ getBin: VertexAccessor; /** Accessor to map each data point to a weight value, defined per channel */ diff --git a/modules/aggregation-layers/src/grid-layer/grid-layer.ts b/modules/aggregation-layers/src/grid-layer/grid-layer.ts index 84c525fcaea..b5049b7d9f3 100644 --- a/modules/aggregation-layers/src/grid-layer/grid-layer.ts +++ b/modules/aggregation-layers/src/grid-layer/grid-layer.ts @@ -244,13 +244,13 @@ type _GridLayerProps = { }; export type GridLayerPickingInfo = PickingInfo<{ - /** Column index of the picked cell, starting from 0 at the left of the viewport */ + /** Column index of the picked cell */ col: number; - /** Row index of the picked cell, starting from 0 at the top of the viewport */ + /** Row index of the picked cell */ row: number; - /** Aggregated color value */ + /** Aggregated color value, as determined by `getColorWeight` and `colorAggregation` */ colorValue: number; - /** Aggregated elevation value */ + /** Aggregated elevation value, as determined by `getElevationWeight` and `elevationAggregation` */ elevationValue: number; /** Number of data points in the picked cell */ count: number; diff --git a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts index d26bcad39e3..640900243ba 100644 --- a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts +++ b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts @@ -83,7 +83,7 @@ type _HexagonLayerProps = { /** * Custom accessor to retrieve a hexagonal bin index from each data object. * Not supported by GPU aggregation. - * @default d3-hexbin + * @default null */ hexagonAggregator?: ((position: number[], radius: number) => [number, number]) | null; @@ -246,13 +246,13 @@ type _HexagonLayerProps = { }; export type HexagonLayerPickingInfo = PickingInfo<{ - /** Column index of the picked cell, starting from 0 at the left of the viewport */ + /** Column index of the picked cell */ col: number; - /** Row index of the picked cell, starting from 0 at the top of the viewport */ + /** Row index of the picked cell */ row: number; - /** Aggregated color value */ + /** Aggregated color value, as determined by `getColorWeight` and `colorAggregation` */ colorValue: number; - /** Aggregated elevation value */ + /** Aggregated elevation value, as determined by `getElevationWeight` and `elevationAggregation` */ elevationValue: number; /** Number of data points in the picked cell */ count: number;