@vitessce/gl 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/BitmaskLayer.js +132 -0
- package/dist/HeatmapBitmapLayer.js +123 -0
- package/dist/HeatmapCompositeTextLayer.js +129 -0
- package/dist/PaddedExpressionHeatmapBitmapLayer.js +129 -0
- package/dist/PixelatedBitmapLayer.js +26 -0
- package/dist/ScaledExpressionExtension/ScaledExpressionExtension.js +105 -0
- package/dist/ScaledExpressionExtension/index.js +2 -0
- package/dist/ScaledExpressionExtension/shader-module.js +32 -0
- package/dist/SelectionExtension/SelectionExtension.js +31 -0
- package/dist/SelectionExtension/index.js +2 -0
- package/dist/SelectionExtension/shader-module.js +20 -0
- package/dist/SelectionLayer.js +167 -0
- package/dist/bitmask-layer-shaders.js +90 -0
- package/dist/constants.js +11 -0
- package/dist/deck.js +4 -0
- package/dist/glsl/index.js +340 -0
- package/dist/heatmap-bitmap-layer-shaders.js +109 -0
- package/dist/heatmap-constants.js +27 -0
- package/dist/index.js +17 -0
- package/dist/layer-controller.js +109 -0
- package/dist/luma.js +2 -0
- package/dist/padded-expression-heatmap-bitmap-layer-shaders.js +143 -0
- package/dist/selection-utils.js +92 -0
- package/dist/spatial.js +281 -0
- package/dist/spatial.test.js +8 -0
- package/dist/viv.js +9 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import GL from '@luma.gl/constants'; // eslint-disable-line import/no-extraneous-dependencies
|
|
2
|
+
import { project32, picking } from '@deck.gl/core'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import { Texture2D, isWebGL2 } from '@luma.gl/core';
|
|
4
|
+
import { XRLayer } from '@hms-dbmi/viv';
|
|
5
|
+
import { fs, vs } from './bitmask-layer-shaders';
|
|
6
|
+
import { GLSL_COLORMAPS, GLSL_COLORMAP_DEFAULT, COLORMAP_SHADER_PLACEHOLDER, } from './constants';
|
|
7
|
+
function padWithDefault(arr, defaultValue, padWidth) {
|
|
8
|
+
const newArr = [...arr];
|
|
9
|
+
for (let i = 0; i < padWidth; i += 1) {
|
|
10
|
+
newArr.push(defaultValue);
|
|
11
|
+
}
|
|
12
|
+
return newArr;
|
|
13
|
+
}
|
|
14
|
+
const defaultProps = {
|
|
15
|
+
hoveredCell: { type: 'number', value: null, compare: true },
|
|
16
|
+
cellColorData: { type: 'object', value: null, compare: true },
|
|
17
|
+
colormap: { type: 'string', value: GLSL_COLORMAP_DEFAULT, compare: true },
|
|
18
|
+
expressionData: { type: 'object', value: null, compare: true },
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A BitmapLayer that performs aggregation in the fragment shader,
|
|
22
|
+
* and renders its texture from a Uint8Array rather than an ImageData.
|
|
23
|
+
*/
|
|
24
|
+
export default class BitmaskLayer extends XRLayer {
|
|
25
|
+
// eslint-disable-next-line class-methods-use-this
|
|
26
|
+
getShaders() {
|
|
27
|
+
const { colormap } = this.props;
|
|
28
|
+
return {
|
|
29
|
+
fs,
|
|
30
|
+
vs,
|
|
31
|
+
modules: [project32, picking],
|
|
32
|
+
defines: {
|
|
33
|
+
[COLORMAP_SHADER_PLACEHOLDER]: GLSL_COLORMAPS.includes(colormap)
|
|
34
|
+
? colormap
|
|
35
|
+
: GLSL_COLORMAP_DEFAULT,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
updateState({ props, oldProps, changeFlags }) {
|
|
40
|
+
super.updateState({ props, oldProps, changeFlags });
|
|
41
|
+
if (props.cellColorData !== oldProps.cellColorData) {
|
|
42
|
+
this.setColorTexture();
|
|
43
|
+
}
|
|
44
|
+
if (props.expressionData !== oldProps.expressionData) {
|
|
45
|
+
const { expressionData, cellTexHeight, cellTexWidth } = this.props;
|
|
46
|
+
const expressionTex = this.dataToTexture(expressionData, cellTexWidth, cellTexHeight);
|
|
47
|
+
this.setState({ expressionTex });
|
|
48
|
+
}
|
|
49
|
+
if (props.colormap !== oldProps.colormap) {
|
|
50
|
+
const { gl } = this.context;
|
|
51
|
+
if (this.state.model) {
|
|
52
|
+
this.state.model.delete();
|
|
53
|
+
}
|
|
54
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
55
|
+
this.setState({ model: this._getModel(gl) });
|
|
56
|
+
this.getAttributeManager().invalidateAll();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
setColorTexture() {
|
|
60
|
+
const { cellColorData: data, cellTexHeight: height, cellTexWidth: width, } = this.props;
|
|
61
|
+
const colorTex = new Texture2D(this.context.gl, {
|
|
62
|
+
width,
|
|
63
|
+
height,
|
|
64
|
+
// Only use Float32 so we don't have to write two shaders
|
|
65
|
+
data,
|
|
66
|
+
// we don't want or need mimaps
|
|
67
|
+
mipmaps: false,
|
|
68
|
+
parameters: {
|
|
69
|
+
// NEAREST for integer data
|
|
70
|
+
[GL.TEXTURE_MIN_FILTER]: GL.NEAREST,
|
|
71
|
+
[GL.TEXTURE_MAG_FILTER]: GL.NEAREST,
|
|
72
|
+
// CLAMP_TO_EDGE to remove tile artifacts
|
|
73
|
+
[GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE,
|
|
74
|
+
[GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE,
|
|
75
|
+
},
|
|
76
|
+
format: GL.RGB,
|
|
77
|
+
dataFormat: GL.RGB,
|
|
78
|
+
type: GL.UNSIGNED_BYTE,
|
|
79
|
+
});
|
|
80
|
+
this.setState({ colorTex });
|
|
81
|
+
}
|
|
82
|
+
draw(opts) {
|
|
83
|
+
const { uniforms } = opts;
|
|
84
|
+
const { channelsVisible, hoveredCell, colorScaleLo, colorScaleHi, isExpressionMode, } = this.props;
|
|
85
|
+
const { textures, model, colorTex, expressionTex, } = this.state;
|
|
86
|
+
// Render the image
|
|
87
|
+
if (textures && model && colorTex) {
|
|
88
|
+
model
|
|
89
|
+
.setUniforms(Object.assign({}, uniforms, {
|
|
90
|
+
hovered: hoveredCell || 0,
|
|
91
|
+
colorTex,
|
|
92
|
+
expressionTex,
|
|
93
|
+
colorTexHeight: colorTex.height,
|
|
94
|
+
colorTexWidth: colorTex.width,
|
|
95
|
+
channelsVisible: padWithDefault(channelsVisible, false,
|
|
96
|
+
// There are six texture entries on the shaders
|
|
97
|
+
6 - channelsVisible.length),
|
|
98
|
+
uColorScaleRange: [colorScaleLo, colorScaleHi],
|
|
99
|
+
uIsExpressionMode: isExpressionMode,
|
|
100
|
+
...textures,
|
|
101
|
+
}))
|
|
102
|
+
.draw();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* This function creates textures from the data
|
|
107
|
+
*/
|
|
108
|
+
dataToTexture(data, width, height) {
|
|
109
|
+
const isWebGL2On = isWebGL2(this.context.gl);
|
|
110
|
+
return new Texture2D(this.context.gl, {
|
|
111
|
+
width,
|
|
112
|
+
height,
|
|
113
|
+
// Only use Float32 so we don't have to write two shaders
|
|
114
|
+
data: new Float32Array(data),
|
|
115
|
+
// we don't want or need mimaps
|
|
116
|
+
mipmaps: false,
|
|
117
|
+
parameters: {
|
|
118
|
+
// NEAREST for integer data
|
|
119
|
+
[GL.TEXTURE_MIN_FILTER]: GL.NEAREST,
|
|
120
|
+
[GL.TEXTURE_MAG_FILTER]: GL.NEAREST,
|
|
121
|
+
// CLAMP_TO_EDGE to remove tile artifacts
|
|
122
|
+
[GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE,
|
|
123
|
+
[GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE,
|
|
124
|
+
},
|
|
125
|
+
format: isWebGL2On ? GL.R32F : GL.LUMINANCE,
|
|
126
|
+
dataFormat: isWebGL2On ? GL.RED : GL.LUMINANCE,
|
|
127
|
+
type: GL.FLOAT,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
BitmaskLayer.layerName = 'BitmaskLayer';
|
|
132
|
+
BitmaskLayer.defaultProps = defaultProps;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import GL from '@luma.gl/constants'; // eslint-disable-line import/no-extraneous-dependencies
|
|
2
|
+
import { _mergeShaders, project32, picking } from '@deck.gl/core'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import { BitmapLayer } from '@deck.gl/layers'; // eslint-disable-line import/no-extraneous-dependencies
|
|
4
|
+
import { Texture2D } from '@luma.gl/core';
|
|
5
|
+
import { PIXELATED_TEXTURE_PARAMETERS, TILE_SIZE } from './heatmap-constants';
|
|
6
|
+
import { GLSL_COLORMAPS, GLSL_COLORMAP_DEFAULT, COLORMAP_SHADER_PLACEHOLDER, } from './constants';
|
|
7
|
+
import { vertexShader, fragmentShader } from './heatmap-bitmap-layer-shaders';
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
image: { type: 'object', value: null, async: true },
|
|
10
|
+
colormap: { type: 'string', value: GLSL_COLORMAP_DEFAULT, compare: true },
|
|
11
|
+
bounds: { type: 'array', value: [1, 0, 0, 1], compare: true },
|
|
12
|
+
aggSizeX: { type: 'number', value: 8.0, compare: true },
|
|
13
|
+
aggSizeY: { type: 'number', value: 8.0, compare: true },
|
|
14
|
+
colorScaleLo: { type: 'number', value: 0.0, compare: true },
|
|
15
|
+
colorScaleHi: { type: 'number', value: 1.0, compare: true },
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A BitmapLayer that performs aggregation in the fragment shader,
|
|
19
|
+
* and renders its texture from a Uint8Array rather than an ImageData.
|
|
20
|
+
*/
|
|
21
|
+
export default class HeatmapBitmapLayer extends BitmapLayer {
|
|
22
|
+
/**
|
|
23
|
+
* Copy of getShaders from Layer (grandparent, parent of BitmapLayer).
|
|
24
|
+
* Reference: https://github.com/visgl/deck.gl/blob/0afd4e99a6199aeec979989e0c361c97e6c17a16/modules/core/src/lib/layer.js#L302
|
|
25
|
+
* @param {object} shaders
|
|
26
|
+
* @returns {object} Merged shaders.
|
|
27
|
+
*/
|
|
28
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
29
|
+
_getShaders(shaders) {
|
|
30
|
+
this.props.extensions.forEach((extension) => {
|
|
31
|
+
// eslint-disable-next-line no-param-reassign
|
|
32
|
+
shaders = _mergeShaders(shaders, extension.getShaders.call(this, extension));
|
|
33
|
+
});
|
|
34
|
+
return shaders;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Need to override to provide custom shaders.
|
|
38
|
+
*/
|
|
39
|
+
getShaders() {
|
|
40
|
+
const { colormap } = this.props;
|
|
41
|
+
const fragmentShaderWithColormap = GLSL_COLORMAPS.includes(colormap)
|
|
42
|
+
? fragmentShader.replace(COLORMAP_SHADER_PLACEHOLDER, colormap)
|
|
43
|
+
: fragmentShader.replace(COLORMAP_SHADER_PLACEHOLDER, GLSL_COLORMAP_DEFAULT);
|
|
44
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
45
|
+
return this._getShaders({
|
|
46
|
+
vs: vertexShader,
|
|
47
|
+
fs: fragmentShaderWithColormap,
|
|
48
|
+
modules: [project32, picking],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
updateState(args) {
|
|
52
|
+
super.updateState(args);
|
|
53
|
+
this.loadTexture(this.props.image);
|
|
54
|
+
const { props, oldProps } = args;
|
|
55
|
+
if (props.colormap !== oldProps.colormap) {
|
|
56
|
+
const { gl } = this.context;
|
|
57
|
+
// eslint-disable-next-line no-unused-expressions
|
|
58
|
+
this.state.model?.delete();
|
|
59
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
60
|
+
this.state.model = this._getModel(gl);
|
|
61
|
+
this.getAttributeManager().invalidateAll();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Need to override to provide additional uniform values.
|
|
66
|
+
* Simplified by removing video-related code.
|
|
67
|
+
* Reference: https://github.com/visgl/deck.gl/blob/0afd4e99a6199aeec979989e0c361c97e6c17a16/modules/layers/src/bitmap-layer/bitmap-layer.js#L173
|
|
68
|
+
* @param {*} opts
|
|
69
|
+
*/
|
|
70
|
+
draw(opts) {
|
|
71
|
+
const { uniforms } = opts;
|
|
72
|
+
const { bitmapTexture, model } = this.state;
|
|
73
|
+
const { aggSizeX, aggSizeY, colorScaleLo, colorScaleHi, } = this.props;
|
|
74
|
+
// Render the image
|
|
75
|
+
if (bitmapTexture && model) {
|
|
76
|
+
model
|
|
77
|
+
.setUniforms(Object.assign({}, uniforms, {
|
|
78
|
+
uBitmapTexture: bitmapTexture,
|
|
79
|
+
uTextureSize: [TILE_SIZE, TILE_SIZE],
|
|
80
|
+
uAggSize: [aggSizeX, aggSizeY],
|
|
81
|
+
uColorScaleRange: [colorScaleLo, colorScaleHi],
|
|
82
|
+
}))
|
|
83
|
+
.draw();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Need to override to provide the custom DEFAULT_TEXTURE_PARAMETERS
|
|
88
|
+
* object.
|
|
89
|
+
* Simplified by removing video-related code.
|
|
90
|
+
* Reference: https://github.com/visgl/deck.gl/blob/0afd4e99a6199aeec979989e0c361c97e6c17a16/modules/layers/src/bitmap-layer/bitmap-layer.js#L218
|
|
91
|
+
* @param {Uint8Array} image
|
|
92
|
+
*/
|
|
93
|
+
loadTexture(image) {
|
|
94
|
+
const { gl } = this.context;
|
|
95
|
+
if (this.state.bitmapTexture) {
|
|
96
|
+
this.state.bitmapTexture.delete();
|
|
97
|
+
}
|
|
98
|
+
if (image instanceof Texture2D) {
|
|
99
|
+
this.setState({
|
|
100
|
+
bitmapTexture: image,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else if (image) {
|
|
104
|
+
this.setState({
|
|
105
|
+
bitmapTexture: new Texture2D(gl, {
|
|
106
|
+
data: image,
|
|
107
|
+
mipmaps: false,
|
|
108
|
+
parameters: PIXELATED_TEXTURE_PARAMETERS,
|
|
109
|
+
// Each color contains a single luminance value.
|
|
110
|
+
// When sampled, rgb are all set to this luminance, alpha is 1.0.
|
|
111
|
+
// Reference: https://luma.gl/docs/api-reference/webgl/texture#texture-formats
|
|
112
|
+
format: GL.LUMINANCE,
|
|
113
|
+
dataFormat: GL.LUMINANCE,
|
|
114
|
+
type: GL.UNSIGNED_BYTE,
|
|
115
|
+
width: TILE_SIZE,
|
|
116
|
+
height: TILE_SIZE,
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
HeatmapBitmapLayer.layerName = 'HeatmapBitmapLayer';
|
|
123
|
+
HeatmapBitmapLayer.defaultProps = defaultProps;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
import { COORDINATE_SYSTEM, CompositeLayer } from '@deck.gl/core'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import { TextLayer } from '@deck.gl/layers'; // eslint-disable-line import/no-extraneous-dependencies
|
|
4
|
+
import { AXIS_LABEL_TEXT_SIZE, AXIS_TITLE_TEXT_SIZE, AXIS_MARGIN, THEME_TO_TEXT_COLOR, AXIS_FONT_FAMILY, COLOR_BAR_SIZE, } from './heatmap-constants';
|
|
5
|
+
export default class HeatmapCompositeTextLayer extends CompositeLayer {
|
|
6
|
+
_renderAxisTopLayers() {
|
|
7
|
+
const { axisTopLabelData, matrixLeft, width, matrixWidth, viewWidth, theme, targetX, targetY, axisTopTitle, cellWidth, axisOffsetTop, scaleFactor, hideTopLabels, } = this.props;
|
|
8
|
+
const showAxisTopLabels = cellWidth >= AXIS_LABEL_TEXT_SIZE;
|
|
9
|
+
const axisLabelTop = targetY + (axisOffsetTop - AXIS_MARGIN) / 2 / scaleFactor;
|
|
10
|
+
return hideTopLabels ? [] : [
|
|
11
|
+
new TextLayer({
|
|
12
|
+
id: 'axisTopLabels',
|
|
13
|
+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
|
|
14
|
+
data: axisTopLabelData,
|
|
15
|
+
getText: d => d[1],
|
|
16
|
+
getPosition: d => [matrixLeft + ((d[0] + 0.5) / width) * matrixWidth, axisLabelTop],
|
|
17
|
+
getTextAnchor: 'start',
|
|
18
|
+
getColor: () => THEME_TO_TEXT_COLOR[theme],
|
|
19
|
+
getSize: (showAxisTopLabels ? AXIS_LABEL_TEXT_SIZE : 0),
|
|
20
|
+
getAngle: 75,
|
|
21
|
+
fontFamily: AXIS_FONT_FAMILY,
|
|
22
|
+
updateTriggers: {
|
|
23
|
+
getPosition: [axisLabelTop, matrixLeft, matrixWidth, viewWidth],
|
|
24
|
+
getSize: [showAxisTopLabels],
|
|
25
|
+
getColor: [theme],
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
new TextLayer({
|
|
29
|
+
id: 'axisTopTitle',
|
|
30
|
+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
|
|
31
|
+
data: [{ title: axisTopTitle }],
|
|
32
|
+
getText: d => d.title,
|
|
33
|
+
getPosition: [targetX, targetY],
|
|
34
|
+
getTextAnchor: 'middle',
|
|
35
|
+
getColor: () => THEME_TO_TEXT_COLOR[theme],
|
|
36
|
+
getSize: (!showAxisTopLabels ? AXIS_TITLE_TEXT_SIZE : 0),
|
|
37
|
+
getAngle: 0,
|
|
38
|
+
fontFamily: AXIS_FONT_FAMILY,
|
|
39
|
+
updateTriggers: {
|
|
40
|
+
getSize: [showAxisTopLabels],
|
|
41
|
+
getColor: [theme],
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
_renderCornerLayers() {
|
|
47
|
+
const { theme, targetX, targetY, axisOffsetTop, scaleFactor, cellColorLabelsData, axisOffsetLeft, transpose, } = this.props;
|
|
48
|
+
const axisLabelTop = targetY + (axisOffsetTop - AXIS_MARGIN) / 2 / scaleFactor;
|
|
49
|
+
const axisLabelLeft = targetX + (axisOffsetLeft - AXIS_MARGIN) / 2 / scaleFactor;
|
|
50
|
+
return cellColorLabelsData.map(data => (new TextLayer({
|
|
51
|
+
id: `cellColorLabel-${data[0]}`,
|
|
52
|
+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
|
|
53
|
+
data: [data],
|
|
54
|
+
getText: d => d[1],
|
|
55
|
+
getTextAnchor: (transpose ? 'end' : 'start'),
|
|
56
|
+
getAlignmentBaseline: 'top',
|
|
57
|
+
getColor: () => THEME_TO_TEXT_COLOR[theme],
|
|
58
|
+
getSize: AXIS_LABEL_TEXT_SIZE,
|
|
59
|
+
getPosition: d => [
|
|
60
|
+
(transpose
|
|
61
|
+
? axisLabelLeft
|
|
62
|
+
: targetX
|
|
63
|
+
+ ((-cellColorLabelsData.length + d[0] * 2) * COLOR_BAR_SIZE + AXIS_MARGIN)
|
|
64
|
+
/ 2 / scaleFactor),
|
|
65
|
+
(transpose
|
|
66
|
+
? targetY
|
|
67
|
+
+ ((-cellColorLabelsData.length + d[0] * 2) * COLOR_BAR_SIZE + AXIS_MARGIN)
|
|
68
|
+
/ 2 / scaleFactor
|
|
69
|
+
: axisLabelTop),
|
|
70
|
+
],
|
|
71
|
+
getAngle: (transpose ? 0 : 90),
|
|
72
|
+
fontFamily: AXIS_FONT_FAMILY,
|
|
73
|
+
})));
|
|
74
|
+
}
|
|
75
|
+
_renderAxisLeftLayers() {
|
|
76
|
+
const { axisLeftLabelData, matrixTop, height, matrixHeight, viewHeight, theme, axisLeftTitle, targetX, targetY, cellHeight, axisOffsetLeft, scaleFactor, hideLeftLabels, } = this.props;
|
|
77
|
+
const showAxisLeftLabels = cellHeight >= AXIS_LABEL_TEXT_SIZE;
|
|
78
|
+
const axisLabelLeft = targetX + (axisOffsetLeft - AXIS_MARGIN) / 2 / scaleFactor;
|
|
79
|
+
return hideLeftLabels ? [] : [
|
|
80
|
+
new TextLayer({
|
|
81
|
+
id: 'axisLeftLabels',
|
|
82
|
+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
|
|
83
|
+
data: axisLeftLabelData,
|
|
84
|
+
getText: d => d[1],
|
|
85
|
+
getPosition: d => [axisLabelLeft, matrixTop + ((d[0] + 0.5) / height) * matrixHeight],
|
|
86
|
+
getTextAnchor: 'end',
|
|
87
|
+
getColor: () => THEME_TO_TEXT_COLOR[theme],
|
|
88
|
+
getSize: (showAxisLeftLabels ? AXIS_LABEL_TEXT_SIZE : 0),
|
|
89
|
+
getAngle: 0,
|
|
90
|
+
fontFamily: AXIS_FONT_FAMILY,
|
|
91
|
+
updateTriggers: {
|
|
92
|
+
getPosition: [axisLabelLeft, matrixTop, matrixHeight, viewHeight],
|
|
93
|
+
getSize: [showAxisLeftLabels],
|
|
94
|
+
getColor: [theme],
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
new TextLayer({
|
|
98
|
+
id: 'axisLeftTitle',
|
|
99
|
+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
|
|
100
|
+
data: [{ title: axisLeftTitle }],
|
|
101
|
+
getText: d => d.title,
|
|
102
|
+
getPosition: [targetX, targetY],
|
|
103
|
+
getTextAnchor: 'middle',
|
|
104
|
+
getColor: () => THEME_TO_TEXT_COLOR[theme],
|
|
105
|
+
getSize: (!showAxisLeftLabels ? AXIS_TITLE_TEXT_SIZE : 0),
|
|
106
|
+
getAngle: 90,
|
|
107
|
+
fontFamily: AXIS_FONT_FAMILY,
|
|
108
|
+
updateTriggers: {
|
|
109
|
+
getSize: [showAxisLeftLabels],
|
|
110
|
+
getColor: [theme],
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
renderLayers() {
|
|
116
|
+
const { axis } = this.props;
|
|
117
|
+
if (axis === 'left') {
|
|
118
|
+
return this._renderAxisLeftLayers();
|
|
119
|
+
}
|
|
120
|
+
if (axis === 'top') {
|
|
121
|
+
return this._renderAxisTopLayers();
|
|
122
|
+
}
|
|
123
|
+
if (axis === 'corner') {
|
|
124
|
+
return this._renderCornerLayers();
|
|
125
|
+
}
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
HeatmapCompositeTextLayer.layerName = 'HeatmapCompositeTextLayer';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
import GL from '@luma.gl/constants'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import { _mergeShaders, project32, picking } from '@deck.gl/core'; // eslint-disable-line import/no-extraneous-dependencies
|
|
4
|
+
import { BitmapLayer } from '@deck.gl/layers'; // eslint-disable-line import/no-extraneous-dependencies
|
|
5
|
+
import { Texture2D } from '@luma.gl/core';
|
|
6
|
+
import { PIXELATED_TEXTURE_PARAMETERS, TILE_SIZE, DATA_TEXTURE_SIZE } from './heatmap-constants';
|
|
7
|
+
import { GLSL_COLORMAPS, GLSL_COLORMAP_DEFAULT, COLORMAP_SHADER_PLACEHOLDER } from './constants';
|
|
8
|
+
import { vertexShader, fragmentShader } from './padded-expression-heatmap-bitmap-layer-shaders';
|
|
9
|
+
const defaultProps = {
|
|
10
|
+
image: { type: 'object', value: null, async: true },
|
|
11
|
+
colormap: { type: 'string', value: GLSL_COLORMAP_DEFAULT, compare: true },
|
|
12
|
+
bounds: { type: 'array', value: [1, 0, 0, 1], compare: true },
|
|
13
|
+
aggSizeX: { type: 'number', value: 8.0, compare: true },
|
|
14
|
+
aggSizeY: { type: 'number', value: 8.0, compare: true },
|
|
15
|
+
colorScaleLo: { type: 'number', value: 0.0, compare: true },
|
|
16
|
+
colorScaleHi: { type: 'number', value: 1.0, compare: true },
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A BitmapLayer that performs aggregation in the fragment shader,
|
|
20
|
+
* and renders its texture from a Uint8Array rather than an ImageData.
|
|
21
|
+
*/
|
|
22
|
+
export default class PaddedExpressionHeatmapBitmapLayer extends BitmapLayer {
|
|
23
|
+
/**
|
|
24
|
+
* Copy of getShaders from Layer (grandparent, parent of BitmapLayer).
|
|
25
|
+
* Reference: https://github.com/visgl/deck.gl/blob/0afd4e99a6199aeec979989e0c361c97e6c17a16/modules/core/src/lib/layer.js#L302
|
|
26
|
+
* @param {object} shaders
|
|
27
|
+
* @returns {object} Merged shaders.
|
|
28
|
+
*/
|
|
29
|
+
_getShaders(shaders) {
|
|
30
|
+
this.props.extensions.forEach((extension) => {
|
|
31
|
+
// eslint-disable-next-line no-param-reassign
|
|
32
|
+
shaders = _mergeShaders(shaders, extension.getShaders.call(this, extension));
|
|
33
|
+
});
|
|
34
|
+
return shaders;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Need to override to provide custom shaders.
|
|
38
|
+
*/
|
|
39
|
+
getShaders() {
|
|
40
|
+
const { colormap } = this.props;
|
|
41
|
+
const fragmentShaderWithColormap = GLSL_COLORMAPS.includes(colormap)
|
|
42
|
+
? fragmentShader.replace(COLORMAP_SHADER_PLACEHOLDER, colormap)
|
|
43
|
+
: fragmentShader.replace(COLORMAP_SHADER_PLACEHOLDER, GLSL_COLORMAP_DEFAULT);
|
|
44
|
+
return this._getShaders({
|
|
45
|
+
vs: vertexShader,
|
|
46
|
+
fs: fragmentShaderWithColormap,
|
|
47
|
+
modules: [project32, picking],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
updateState(args) {
|
|
51
|
+
super.updateState(args);
|
|
52
|
+
const { props, oldProps } = args;
|
|
53
|
+
if (props.colormap !== oldProps.colormap) {
|
|
54
|
+
const { gl } = this.context;
|
|
55
|
+
// eslint-disable-next-line no-unused-expressions
|
|
56
|
+
this.state.model?.delete();
|
|
57
|
+
this.state.model = this._getModel(gl);
|
|
58
|
+
this.getAttributeManager().invalidateAll();
|
|
59
|
+
}
|
|
60
|
+
if (props.image !== oldProps.image) {
|
|
61
|
+
this.loadTexture(this.props.image);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Need to override to provide additional uniform values.
|
|
66
|
+
* Simplified by removing video-related code.
|
|
67
|
+
* Reference: https://github.com/visgl/deck.gl/blob/0afd4e99a6199aeec979989e0c361c97e6c17a16/modules/layers/src/bitmap-layer/bitmap-layer.js#L173
|
|
68
|
+
* @param {*} opts
|
|
69
|
+
*/
|
|
70
|
+
draw(opts) {
|
|
71
|
+
const { uniforms } = opts;
|
|
72
|
+
const { bitmapTexture, model } = this.state;
|
|
73
|
+
const { aggSizeX, aggSizeY, colorScaleLo, colorScaleHi, origDataSize, tileI, tileJ, numXTiles, numYTiles, } = this.props;
|
|
74
|
+
// Render the image
|
|
75
|
+
if (bitmapTexture && model) {
|
|
76
|
+
model
|
|
77
|
+
.setUniforms(Object.assign({}, uniforms, {
|
|
78
|
+
uBitmapTexture: bitmapTexture,
|
|
79
|
+
uOrigDataSize: origDataSize,
|
|
80
|
+
uReshapedDataSize: [DATA_TEXTURE_SIZE, DATA_TEXTURE_SIZE],
|
|
81
|
+
uTextureSize: [TILE_SIZE, TILE_SIZE],
|
|
82
|
+
uAggSize: [aggSizeX, aggSizeY],
|
|
83
|
+
uColorScaleRange: [colorScaleLo, colorScaleHi],
|
|
84
|
+
tileIJ: [tileI, tileJ],
|
|
85
|
+
dataIJ: [0, 0],
|
|
86
|
+
numTiles: [numXTiles, numYTiles],
|
|
87
|
+
numData: [1, 1],
|
|
88
|
+
}))
|
|
89
|
+
.draw();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Need to override to provide the custom DEFAULT_TEXTURE_PARAMETERS
|
|
94
|
+
* object.
|
|
95
|
+
* Simplified by removing video-related code.
|
|
96
|
+
* Reference: https://github.com/visgl/deck.gl/blob/0afd4e99a6199aeec979989e0c361c97e6c17a16/modules/layers/src/bitmap-layer/bitmap-layer.js#L218
|
|
97
|
+
* @param {Array<Uint8Array>} images
|
|
98
|
+
*/
|
|
99
|
+
loadTexture(image) {
|
|
100
|
+
const { gl } = this.context;
|
|
101
|
+
if (this.state.bitmapTexture) {
|
|
102
|
+
this.state.bitmapTexture.delete();
|
|
103
|
+
}
|
|
104
|
+
if (image && image instanceof Texture2D) {
|
|
105
|
+
this.setState({
|
|
106
|
+
bitmapTexture: image,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else if (image) {
|
|
110
|
+
this.setState({
|
|
111
|
+
bitmapTexture: new Texture2D(gl, {
|
|
112
|
+
data: image,
|
|
113
|
+
mipmaps: false,
|
|
114
|
+
parameters: PIXELATED_TEXTURE_PARAMETERS,
|
|
115
|
+
// Each color contains a single luminance value.
|
|
116
|
+
// When sampled, rgb are all set to this luminance, alpha is 1.0.
|
|
117
|
+
// Reference: https://luma.gl/docs/api-reference/webgl/texture#texture-formats
|
|
118
|
+
format: GL.LUMINANCE,
|
|
119
|
+
dataFormat: GL.LUMINANCE,
|
|
120
|
+
type: GL.UNSIGNED_BYTE,
|
|
121
|
+
width: DATA_TEXTURE_SIZE,
|
|
122
|
+
height: DATA_TEXTURE_SIZE,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
PaddedExpressionHeatmapBitmapLayer.layerName = 'PaddedExpressionHeatmapBitmapLayer';
|
|
129
|
+
PaddedExpressionHeatmapBitmapLayer.defaultProps = defaultProps;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BitmapLayer } from '@deck.gl/layers'; // eslint-disable-line import/no-extraneous-dependencies
|
|
2
|
+
import { CompositeLayer } from '@deck.gl/core'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import { PIXELATED_TEXTURE_PARAMETERS } from './heatmap-constants';
|
|
4
|
+
// These are the same defaultProps as for BitmapLayer.
|
|
5
|
+
const defaultProps = {
|
|
6
|
+
...BitmapLayer.defaultProps,
|
|
7
|
+
image: { type: 'object', value: null, async: true },
|
|
8
|
+
bounds: { type: 'array', value: [1, 0, 0, 1], compare: true },
|
|
9
|
+
desaturate: {
|
|
10
|
+
type: 'number', min: 0, max: 1, value: 0,
|
|
11
|
+
},
|
|
12
|
+
transparentColor: { type: 'color', value: [0, 0, 0, 0] },
|
|
13
|
+
tintColor: { type: 'color', value: [255, 255, 255] },
|
|
14
|
+
};
|
|
15
|
+
export default class PixelatedBitmapLayer extends CompositeLayer {
|
|
16
|
+
renderLayers() {
|
|
17
|
+
const { image } = this.props;
|
|
18
|
+
return new BitmapLayer(this.props, {
|
|
19
|
+
id: `${this.props.id}-wrapped`,
|
|
20
|
+
image,
|
|
21
|
+
textureParameters: PIXELATED_TEXTURE_PARAMETERS,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
PixelatedBitmapLayer.layerName = 'PixelatedBitmapLayer';
|
|
26
|
+
PixelatedBitmapLayer.defaultProps = defaultProps;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
3
|
+
import GL from '@luma.gl/constants';
|
|
4
|
+
import { LayerExtension } from '@deck.gl/core';
|
|
5
|
+
import { GLSL_COLORMAPS, GLSL_COLORMAP_DEFAULT, COLORMAP_SHADER_PLACEHOLDER } from '../constants';
|
|
6
|
+
import module from './shader-module';
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
colormap: { type: 'string', value: GLSL_COLORMAP_DEFAULT, compare: true },
|
|
9
|
+
colorScaleLo: { type: 'number', value: 0.0, compare: true },
|
|
10
|
+
colorScaleHi: { type: 'number', value: 1.0, compare: true },
|
|
11
|
+
isExpressionMode: false,
|
|
12
|
+
getExpressionValue: { type: 'accessor', value: 0 },
|
|
13
|
+
getSelectionState: { type: 'accessor', value: 0.0 },
|
|
14
|
+
};
|
|
15
|
+
export default class ScaledExpressionExtension extends LayerExtension {
|
|
16
|
+
getShaders() {
|
|
17
|
+
const { colormap } = this.props;
|
|
18
|
+
return {
|
|
19
|
+
modules: [module],
|
|
20
|
+
defines: {
|
|
21
|
+
[COLORMAP_SHADER_PLACEHOLDER]: GLSL_COLORMAPS.includes(colormap)
|
|
22
|
+
? colormap
|
|
23
|
+
: GLSL_COLORMAP_DEFAULT,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
updateState({ props, oldProps }) {
|
|
28
|
+
if (props.colormap !== oldProps.colormap) {
|
|
29
|
+
const { gl } = this.context;
|
|
30
|
+
// Normal single model layers, like ScatterplotLayer
|
|
31
|
+
if (this.state.model) {
|
|
32
|
+
// eslint-disable-next-line no-unused-expressions
|
|
33
|
+
this.state.model?.delete();
|
|
34
|
+
this.state.model = this._getModel(gl);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Special handling for PolygonLayer sublayer models.
|
|
38
|
+
if (this.state.models) {
|
|
39
|
+
// eslint-disable-next-line no-unused-expressions
|
|
40
|
+
this.state.models?.forEach(model => model?.delete());
|
|
41
|
+
}
|
|
42
|
+
if (this.state.topModel) {
|
|
43
|
+
// eslint-disable-next-line no-unused-expressions
|
|
44
|
+
this.state.topModel?.delete();
|
|
45
|
+
}
|
|
46
|
+
if (this.state.sideModel) {
|
|
47
|
+
// eslint-disable-next-line no-unused-expressions
|
|
48
|
+
this.state.sideModel?.delete();
|
|
49
|
+
}
|
|
50
|
+
if (this._getModels) {
|
|
51
|
+
this.setState(this._getModels(this.context.gl));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const attributeManager = this.getAttributeManager();
|
|
55
|
+
if (attributeManager) {
|
|
56
|
+
this.getAttributeManager().invalidateAll();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
initializeState() {
|
|
61
|
+
const layer = this.getCurrentLayer();
|
|
62
|
+
// No need to run this on layers that don't have a `draw` call.
|
|
63
|
+
if (layer.isComposite) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const attributeManager = this.getAttributeManager();
|
|
67
|
+
if (attributeManager) {
|
|
68
|
+
// This attributes is the array of expression data needed for
|
|
69
|
+
// coloring cells against the colormap.
|
|
70
|
+
attributeManager.add({
|
|
71
|
+
expressionValue: {
|
|
72
|
+
type: GL.FLOAT,
|
|
73
|
+
size: 1,
|
|
74
|
+
transition: true,
|
|
75
|
+
accessor: 'getExpressionValue',
|
|
76
|
+
defaultValue: 1,
|
|
77
|
+
// PolygonLayer fill needs not-intsanced attribute but
|
|
78
|
+
// ScatterplotLayer and the PolygonLayer stroke needs instanced.
|
|
79
|
+
// So use another attribute's divisor property as a proxy for this divisor.
|
|
80
|
+
divisor: Object.values(attributeManager.attributes)[0].settings.divisor,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
draw() {
|
|
86
|
+
const { colorScaleLo, colorScaleHi, isExpressionMode, } = this.props;
|
|
87
|
+
const { topModel, sideModel, models, model, } = this.state;
|
|
88
|
+
const uniforms = {
|
|
89
|
+
uColorScaleRange: [colorScaleLo, colorScaleHi],
|
|
90
|
+
uIsExpressionMode: isExpressionMode,
|
|
91
|
+
};
|
|
92
|
+
// ScatterplotLayer model
|
|
93
|
+
// eslint-disable-next-line no-unused-expressions
|
|
94
|
+
model?.setUniforms(uniforms);
|
|
95
|
+
// PolygonLayer models from sublayers
|
|
96
|
+
// eslint-disable-next-line no-unused-expressions
|
|
97
|
+
models?.forEach(m => m.setUniforms(uniforms));
|
|
98
|
+
// eslint-disable-next-line no-unused-expressions
|
|
99
|
+
topModel?.setUniforms(uniforms);
|
|
100
|
+
// eslint-disable-next-line no-unused-expressions
|
|
101
|
+
sideModel?.setUniforms(uniforms);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
ScaledExpressionExtension.extensionName = 'ScaledExpressionExtension';
|
|
105
|
+
ScaledExpressionExtension.defaultProps = defaultProps;
|