Skip to content

Commit

Permalink
Add extensions module (#3306)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Jul 2, 2019
1 parent 5e694fa commit b89988b
Show file tree
Hide file tree
Showing 17 changed files with 567 additions and 111 deletions.
175 changes: 175 additions & 0 deletions docs/extensions/data-filter-extension.md
@@ -0,0 +1,175 @@

# DataFilterExtension (experimental)

The `DataFilterExtension` adds GPU-based data filtering functionalities to layers. It allows the layer to show/hide objects based on user-defined properties. This extension provides a significantly more performant alternative to filtering the data array on the CPU.

> Note: This extension does not work with all deck.gl layers. See "limitations" below.
```js
import {GeoJsonLayer} from '@deck.gl/layers';
import {DataFilterExtension} from '@deck.gl/extensions';

const layer = new GeoJsonLayer({
id: 'geojson-layer',
data: GEOJSON,

// props from GeoJsonLayer
getFillColor: [160, 160, 180],
getLineColor: [0, 0, 0],
getLineWidth: 10,

// props added by DataFilterExtension
getFilterValue: f => f.properties.timeOfDay, // in seconds
filterRange: [43200, 46800], // 12:00 - 13:00

// Define extensions
extensions: [new DataFilterExtension({filterSize: 1})]
});
```

## Installation

To install the dependencies from NPM:

```bash
npm install deck.gl
# or
npm install @deck.gl/core @deck.gl/layers @deck.gl/extensions
```

```js
import {DataFilterExtension} from '@deck.gl/extensions';
new DataFilterExtension({});
```

To use pre-bundled scripts:

```html
<script src="https://unpkg.com/deck.gl@~7.0.0/dist.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/@deck.gl/core@~7.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@~7.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/extensions@~7.0.0/dist.min.js"></script>
```

```js
new deck.DataFilterExtension({});
```

## Constructor

```js
new DataFilterExtension({filterSize});
```

* `filterSize` (Number) - the size of the filter (number of columns to filter by). The data filter can show/hide data based on 1-4 numeric properties of each object. Default `1`.


## Layer Properties

When added to a layer via the `extensions` prop, the `DataFilterExtension` adds the following properties to the layer:

##### `getFilterValue` ([Function](/docs/developer-guide/using-layers.md#accessors))

Called to retrieve the value for each object that it will be filtered by. Returns either a number (if `filterSize: 1`) or an array.

For example, consider data in the following format:

```json
[
{"timestamp": 0.1, "coordinates": [-122.45, 37.78], "speed": 13.3},
...
]
```

To filter by timestamp:

```js
new ScatterplotLayer({
data,
getPosition: d => d.coordinates,
getFilterValue: d => d.timestamp,
filterRange: [0, 1],
extensions: [new DataFilterExtension({filterSize: 1})]
})
```

To filter by both timestamp and speed:

```js
new ScatterplotLayer({
data,
getPosition: d => d.coordinates,
getFilterValue: d => [d.timestamp, d.speed],
filterRange: [[0, 1], [10, 20]],
extensions: [new DataFilterExtension({filterSize: 2})]
})
```

Note that all filtered values are uploaded as 32-bit floating numbers, so certain values e.g. raw unix epoch time may not be accurately represented. You may test the validity of a timestamp by calling `Math.fround(t)` to check if there would be any loss of precision.


##### `filterRange` (Array)

The bounds which defines whether an object should be rendered. If an object's filtered value is within the bounds, the object will be rendered; otherwise it will be hidden. This prop can be updated on user input or animation with very little cost.

Format:

* If `filterSize` is `1`: `[min, max]`
* If `filterSize` is `2` to `4`: `[[min0, max0], [min1, max1], ...]` for each filtered property, respectively.


##### `filterSoftRange` (Array, optional)

* Default: `null`

If specified, objects will be faded in/out instead of abruptly shown/hiden. When the filtered value is outside of the bounds defined by `filterSoftRange` but still within the bounds defined by `filterRange`, the object will be rendered as "faded." See `filterTransformSize` and `filterTransformColor` for additional control over this behavior.

```js
new ScatterplotLayer({
data,
getPosition: d => d.coordinates,
getFilterValue: d => d.timestamp,
filterRange: [0, 1],
filterSoftRange: [0.2, 0.8],
filterTransformSize: true,
filterTransformColor: true,
extensions: [new DataFilterExtension({filterSize: 1})]
})
```

Format:

* If `filterSize` is `1`: `[softMin, softMax]`
* If `filterSize` is `2` to `4`: `[[softMin0, softMax0], [softMin1, softMax1], ...]` for each filtered property, respectively.


##### `filterTransformSize` (Boolean, optional)

* Default: `true`

When an object is "faded", manipulate its size so that it appears smaller or thinner. Only works if `filterSoftRange` is specified.


##### `filterTransformColor` (Boolean, optional)

* Default: `true`

When an object is "faded", manipulate its opacity so that it appears more translucent. Only works if `filterSoftRange` is specified.


##### `filterEnabled` (Boolean, optional)

* Default: `true`

Enable/disable the data filter. If the data filter is disabled, all objects are rendered.


## Limitations

The `DataFilterExtension` does not work with any layer from the `@deck.gl/aggregation-layers` module.


## Source

[modules/extensions/src/data-filter](https://github.com/uber/deck.gl/tree/master/modules/extensions/src/data-filter)
5 changes: 5 additions & 0 deletions modules/extensions/README.md
@@ -0,0 +1,5 @@
# @deck.gl/extensions

Optional functionalities for deck.gl layers.

See [deck.gl](http://deck.gl) for documentation.
12 changes: 12 additions & 0 deletions modules/extensions/bundle.js
@@ -0,0 +1,12 @@
const Extensions = require('./src');

/* global window, global */
const _global = typeof window === 'undefined' ? global : window;
const deck = _global.deck || {};

// Check if peer dependencies are included
if (!deck.Layer) {
throw new Error('@deck.gl/core is not found');
}

module.exports = Object.assign(deck, Extensions);
35 changes: 35 additions & 0 deletions modules/extensions/package.json
@@ -0,0 +1,35 @@
{
"name": "@deck.gl/extensions",
"description": "Plug-and-play functionalities for deck.gl layers",
"license": "MIT",
"version": "7.2.0-alpha.1",
"publishConfig": {
"access": "public"
},
"keywords": [
"webgl",
"visualization",
"overlay",
"layer"
],
"repository": {
"type": "git",
"url": "https://github.com/uber/deck.gl.git"
},
"main": "dist/es5/index.js",
"module": "dist/esm/index.js",
"esnext": "dist/es6/index.js",
"files": [
"dist",
"src",
"dist.min.js"
],
"sideEffects": false,
"scripts": {
"build-bundle": "webpack --config ../../scripts/bundle.config.js",
"prepublishOnly": "npm run build-bundle && npm run build-bundle -- --env.dev"
},
"peerDependencies": {
"@deck.gl/core": "^7.0.0"
}
}
82 changes: 82 additions & 0 deletions modules/extensions/src/data-filter/data-filter.js
@@ -0,0 +1,82 @@
// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import {LayerExtension} from '@deck.gl/core';
import shaderModule from './shader-module';

const defaultProps = {
getFilterValue: {type: 'accessor', value: 0},

filterEnabled: true,
filterRange: [-1, 1],
filterSoftRange: null,
filterTransformSize: true,
filterTransformColor: true
};

const DATA_TYPE_FROM_SIZE = {
1: 'float',
2: 'vec2',
3: 'vec3',
4: 'vec4'
};

export default class DataFilterExtension extends LayerExtension {
constructor({filterSize = 1} = {}) {
if (!DATA_TYPE_FROM_SIZE[filterSize]) {
throw new Error('filterSize out of range');
}

super({filterSize});
}

getShaders(extension) {
const {filterSize} = extension.opts;
return {
modules: [shaderModule],
defines: {
DATAFILTER_TYPE: DATA_TYPE_FROM_SIZE[filterSize]
}
};
}

initializeState(context, extension) {
const attributeManager = this.getAttributeManager();
if (attributeManager) {
attributeManager.add({
filterValues: {
size: extension.opts.filterSize,
accessor: 'getFilterValue',
shaderAttributes: {
filterValues: {
divisor: 0
},
instanceFilterValues: {
divisor: 1
}
}
}
});
}
}
}

DataFilterExtension.extensionName = 'DataFilterExtension';
DataFilterExtension.defaultProps = defaultProps;

0 comments on commit b89988b

Please sign in to comment.