@vitessce/gl 3.5.8 → 3.5.10
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/dist/{deflate-cfdd9e56.js → deflate-b17d64b1.js} +1 -1
- package/dist/{index-06f9318c.js → index-e113c88f.js} +10425 -5957
- package/dist/index.js +14 -13
- package/dist/{jpeg-45af9337.js → jpeg-a43f1407.js} +1 -1
- package/dist/{lerc-956ec4f4.js → lerc-d05b8280.js} +1 -1
- package/dist/{lzw-5698c290.js → lzw-cae4aaf9.js} +1 -1
- package/dist/{packbits-110b757d.js → packbits-965c5d70.js} +1 -1
- package/dist/{raw-2ccdfb85.js → raw-3c07a927.js} +1 -1
- package/dist/{webimage-f56b4603.js → webimage-36b3a167.js} +1 -1
- package/dist-tsc/ContourLayerWithText.d.ts +83 -0
- package/dist-tsc/ContourLayerWithText.d.ts.map +1 -0
- package/dist-tsc/ContourLayerWithText.js +220 -0
- package/dist-tsc/SelectionLayer.js +4 -4
- package/dist-tsc/index.d.ts +1 -0
- package/dist-tsc/index.js +1 -0
- package/package.json +14 -10
- package/src/ContourLayerWithText.js +254 -0
- package/src/SelectionLayer.js +4 -4
- package/src/index.js +1 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { ContourLayer } from '@deck.gl/aggregation-layers';
|
|
2
|
+
import { LineLayer, TextLayer } from '@deck.gl/layers';
|
|
3
|
+
import { point as turfPoint, polygons } from '@turf/helpers';
|
|
4
|
+
import { getGeom } from '@turf/invariant';
|
|
5
|
+
import { flattenReduce } from '@turf/meta';
|
|
6
|
+
import { union } from '@turf/union';
|
|
7
|
+
import { area } from '@turf/area';
|
|
8
|
+
import { distance } from '@turf/distance';
|
|
9
|
+
import { range } from 'lodash-es';
|
|
10
|
+
import { AXIS_FONT_FAMILY } from './heatmap-constants.js';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const DEFAULT_THRESHOLD = 1;
|
|
14
|
+
|
|
15
|
+
const defaultProps = {
|
|
16
|
+
// for text
|
|
17
|
+
obsSetPath: { type: 'object' },
|
|
18
|
+
sampleSetPath: { type: 'object' },
|
|
19
|
+
circleInfo: { type: 'object' },
|
|
20
|
+
circlePointSet: { type: 'object' },
|
|
21
|
+
obsSetLabelsVisible: { type: 'boolean' },
|
|
22
|
+
obsSetLabelSize: { type: 'number', min: 1, max: 100, value: 12 },
|
|
23
|
+
|
|
24
|
+
// grid aggregation
|
|
25
|
+
cellSize: { type: 'number', min: 1, max: 1000, value: 1000 },
|
|
26
|
+
getPosition: { type: 'accessor', value: x => x.position },
|
|
27
|
+
getWeight: { type: 'accessor', value: 1 },
|
|
28
|
+
gpuAggregation: true,
|
|
29
|
+
aggregation: 'SUM',
|
|
30
|
+
|
|
31
|
+
// contour lines
|
|
32
|
+
contours: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
value: [{ threshold: DEFAULT_THRESHOLD }],
|
|
35
|
+
optional: true,
|
|
36
|
+
compare: 3,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
zOffset: 0.005,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const MIN_AREA_THRESHOLD = 10;
|
|
43
|
+
const MAX_NUM_VERTICES = 36;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the polygon with the maximum area,
|
|
47
|
+
* and also return its area value.
|
|
48
|
+
* @param {{contour, vertices}[]} levelToUse The
|
|
49
|
+
* contourPolygons array filtered to a given level.
|
|
50
|
+
* @returns {[number, object]} A tuple of
|
|
51
|
+
* [area value, Turf polygon object].
|
|
52
|
+
*/
|
|
53
|
+
function getMaxAreaPolygon(levelToUse) {
|
|
54
|
+
if (levelToUse.length === 0) {
|
|
55
|
+
return [0, null];
|
|
56
|
+
}
|
|
57
|
+
const levelPolygons = levelToUse.map(d => (
|
|
58
|
+
[
|
|
59
|
+
// The vertices are 3D, but we only need 2D.
|
|
60
|
+
[
|
|
61
|
+
...d.vertices.map(v => ([v[0], v[1]])),
|
|
62
|
+
// Need four vertices (not 3), so repeat the first vertex.
|
|
63
|
+
[d.vertices[0][0], d.vertices[0][1]],
|
|
64
|
+
],
|
|
65
|
+
]
|
|
66
|
+
));
|
|
67
|
+
const levelPolygonsUnion = union(polygons(levelPolygons));
|
|
68
|
+
const [maxAreaValue, maxAreaPolygon] = flattenReduce(
|
|
69
|
+
levelPolygonsUnion,
|
|
70
|
+
(previousValue, currentFeature) => {
|
|
71
|
+
if (getGeom(currentFeature).type === 'Polygon') {
|
|
72
|
+
const currArea = area(currentFeature);
|
|
73
|
+
if (currArea >= previousValue[0]) {
|
|
74
|
+
return [currArea, currentFeature];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return previousValue;
|
|
78
|
+
},
|
|
79
|
+
// Initial value
|
|
80
|
+
[0, null],
|
|
81
|
+
);
|
|
82
|
+
return [maxAreaValue, maxAreaPolygon];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the area, polygon, and level associated with
|
|
87
|
+
* the "polygon of maximum area". (Not actually maximum
|
|
88
|
+
* among all levels but the max within a given level)
|
|
89
|
+
* @param {{contour, vertices}[]} contourPolygons The
|
|
90
|
+
* contourPolygons value within state.contourData
|
|
91
|
+
* @returns {[number, object, number]} A tuple of
|
|
92
|
+
* [area value, Turf polygon object, level index].
|
|
93
|
+
*/
|
|
94
|
+
function getMaxAreaPolygonAndLevel(contourPolygons) {
|
|
95
|
+
const l2 = contourPolygons?.filter(d => d.contour.i === 2) ?? [];
|
|
96
|
+
const [l2area, l2polygon] = getMaxAreaPolygon(l2);
|
|
97
|
+
if (Math.log10(l2area) >= MIN_AREA_THRESHOLD) {
|
|
98
|
+
return [l2area, l2polygon, 2];
|
|
99
|
+
}
|
|
100
|
+
const l1 = contourPolygons?.filter(d => d.contour.i === 1) ?? [];
|
|
101
|
+
const [l1area, l1polygon] = getMaxAreaPolygon(l1);
|
|
102
|
+
if (Math.log10(l1area) >= MIN_AREA_THRESHOLD) {
|
|
103
|
+
return [l1area, l1polygon, 1];
|
|
104
|
+
}
|
|
105
|
+
const l0 = contourPolygons?.filter(d => d.contour.i === 0) ?? [];
|
|
106
|
+
const [l0area, l0polygon] = getMaxAreaPolygon(l0);
|
|
107
|
+
return [l0area, l0polygon, 0];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Reference: https://github.com/visgl/deck.gl/blob/v8.9.36/modules/aggregation-layers/src/contour-layer/contour-layer.ts
|
|
111
|
+
export default class ContourLayerWithText extends ContourLayer {
|
|
112
|
+
/**
|
|
113
|
+
* Construct line and text layers to render.
|
|
114
|
+
* @returns {object[]} Array of DeckGL layers.
|
|
115
|
+
*/
|
|
116
|
+
getLineAndTextLayers() {
|
|
117
|
+
const lineAndTextLayers = [];
|
|
118
|
+
|
|
119
|
+
const { contourPolygons } = this.state.contourData;
|
|
120
|
+
const { obsSetPath, contours, circleInfo, circlePointSet, obsSetLabelSize } = this.props;
|
|
121
|
+
|
|
122
|
+
if (!circleInfo) {
|
|
123
|
+
return lineAndTextLayers;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const obsSetName = obsSetPath?.at(-1);
|
|
127
|
+
const [maxAreaValue, maxAreaPolygon, levelI] = getMaxAreaPolygonAndLevel(contourPolygons);
|
|
128
|
+
|
|
129
|
+
// Get a circle polygon which outlines the whole plot.
|
|
130
|
+
const { center, steps, polygon: circlePolygon } = circleInfo;
|
|
131
|
+
|
|
132
|
+
if (maxAreaValue > 0 && maxAreaPolygon) {
|
|
133
|
+
let minDist = Infinity;
|
|
134
|
+
let minCirclePoint;
|
|
135
|
+
let minCirclePointI;
|
|
136
|
+
let minPolygonPoint;
|
|
137
|
+
|
|
138
|
+
const numVertices = maxAreaPolygon.geometry.coordinates[0].length;
|
|
139
|
+
|
|
140
|
+
// Only consider a maximum of 36 vertices from the polygon,
|
|
141
|
+
// since it may be complex with many vertices.
|
|
142
|
+
const polygonVertices = numVertices > MAX_NUM_VERTICES
|
|
143
|
+
? range(MAX_NUM_VERTICES).map(
|
|
144
|
+
i => maxAreaPolygon
|
|
145
|
+
.geometry
|
|
146
|
+
.coordinates[0][Math.floor(i * numVertices / MAX_NUM_VERTICES)],
|
|
147
|
+
)
|
|
148
|
+
: [...maxAreaPolygon.geometry.coordinates[0]];
|
|
149
|
+
|
|
150
|
+
// Find the pair (vertex on circle, vertex on contourPolygon) which
|
|
151
|
+
// have the shortest distance.
|
|
152
|
+
circlePolygon.geometry.coordinates[0].forEach((circleCoord, circlePointI) => {
|
|
153
|
+
const circlePoint = turfPoint(circleCoord);
|
|
154
|
+
polygonVertices.forEach((polyCoord) => {
|
|
155
|
+
const polyPoint = turfPoint(polyCoord);
|
|
156
|
+
const dist = distance(circlePoint, polyPoint);
|
|
157
|
+
if (dist < minDist) {
|
|
158
|
+
minDist = dist;
|
|
159
|
+
minCirclePoint = circleCoord;
|
|
160
|
+
minCirclePointI = circlePointI;
|
|
161
|
+
minPolygonPoint = polyCoord;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// If this circle vertex has already been used, then its index
|
|
167
|
+
// will be in the circlePointSet, so we instead try the subsequent
|
|
168
|
+
// vertex until we find one that is unused
|
|
169
|
+
// (or until the set is full, in which case we must reuse a vertex).
|
|
170
|
+
while (circlePointSet.size < steps && circlePointSet.has(minCirclePointI)) {
|
|
171
|
+
const nextCirclePointI = (minCirclePointI + 1) % steps;
|
|
172
|
+
minCirclePointI = nextCirclePointI;
|
|
173
|
+
}
|
|
174
|
+
circlePointSet.add(minCirclePointI);
|
|
175
|
+
minCirclePoint = circlePolygon.geometry.coordinates[0][minCirclePointI];
|
|
176
|
+
|
|
177
|
+
// Compute the angle formed by the line
|
|
178
|
+
// (from the circle's center point).
|
|
179
|
+
const angleRadians = Math.atan2(
|
|
180
|
+
minCirclePoint[1] - center[1],
|
|
181
|
+
minCirclePoint[0] - center[0],
|
|
182
|
+
);
|
|
183
|
+
let angleDegrees = angleRadians * 180 / Math.PI;
|
|
184
|
+
|
|
185
|
+
// Create the line and text layers.
|
|
186
|
+
lineAndTextLayers.push(new LineLayer({
|
|
187
|
+
id: `line-${obsSetName}`,
|
|
188
|
+
data: [
|
|
189
|
+
{
|
|
190
|
+
from: minPolygonPoint,
|
|
191
|
+
to: minCirclePoint,
|
|
192
|
+
color: contours[levelI].color,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
getColor: d => d.color,
|
|
196
|
+
getSourcePosition: d => d.from,
|
|
197
|
+
getTargetPosition: d => d.to,
|
|
198
|
+
getWidth: levelI + 0.5,
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
// Determine textAnchor based on angle formed on circle.
|
|
202
|
+
let textAnchor = 'start';
|
|
203
|
+
if (angleDegrees < 0) {
|
|
204
|
+
angleDegrees += 360;
|
|
205
|
+
}
|
|
206
|
+
if (angleDegrees > 90 && angleDegrees < 270) {
|
|
207
|
+
angleDegrees -= 180;
|
|
208
|
+
textAnchor = 'end';
|
|
209
|
+
}
|
|
210
|
+
lineAndTextLayers.push(new TextLayer({
|
|
211
|
+
id: `text-${obsSetName}`,
|
|
212
|
+
data: [
|
|
213
|
+
{
|
|
214
|
+
label: obsSetName,
|
|
215
|
+
to: minCirclePoint,
|
|
216
|
+
color: contours[levelI].color,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
getPosition: d => d.to,
|
|
220
|
+
getText: d => d.label,
|
|
221
|
+
getColor: d => d.color,
|
|
222
|
+
getSize: obsSetLabelSize,
|
|
223
|
+
getAngle: -angleDegrees,
|
|
224
|
+
getTextAnchor: textAnchor,
|
|
225
|
+
getAlignmentBaseline: 'center',
|
|
226
|
+
fontFamily: AXIS_FONT_FAMILY,
|
|
227
|
+
fontWeight: 'normal',
|
|
228
|
+
// maxWidth: 80 * obsSetLabelSize,
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return lineAndTextLayers;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Overrides the ContourLayer renderLayers function,
|
|
237
|
+
* so that the custom text/line layers can be
|
|
238
|
+
* appended to the contour line/polygon layers.
|
|
239
|
+
* @returns {object[]} Array of DeckGL layers.
|
|
240
|
+
*/
|
|
241
|
+
renderLayers() {
|
|
242
|
+
const { obsSetLabelsVisible } = this.props;
|
|
243
|
+
const contourLayers = super.renderLayers();
|
|
244
|
+
const lineAndTextLayers = obsSetLabelsVisible
|
|
245
|
+
? this.getLineAndTextLayers()
|
|
246
|
+
: [];
|
|
247
|
+
return [
|
|
248
|
+
...contourLayers,
|
|
249
|
+
...lineAndTextLayers,
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
ContourLayerWithText.layerName = 'ContourLayerWithText';
|
|
254
|
+
ContourLayerWithText.defaultProps = defaultProps;
|
package/src/SelectionLayer.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
// https://github.com/uber/nebula.gl/blob/8e9c2ec8d7cf4ca7050909ed826eb847d5e2cd9c/modules/layers/src/layers/selection-layer.js
|
|
5
5
|
import { CompositeLayer } from 'deck.gl';
|
|
6
6
|
import { polygon as turfPolygon, point as turfPoint } from '@turf/helpers';
|
|
7
|
-
import booleanWithin from '@turf/boolean-within';
|
|
8
|
-
import booleanContains from '@turf/boolean-contains';
|
|
9
|
-
import booleanOverlap from '@turf/boolean-overlap';
|
|
10
|
-
import booleanPointInPolygon from '@turf/boolean-point-in-polygon';
|
|
7
|
+
import { booleanWithin } from '@turf/boolean-within';
|
|
8
|
+
import { booleanContains } from '@turf/boolean-contains';
|
|
9
|
+
import { booleanOverlap } from '@turf/boolean-overlap';
|
|
10
|
+
import { booleanPointInPolygon } from '@turf/boolean-point-in-polygon';
|
|
11
11
|
import { ScatterplotLayer } from '@deck.gl/layers';
|
|
12
12
|
import { SELECTION_TYPE } from 'nebula.gl';
|
|
13
13
|
import { EditableGeoJsonLayer } from '@nebula.gl/layers';
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { default as ScaledExpressionExtension } from './ScaledExpressionExtensio
|
|
|
16
16
|
export { default as SelectionExtension } from './SelectionExtension/index.js';
|
|
17
17
|
export { default as BitmaskLayer } from './BitmaskLayer.js';
|
|
18
18
|
export { default as BitmaskLayerBeta } from './BitmaskLayerBeta.js';
|
|
19
|
-
|
|
19
|
+
export { default as ContourLayerWithText } from './ContourLayerWithText.js';
|
|
20
20
|
|
|
21
21
|
export {
|
|
22
22
|
TILE_SIZE, MAX_ROW_AGG, MIN_ROW_AGG,
|