@abi-software/flatmap-viewer 2.5.0-a.2 → 2.5.1

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.
@@ -18,66 +18,134 @@ limitations under the License.
18
18
 
19
19
  ******************************************************************************/
20
20
 
21
- import {colord} from 'colord'
22
21
  import {ArcLayer} from '@deck.gl/layers'
23
22
  import {MapboxOverlay as DeckOverlay} from '@deck.gl/mapbox'
24
- import {PathStyleExtension} from '@deck.gl/extensions'
23
+ import {Model, Geometry} from '@luma.gl/core'
24
+ import GL from '@luma.gl/constants'
25
25
 
26
26
  //==============================================================================
27
27
 
28
- import {pathColour} from '../pathways'
28
+ import {pathColourArray} from '../pathways'
29
29
 
30
- // Should this be in `pathways.js` ??
31
- function pathColourRGB(pathType, alpha=255)
32
- //=========================================
30
+
31
+ //==============================================================================
32
+
33
+ const transparencyCheck = '|| length(vColor) == 0.0'
34
+
35
+ class ArcMapLayer extends ArcLayer
33
36
  {
34
- const rgb = colord(pathColour(pathType)).toRgb()
35
- return [rgb.r, rgb.g, rgb.b, alpha]
37
+ static layerName = 'ArcMapLayer'
38
+
39
+ #dirty = false
40
+ #pathData
41
+
42
+ constructor(...args)
43
+ {
44
+ super(...args)
45
+ this.#pathData = new Map([...this.props.data].map(ann => [+ann.featureId, ann]))
46
+ this.#pathData.forEach(ann => delete ann['hidden'])
47
+ }
48
+
49
+ get featureIds()
50
+ //==============
51
+ {
52
+ return [...this.#pathData.keys()]
53
+ }
54
+
55
+ getShaders()
56
+ //==========
57
+ {
58
+ const shaders = super.getShaders()
59
+ shaders.fs = `#version 300 es\n${shaders.fs}`
60
+ .replace('isValid == 0.0', `isValid == 0.0 ${transparencyCheck}`)
61
+ shaders.vs = `#version 300 es\n${shaders.vs}`
62
+ return shaders
63
+ }
64
+ setDataProperty(featureId, key, enabled)
65
+ //======================================
66
+ {
67
+ const properties = this.#pathData.get(+featureId)
68
+ if (properties) {
69
+ if (!(key in properties) || properties[key] !== enabled) {
70
+ properties[key] = enabled
71
+ this.#dirty = true
72
+ }
73
+ }
74
+ }
75
+
76
+ redraw(force=false)
77
+ //=================
78
+ {
79
+ if (force || this.#dirty) {
80
+ this.internalState.changeFlags.dataChanged = true
81
+ this.setNeedsUpdate()
82
+ this.#dirty = false
83
+ }
84
+ }
36
85
  }
37
86
 
38
87
  //==============================================================================
39
88
 
40
- /*
41
- TODO:
42
-
43
- * Dashed paths
44
- * Control by SCKAN status
45
- * Control by taxon visibility
46
- * Control by visible layer
89
+ const makeDashedTriangles = ` float alpha = floor(fract(float(gl_VertexID)/12.0)+0.5);
90
+ if (vColor.a != 0.0) vColor.a *= alpha;
91
+ `
47
92
 
93
+ class ArcDashedLayer extends ArcMapLayer
94
+ {
95
+ static layerName = 'ArcDashedLayer'
48
96
 
49
- Also see https://alasarr.github.io/deck.gl/docs/api-reference/core/layer#updatetriggers
97
+ constructor(...args)
98
+ {
99
+ super(...args)
100
+ }
50
101
 
102
+ getShaders()
103
+ //==========
104
+ {
105
+ const shaders = super.getShaders()
106
+ shaders.vs = shaders.vs.replace('DECKGL_FILTER_COLOR(', `${makeDashedTriangles}\n DECKGL_FILTER_COLOR(`)
107
+ return shaders
108
+ }
51
109
 
110
+ _getModel(gl)
111
+ //===========
52
112
  {
53
- "featureId": 37,
54
- "id": "ilxtr_neuron-type-keast-10",
55
- "kind": "sensory",
56
- "label": "L6-S1 sensory neuron innervating bladder",
57
- "models": "ilxtr:neuron-type-keast-10",
58
- "sckan": true,
59
- "source": "https://apinatomy.org/uris/models/keast-bladder",
60
- "taxons": ["NCBITaxon:10116"],
61
- "tile-layer": "pathways",
62
- "type": "line",
63
- "bounds": [1.5454426659162825, -1.6254174813389017, 1.7478459571498208, -1.3632864333949712],
64
- "markerPosition": [1.6466443115330516, -1.4943519573669364],
65
- "geometry": "LineString",
66
- "layer": "neural-routes",
67
- "pathStartPosition": [1.7478459571498208, -1.3632864333949712],
68
- "pathEndPosition": [1.5454426659162825, -1.6254174813389017]
113
+ const {numSegments} = this.props
114
+ let positions = []
115
+ for (let i = 0; i < numSegments; i++) {
116
+ positions = positions.concat([i, 1, 0, i, -1, 0, i+1, 1, 0,
117
+ i, -1, 0, i+1, 1, 0, i+1, -1, 0])
118
+ }
119
+ const model = new Model(gl, {
120
+ ...this.getShaders(),
121
+ id: this.props.id,
122
+ geometry: new Geometry({
123
+ drawMode: GL.TRIANGLES,
124
+ attributes: {
125
+ positions: new Float32Array(positions)
126
+ }
127
+ }),
128
+ isInstanced: true,
129
+ })
130
+ model.setUniforms({numSegments: numSegments})
131
+ return model
69
132
  }
70
- */
133
+ }
71
134
 
72
135
  //==============================================================================
73
136
 
74
137
  export class Paths3DLayer
75
138
  {
139
+ #arcLayers = new Map()
76
140
  #deckOverlay = null
141
+ #dimmed = false
77
142
  #enabled = false
143
+ #featureToLayer = new Map()
144
+ #knownTypes = []
78
145
  #map
79
146
  #pathData
80
147
  #pathManager
148
+ #pathStyles
81
149
  #ui
82
150
 
83
151
  constructor(flatmap, ui)
@@ -86,85 +154,182 @@ export class Paths3DLayer
86
154
  this.#map = flatmap.map
87
155
  this.#pathManager = ui.pathManager
88
156
  this.#pathManager.addWatcher(this.#pathStateChanged.bind(this))
89
- this.#pathData = [...flatmap.annotations.values()]
90
- .filter(ann => ann['tile-layer'] === 'pathways'
91
- && ann['geometry'] === 'LineString'
92
- && 'type' in ann && ann['type'].startsWith('line')
93
- && 'kind' in ann // && !ann['kind'].includes('arterial') && !ann['kind'].includes('venous')
94
- && 'pathStartPosition' in ann
95
- && 'pathEndPosition' in ann)
157
+ this.#pathData = new Map([...flatmap.annotations.values()]
158
+ .filter(ann => ann['tile-layer'] === 'pathways'
159
+ && ann['geometry'] === 'LineString'
160
+ && 'type' in ann && ann['type'].startsWith('line')
161
+ && 'kind' in ann
162
+ && 'pathStartPosition' in ann
163
+ && 'pathEndPosition' in ann)
164
+ .map(ann => [ann.featureId, ann]))
165
+ this.#pathStyles = new Map(this.#pathManager.pathStyles().map(s => [s.type, s]))
166
+ this.#knownTypes = [...this.#pathStyles.keys()].filter(t => t !== 'other')
96
167
  }
97
168
 
98
169
  enable(enable=true)
99
170
  //=================
100
171
  {
101
172
  if (enable && !this.#enabled) {
102
- this.#setDeckOverlay()
173
+ this.#setupDeckOverlay()
103
174
  this.#map.addControl(this.#deckOverlay)
104
175
  } else if (!enable && this.#enabled) {
105
176
  if (this.#deckOverlay) {
106
177
  this.#map.removeControl(this.#deckOverlay)
178
+ this.#deckOverlay.finalize()
107
179
  this.#deckOverlay = null
108
180
  }
181
+ this.#featureToLayer = new Map()
109
182
  }
110
183
  this.#enabled = enable
111
184
  }
112
185
 
113
- #pathStateChanged()
114
- //=================
186
+ queryFeaturesAtPoint(point)
187
+ //=========================
115
188
  {
116
189
  if (this.#deckOverlay) {
117
- this.#map.removeControl(this.#deckOverlay)
118
- this.#setDeckOverlay()
119
- this.#map.addControl(this.#deckOverlay)
190
+ return this.#deckOverlay
191
+ .pickMultipleObjects(point)
192
+ .map(o => this.#makeMapFeature(o.object))
193
+ }
194
+ return []
195
+ }
196
+
197
+ redraw(force=false)
198
+ //=================
199
+ {
200
+ for (const layer of this.#arcLayers.values()) {
201
+ layer.redraw(force)
202
+ }
203
+ }
204
+
205
+ removeFeatureState(featureId, key)
206
+ //================================
207
+ {
208
+ const layer = this.#featureToLayer.get(+featureId)
209
+ if (layer) {
210
+ layer.setDataProperty(featureId, key, false)
211
+ layer.redraw()
120
212
  }
121
213
  }
122
214
 
123
- #setDeckOverlay()
215
+ setFeatureState(featureId, state)
216
+ //===============================
217
+ {
218
+ const layer = this.#featureToLayer.get(+featureId)
219
+ if (layer) {
220
+ for (const [key, value] of Object.entries(state)) {
221
+ layer.setDataProperty(featureId, key, value)
222
+ }
223
+ layer.redraw()
224
+ }
225
+ }
226
+
227
+ setPaint(options)
124
228
  //===============
125
229
  {
126
- this.#deckOverlay = new DeckOverlay({
127
- layers: [
128
- // Need to have two layers, one with dashed lines, one without
129
- //
130
- // Better, one layer per pathType and set/clear layer.visible...
131
- //
132
- new ArcLayer({
133
- id: 'arcs',
134
- data: this.#pathData
135
- .filter(f => this.#pathManager.pathTypeEnabled(f.kind)),
136
- pickable: true,
137
- autoHighlight: true,
138
- numSegments: 100,
139
- onHover: (i, e) => {
140
- //console.log('hover', i, e)
141
- if (i.object) {
142
- const lineFeatureId = +i.object.featureId
143
- this.#ui.activateFeature(this.#ui.mapFeature(lineFeatureId))
144
- for (const featureId of this.#pathManager.lineFeatureIds([lineFeatureId])) {
145
- if (+featureId !== lineFeatureId) {
146
- this.#ui.activateFeature(this.#ui.mapFeature(featureId))
147
- }
148
- }
149
- }
150
- },
151
- onClick: (i, e) => {
152
- console.log('click', i, e)
153
- },
154
- // Styles
155
- getSourcePosition: f => f.pathStartPosition,
156
- getTargetPosition: f => f.pathEndPosition,
157
- getSourceColor: f => pathColourRGB(f.kind, 160),
158
- getTargetColor: f => pathColourRGB(f.kind, 160),
159
- highlightColor: o => pathColourRGB(o.object.kind),
160
- getWidth: 3,
161
- extensions: [new PathStyleExtension({dash: true})],
162
- getDashArray: [3, 2],
163
- dashJustified: true,
164
- dashGapPickable: true,
230
+ const dimmed = options.dimmed || false
231
+ if (this.#dimmed !== dimmed) {
232
+ this.#dimmed = dimmed
233
+ this.redraw(true)
234
+ }
235
+ }
236
+
237
+ #addArcLayer(pathType)
238
+ //====================
239
+ {
240
+ const layer = this.#pathStyles.get(pathType).dashed
241
+ ? new ArcDashedLayer(this.#layerOptions(pathType))
242
+ : new ArcMapLayer(this.#layerOptions(pathType))
243
+ layer.featureIds.forEach(id => this.#featureToLayer.set(+id, layer))
244
+ this.#arcLayers.set(pathType, layer)
245
+ }
246
+
247
+ #removeArcLayer(pathType)
248
+ //=======================
249
+ {
250
+ const layer = this.#arcLayers.get(pathType)
251
+ if (layer) {
252
+ layer.featureIds.forEach(id => this.#featureToLayer.delete(+id))
253
+ this.#arcLayers.delete(pathType)
254
+ }
255
+ }
256
+
257
+ #pathColour(properties)
258
+ //=====================
259
+ {
260
+ if (properties.hidden) {
261
+ return [0, 0, 0, 0]
262
+ }
263
+ return pathColourArray(properties.kind,
264
+ properties.active || properties.selected ? 255
265
+ : this.#dimmed ? 20 : 160)
266
+ }
267
+
268
+ #pathStateChanged(changes={})
269
+ //===========================
270
+ {
271
+ if (this.#deckOverlay) {
272
+ if ('pathType' in changes) {
273
+ const pathType = changes.pathType
274
+ const enabled = this.#pathManager.pathTypeEnabled(pathType)
275
+ if (enabled && !this.#arcLayers.has(pathType)) {
276
+ this.#addArcLayer(pathType)
277
+ } else if (!enabled && this.#arcLayers.has(pathType)) {
278
+ this.#removeArcLayer(pathType)
279
+ }
280
+ this.#deckOverlay.setProps({
281
+ layers: [...this.#arcLayers.values()]
165
282
  })
166
- ],
167
- getTooltip: ({object}) => object && object.label
283
+ }
284
+ }
285
+ }
286
+
287
+
288
+ #layerOptions(pathType)
289
+ //=====================
290
+ {
291
+ const pathData = [...this.#pathData.values()]
292
+ .filter(ann => (this.#knownTypes.includes(ann.kind) && (ann.kind === pathType)
293
+ || !this.#knownTypes.includes(ann.kind) && (pathType === 'other')))
294
+ return {
295
+ id: `arc-${pathType}`,
296
+ data: pathData,
297
+ pickable: true,
298
+ autoHighlight: true,
299
+ numSegments: 400,
300
+ // Styles
301
+ getSourcePosition: f => f.pathStartPosition,
302
+ getTargetPosition: f => f.pathEndPosition,
303
+ getSourceColor: this.#pathColour.bind(this),
304
+ getTargetColor: this.#pathColour.bind(this),
305
+ highlightColor: o => this.#pathColour(o.object),
306
+ opacity: 1.0,
307
+ getWidth: 3,
308
+ }
309
+ }
310
+
311
+ #makeMapFeature(pickedObject)
312
+ //===========================
313
+ {
314
+ // Mock up a map vector feature
315
+ return {
316
+ id: pickedObject.featureId,
317
+ source: 'vector-tiles',
318
+ sourceLayer: `${pickedObject.layer}_${pickedObject['tile-layer']}`,
319
+ properties: pickedObject,
320
+ arc3dLayer: true
321
+ }
322
+ }
323
+
324
+ #setupDeckOverlay()
325
+ //=================
326
+ {
327
+ [...this.#pathStyles.values()].filter(style => this.#pathManager.pathTypeEnabled(style.type))
328
+ .forEach(style => this.#addArcLayer(style.type))
329
+ this.#deckOverlay = new DeckOverlay({
330
+ layers: [...this.#arcLayers.values()],
168
331
  })
169
332
  }
170
333
  }
334
+
335
+ //==============================================================================
package/src/main.js CHANGED
@@ -62,6 +62,7 @@ export async function standaloneViewer(map_endpoint=null, options={})
62
62
  showId: true,
63
63
  showPosition: false,
64
64
  standalone: true,
65
+ annotator: true,
65
66
  }, options);
66
67
 
67
68
  function loadMap(id, taxon, sex)
package/src/pathways.js CHANGED
@@ -20,6 +20,8 @@ limitations under the License.
20
20
 
21
21
  'use strict';
22
22
 
23
+ import {colord} from 'colord'
24
+
23
25
  //==============================================================================
24
26
 
25
27
  import { reverseMap } from './utils';
@@ -49,15 +51,18 @@ const PATH_TYPES = [
49
51
  { type: "error", label: "Paths with errors or warnings", colour: "#FF0", enabled: false}
50
52
  ];
51
53
 
54
+ const PathTypeMap = new Map(PATH_TYPES.map(t => [t.type, t]))
55
+
52
56
  export const PATH_STYLE_RULES =
53
57
  PATH_TYPES.flatMap(pathType => [['==', ['get', 'kind'], pathType.type], pathType.colour]);
54
58
 
55
- export const PATH_COLOURS =
56
- Object.fromEntries(PATH_TYPES.flatMap(pathType => [[pathType.type, pathType.colour]]));
57
-
58
- export function pathColour(pathType)
59
+ export function pathColourArray(pathType, alpha=255)
60
+ //==================================================
59
61
  {
60
- return PATH_COLOURS[pathType] || '#FF0';
62
+ const rgb = colord(PathTypeMap.has(pathType)
63
+ ? PathTypeMap.get(pathType).colour
64
+ : '#FF0').toRgb()
65
+ return [rgb.r, rgb.g, rgb.b, alpha]
61
66
  }
62
67
 
63
68
  //==============================================================================
@@ -162,6 +167,21 @@ export class PathManager
162
167
  }
163
168
  }
164
169
 
170
+ pathStyles()
171
+ //==========
172
+ {
173
+ const styles = []
174
+ for (const mapType of this.pathTypes()) {
175
+ const defn = PathTypeMap.get(mapType.type)
176
+ styles.push({
177
+ type: defn.type,
178
+ colour: defn.colour,
179
+ dashed: defn.dashed || false
180
+ })
181
+ }
182
+ return styles
183
+ }
184
+
165
185
  pathTypes()
166
186
  //=========
167
187
  {
@@ -335,7 +355,7 @@ export class PathManager
335
355
  this.__ui.enableFeature(featureId, enable, force);
336
356
  }
337
357
  this.__pathtypeEnabled[pathType] = enable;
338
- this.#notifyWatchers()
358
+ this.#notifyWatchers({pathType})
339
359
  }
340
360
  }
341
361
 
@@ -390,11 +410,11 @@ export class PathManager
390
410
  this.#watcherCallbacks.delete(watcherId)
391
411
  }
392
412
 
393
- #notifyWatchers()
394
- //===============
413
+ #notifyWatchers(changes={})
414
+ //=========================
395
415
  {
396
416
  for (const callback of this.#watcherCallbacks.values()) {
397
- callback()
417
+ callback(changes)
398
418
  }
399
419
  }
400
420
  }