@abi-software/flatmap-viewer 2.3.0-a.1 → 2.3.0-a.3
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/README.rst +1 -1
- package/package.json +6 -3
- package/src/annotation.js +487 -0
- package/src/controls.js +503 -11
- package/src/editor.js +198 -0
- package/src/flatmap-viewer.js +176 -64
- package/src/info.js +5 -1
- package/src/interactions.js +299 -232
- package/src/layers.js +242 -151
- package/src/main.js +22 -17
- package/src/newcontrols.js +617 -0
- package/src/pathways.js +47 -21
- package/src/search.js +4 -1
- package/src/styling.js +275 -95
- package/src/systems.js +76 -0
- package/src/utils.js +1 -1
- package/static/{flatmap-viewer.css → css/flatmap-viewer.css} +153 -30
- /package/static/{favicon.ico → icons/favicon.ico} +0 -0
package/src/interactions.js
CHANGED
|
@@ -33,16 +33,18 @@ import polylabel from 'polylabel';
|
|
|
33
33
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
|
-
import {
|
|
37
|
-
import {displayedProperties} from './info
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
import {SearchControl} from './search
|
|
43
|
-
import {VECTOR_TILES_SOURCE} from './styling
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
import {Annotator} from './annotation';
|
|
37
|
+
import {displayedProperties, InfoControl} from './info';
|
|
38
|
+
import {LayerManager} from './layers';
|
|
39
|
+
import {PATHWAYS_LAYER, Pathways} from './pathways';
|
|
40
|
+
import {BackgroundControl, LayerControl, NerveControl,
|
|
41
|
+
PathControl, SCKANControl} from './controls';
|
|
42
|
+
import {SearchControl} from './search';
|
|
43
|
+
import {VECTOR_TILES_SOURCE} from './styling';
|
|
44
|
+
import {SystemsControl, SystemsManager} from './systems';
|
|
45
|
+
|
|
46
|
+
import * as pathways from './pathways';
|
|
47
|
+
import * as utils from './utils';
|
|
46
48
|
|
|
47
49
|
//==============================================================================
|
|
48
50
|
|
|
@@ -114,75 +116,90 @@ export class UserInteractions
|
|
|
114
116
|
this.__activeMarker = null;
|
|
115
117
|
this.__lastMarkerId = 900000;
|
|
116
118
|
this.__markerIdByMarker = new Map();
|
|
119
|
+
this.__markerIdByFeatureId = new Map();
|
|
117
120
|
this.__annotationByMarkerId = new Map();
|
|
118
121
|
|
|
119
122
|
// Where to put labels and popups on a feature
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
// MapLibre dynamically sets a transform on marker elements so in
|
|
123
|
-
// order to apply a scale transform we need to create marker icons
|
|
124
|
-
// inside the marker container <div>.
|
|
125
|
-
this._defaultMarkerHTML = new maplibre.Marker().getElement().innerHTML;
|
|
126
|
-
this._simulationMarkerHTML = new maplibre.Marker({color: '#005974'}).getElement().innerHTML;
|
|
123
|
+
this.__markerPositions = new Map();
|
|
127
124
|
|
|
128
125
|
// Fit the map to its initial position
|
|
129
126
|
|
|
130
127
|
flatmap.setInitialPosition();
|
|
131
128
|
|
|
132
|
-
// Add
|
|
133
|
-
|
|
134
|
-
if (flatmap.options.searchable) {
|
|
135
|
-
this._map.addControl(new SearchControl(flatmap));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Show information about features
|
|
129
|
+
// Add and manage our layers
|
|
139
130
|
|
|
140
|
-
|
|
141
|
-
this._infoControl = new InfoControl(flatmap);
|
|
142
|
-
if (flatmap.options.featureInfo) {
|
|
143
|
-
this._map.addControl(this._infoControl);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
131
|
+
this._layerManager = new LayerManager(flatmap);
|
|
146
132
|
|
|
147
|
-
//
|
|
148
|
-
// or by our local controls
|
|
133
|
+
// Path visibility is either controlled externally or by a local control
|
|
149
134
|
|
|
150
135
|
this._pathways = new Pathways(flatmap);
|
|
151
136
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this._map.addControl(new PathControl(flatmap, this._pathways));
|
|
155
|
-
}
|
|
137
|
+
// The path types in this map
|
|
138
|
+
const mapPathTypes = this._pathways.pathTypes();
|
|
156
139
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
140
|
+
// Disable paths that are not initially shown
|
|
141
|
+
for (const path of mapPathTypes) {
|
|
142
|
+
if ('enabled' in path && !path.enabled) {
|
|
143
|
+
this.enablePath(path.type, false);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
160
146
|
|
|
161
|
-
// Flag features that have annotations
|
|
162
|
-
// Also flag those features that are models of something
|
|
147
|
+
// Flag features that have annotations and note which are FC systems
|
|
163
148
|
|
|
149
|
+
this.__systems = new Map();
|
|
164
150
|
for (const [id, ann] of flatmap.annotations) {
|
|
165
151
|
const feature = this.mapFeature_(id);
|
|
166
152
|
if (feature !== undefined) {
|
|
167
153
|
this._map.setFeatureState(feature, { 'annotated': true });
|
|
168
154
|
}
|
|
155
|
+
if (ann['fc-class'] === 'fc-class:System') {
|
|
156
|
+
if (this.__systems.has(ann.name)) {
|
|
157
|
+
this.__systems.get(ann.name).featureIds.push(ann.featureId)
|
|
158
|
+
} else {
|
|
159
|
+
this.__systems.set(ann.name, {
|
|
160
|
+
id: ann.name.replaceAll(' ', '_'),
|
|
161
|
+
colour: ann.colour,
|
|
162
|
+
featureIds: [ ann.featureId ]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
169
166
|
}
|
|
170
167
|
|
|
171
|
-
//
|
|
168
|
+
// Add various controls when running standalone
|
|
169
|
+
|
|
170
|
+
if (flatmap.options.standalone) {
|
|
171
|
+
// Add a control to search annotations if option set
|
|
172
|
+
this._map.addControl(new SearchControl(flatmap));
|
|
173
|
+
|
|
174
|
+
// Show information about features
|
|
175
|
+
this._infoControl = new InfoControl(flatmap);
|
|
176
|
+
this._map.addControl(this._infoControl);
|
|
177
|
+
|
|
178
|
+
// Control background colour (NB. this depends on having map layers created)
|
|
179
|
+
this._map.addControl(new BackgroundControl(flatmap));
|
|
172
180
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this._map.on('contextmenu', this.contextMenuEvent_.bind(this));
|
|
181
|
+
// Add a control to manage our paths
|
|
182
|
+
this._map.addControl(new PathControl(flatmap, mapPathTypes));
|
|
176
183
|
|
|
177
|
-
|
|
184
|
+
// Add a control to manage our layers
|
|
185
|
+
this._map.addControl(new LayerControl(flatmap, this._layerManager));
|
|
178
186
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
this.contextMenuEvent_(e);
|
|
187
|
+
// Add a control for nerve centrelines if they are present
|
|
188
|
+
if (this._pathways.haveCentrelines) {
|
|
189
|
+
this._map.addControl(new NerveControl(flatmap, this._layerManager, {showCentrelines: false}));
|
|
190
|
+
this.enableCentrelines(false);
|
|
184
191
|
}
|
|
185
|
-
|
|
192
|
+
|
|
193
|
+
// SCKAN path and SYSTEMS controls for FC maps
|
|
194
|
+
if (flatmap.options.style === 'functional') {
|
|
195
|
+
this._map.addControl(new SystemsControl(flatmap, this.__systems));
|
|
196
|
+
this._map.addControl(new SCKANControl(flatmap, flatmap.options.layerOptions));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add annotation capabilities
|
|
201
|
+
|
|
202
|
+
this.__annotator = new Annotator(flatmap);
|
|
186
203
|
|
|
187
204
|
// Handle mouse events
|
|
188
205
|
|
|
@@ -198,6 +215,12 @@ export class UserInteractions
|
|
|
198
215
|
this.__pan_zoom_enabled = false;
|
|
199
216
|
}
|
|
200
217
|
|
|
218
|
+
get pathways()
|
|
219
|
+
//============
|
|
220
|
+
{
|
|
221
|
+
return this._pathways;
|
|
222
|
+
}
|
|
223
|
+
|
|
201
224
|
getState()
|
|
202
225
|
//========
|
|
203
226
|
{
|
|
@@ -206,7 +229,7 @@ export class UserInteractions
|
|
|
206
229
|
return {
|
|
207
230
|
center: this._map.getCenter().toArray(),
|
|
208
231
|
zoom: this._map.getZoom(),
|
|
209
|
-
layers: this.
|
|
232
|
+
layers: this.layers
|
|
210
233
|
};
|
|
211
234
|
}
|
|
212
235
|
|
|
@@ -231,17 +254,83 @@ export class UserInteractions
|
|
|
231
254
|
}
|
|
232
255
|
}
|
|
233
256
|
|
|
234
|
-
|
|
235
|
-
|
|
257
|
+
setPaint(options)
|
|
258
|
+
//===============
|
|
236
259
|
{
|
|
237
260
|
this.__colourOptions = options;
|
|
238
|
-
this._layerManager.
|
|
261
|
+
this._layerManager.setPaint(options);
|
|
239
262
|
}
|
|
240
263
|
|
|
241
|
-
|
|
242
|
-
|
|
264
|
+
getLayers()
|
|
265
|
+
//=========
|
|
266
|
+
{
|
|
267
|
+
return this._layerManager.layers;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
enableLayer(layerId, enable=true)
|
|
271
|
+
//===============================
|
|
272
|
+
{
|
|
273
|
+
this._layerManager.activate(layerId, enable);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getSystems()
|
|
277
|
+
//==========
|
|
278
|
+
{
|
|
279
|
+
const systems = [];
|
|
280
|
+
for (const system of this.__systems.values()) {
|
|
281
|
+
systems.push({
|
|
282
|
+
name: system.name,
|
|
283
|
+
colour: system.colour,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return systems;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
enableSystem(systemName, enable=true)
|
|
290
|
+
//===================================
|
|
291
|
+
{
|
|
292
|
+
if (this.__systems.has(systemName)) {
|
|
293
|
+
for (const featureId of this.__systems.get(systemName).featureIds) {
|
|
294
|
+
this.__enableFeatureWithChildren(featureId, enable);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
__enableFeatureWithChildren(featureId, enable=true)
|
|
300
|
+
//=================================================
|
|
301
|
+
{
|
|
302
|
+
const feature = this.mapFeature_(featureId);
|
|
303
|
+
if (feature !== undefined) {
|
|
304
|
+
this.__enableFeature(feature, enable);
|
|
305
|
+
for (const childFeatureId of feature.children) {
|
|
306
|
+
this.__enableFeatureWithChildren(childFeatureId, enable);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
__enableFeatureMarker(featureId, enable=true)
|
|
312
|
+
//===========================================
|
|
313
|
+
{
|
|
314
|
+
const markerId = this.__markerIdByFeatureId.get(+featureId);
|
|
315
|
+
if (markerId !== undefined) {
|
|
316
|
+
const markerDiv = document.getElementById(`marker-${markerId}`);
|
|
317
|
+
if (markerDiv) {
|
|
318
|
+
markerDiv.style.visibility = enable ? 'visible' : 'hidden';
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
__enableFeature(feature, enable=true)
|
|
324
|
+
//===================================
|
|
243
325
|
{
|
|
244
|
-
|
|
326
|
+
if (feature !== undefined) {
|
|
327
|
+
if (enable) {
|
|
328
|
+
this._map.removeFeatureState(feature, 'hidden');
|
|
329
|
+
} else {
|
|
330
|
+
this._map.setFeatureState(feature, { 'hidden': true });
|
|
331
|
+
}
|
|
332
|
+
this.__enableFeatureMarker(feature.id, enable);
|
|
333
|
+
}
|
|
245
334
|
}
|
|
246
335
|
|
|
247
336
|
mapFeature_(featureId)
|
|
@@ -252,11 +341,13 @@ export class UserInteractions
|
|
|
252
341
|
return {
|
|
253
342
|
id: featureId,
|
|
254
343
|
source: VECTOR_TILES_SOURCE,
|
|
255
|
-
sourceLayer: this._flatmap.options.separateLayers
|
|
344
|
+
sourceLayer: (this._flatmap.options.separateLayers
|
|
256
345
|
? `${ann['layer']}_${ann['tile-layer']}`
|
|
257
|
-
: ann['tile-layer']
|
|
346
|
+
: ann['tile-layer']).replaceAll('/', '_'),
|
|
347
|
+
children: ann.children || []
|
|
258
348
|
};
|
|
259
349
|
}
|
|
350
|
+
return undefined;
|
|
260
351
|
}
|
|
261
352
|
|
|
262
353
|
featureSelected_(featureId)
|
|
@@ -270,7 +361,7 @@ export class UserInteractions
|
|
|
270
361
|
{
|
|
271
362
|
featureId = +featureId; // Ensure numeric
|
|
272
363
|
if (this._selectedFeatureIds.size === 0) {
|
|
273
|
-
this._layerManager.
|
|
364
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
|
|
274
365
|
}
|
|
275
366
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
276
367
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
@@ -300,7 +391,7 @@ export class UserInteractions
|
|
|
300
391
|
}
|
|
301
392
|
}
|
|
302
393
|
if (this._selectedFeatureIds.size === 0) {
|
|
303
|
-
this._layerManager.
|
|
394
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
|
|
304
395
|
}
|
|
305
396
|
}
|
|
306
397
|
|
|
@@ -314,20 +405,7 @@ export class UserInteractions
|
|
|
314
405
|
}
|
|
315
406
|
}
|
|
316
407
|
this._selectedFeatureIds.clear();
|
|
317
|
-
this._layerManager.
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
activeFeaturesAtEvent_(event)
|
|
321
|
-
//===========================
|
|
322
|
-
{
|
|
323
|
-
// Get the features covering the event's point that are in the active layers
|
|
324
|
-
|
|
325
|
-
return this._map.queryRenderedFeatures(event.point).filter(f => {
|
|
326
|
-
return (this.__enabledFeature(f)
|
|
327
|
-
&& this.activeLayerNames.indexOf(f.sourceLayer) >= 0)
|
|
328
|
-
&& ('featureId' in f.properties);
|
|
329
|
-
}
|
|
330
|
-
);
|
|
408
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
|
|
331
409
|
}
|
|
332
410
|
|
|
333
411
|
__activateFeature(feature)
|
|
@@ -381,45 +459,6 @@ export class UserInteractions
|
|
|
381
459
|
return smallestFeature;
|
|
382
460
|
}
|
|
383
461
|
|
|
384
|
-
contextMenuEvent_(event)
|
|
385
|
-
//======================
|
|
386
|
-
{
|
|
387
|
-
event.preventDefault();
|
|
388
|
-
|
|
389
|
-
// Chrome on Android sends both touch and contextmenu events
|
|
390
|
-
// so ignore duplicate
|
|
391
|
-
|
|
392
|
-
if (Date.now() < (this._lastContextTime + 100)) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
this._lastContextTime = Date.now();
|
|
396
|
-
|
|
397
|
-
if (this._activeFeatures.length > 0) {
|
|
398
|
-
const feature = this._activeFeatures[0];
|
|
399
|
-
|
|
400
|
-
// Remove any tooltip
|
|
401
|
-
this.removeTooltip_();
|
|
402
|
-
|
|
403
|
-
const featureId = feature.id;
|
|
404
|
-
if (this._pathways.isNode(featureId)) {
|
|
405
|
-
const items = [
|
|
406
|
-
{
|
|
407
|
-
featureId: featureId,
|
|
408
|
-
prompt: 'Show paths',
|
|
409
|
-
action: this.enablePaths_.bind(this, true)
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
featureId: featureId,
|
|
413
|
-
prompt: 'Hide paths',
|
|
414
|
-
action: this.enablePaths_.bind(this, false)
|
|
415
|
-
}
|
|
416
|
-
];
|
|
417
|
-
this.setModal_();
|
|
418
|
-
this._contextMenu.show(event.lngLat, items, feature.properties.label);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
462
|
setModal_(event)
|
|
424
463
|
//==============
|
|
425
464
|
{
|
|
@@ -515,7 +554,12 @@ export class UserInteractions
|
|
|
515
554
|
zoomToFeatures(featureIds, options=null)
|
|
516
555
|
//======================================
|
|
517
556
|
{
|
|
518
|
-
options = utils.
|
|
557
|
+
options = utils.setDefaults(options, {
|
|
558
|
+
select: true,
|
|
559
|
+
highlight:
|
|
560
|
+
false, noZoomIn:
|
|
561
|
+
false, padding:10
|
|
562
|
+
});
|
|
519
563
|
const select = (options.select === true);
|
|
520
564
|
const highlight = (options.highlight === true);
|
|
521
565
|
if (featureIds.length) {
|
|
@@ -583,7 +627,7 @@ export class UserInteractions
|
|
|
583
627
|
location = this.__lastClickLngLat;
|
|
584
628
|
} else {
|
|
585
629
|
// Position popup at the feature's 'centre'
|
|
586
|
-
location = this.
|
|
630
|
+
location = this.__markerPosition(featureId, ann);
|
|
587
631
|
}
|
|
588
632
|
|
|
589
633
|
// Make sure the feature is on screen
|
|
@@ -625,9 +669,13 @@ export class UserInteractions
|
|
|
625
669
|
const tooltips = [];
|
|
626
670
|
for (const lineFeature of lineFeatures) {
|
|
627
671
|
const properties = lineFeature.properties;
|
|
628
|
-
if ('
|
|
629
|
-
|
|
630
|
-
|
|
672
|
+
if ('error' in properties) {
|
|
673
|
+
tooltips.push(`<div class="feature-error">Error: ${properties.error}</div>`)
|
|
674
|
+
}
|
|
675
|
+
if ('warning' in properties) {
|
|
676
|
+
tooltips.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
677
|
+
}
|
|
678
|
+
if ('label' in properties && (!('tooltip' in properties) || properties.tooltip)) {
|
|
631
679
|
let tooltip = '';
|
|
632
680
|
const label = properties.label;
|
|
633
681
|
const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
|
|
@@ -636,32 +684,37 @@ export class UserInteractions
|
|
|
636
684
|
}
|
|
637
685
|
}
|
|
638
686
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
return `<div class='flatmap-feature-label'>${tooltips.join('<hr/>')}</div>`;
|
|
687
|
+
return (tooltips.length === 0) ? ''
|
|
688
|
+
: `<div class='flatmap-feature-label'>${tooltips.join('<hr/>')}</div>`;
|
|
643
689
|
}
|
|
644
690
|
|
|
645
691
|
tooltipHtml_(properties, forceLabel=false)
|
|
646
692
|
//========================================
|
|
647
693
|
{
|
|
694
|
+
const tooltip = [];
|
|
695
|
+
if ('error' in properties) {
|
|
696
|
+
tooltip.push(`<div class="feature-error">Error: ${properties.error}</div>`)
|
|
697
|
+
}
|
|
698
|
+
if ('warning' in properties) {
|
|
699
|
+
tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
700
|
+
}
|
|
648
701
|
if (('label' in properties || 'hyperlink' in properties)
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const label = properties.label;
|
|
654
|
-
tooltip = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
|
|
655
|
-
} else {
|
|
656
|
-
tooltip = properties.hyperlink
|
|
657
|
-
}
|
|
702
|
+
&& (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
|
|
703
|
+
const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
|
|
704
|
+
+ properties.label.substr(1)).replaceAll("\n", "<br/>")
|
|
705
|
+
: '';
|
|
658
706
|
if ('hyperlink' in properties) {
|
|
659
|
-
|
|
707
|
+
if (label === '') {
|
|
708
|
+
tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
|
|
709
|
+
} else {
|
|
710
|
+
tooltip.push(`<a href='${properties.hyperlink}'>${label}</a></div>`);
|
|
711
|
+
}
|
|
660
712
|
} else {
|
|
661
|
-
|
|
713
|
+
tooltip.push(label);
|
|
662
714
|
}
|
|
663
715
|
}
|
|
664
|
-
return ''
|
|
716
|
+
return (tooltip.length === 0) ? ''
|
|
717
|
+
: `<div class='flatmap-feature-label'>${tooltip.join('<hr/>')}</div>`;
|
|
665
718
|
}
|
|
666
719
|
|
|
667
720
|
__featureEvent(type, feature)
|
|
@@ -841,59 +894,83 @@ export class UserInteractions
|
|
|
841
894
|
selectionEvent_(event, feature)
|
|
842
895
|
//=============================
|
|
843
896
|
{
|
|
844
|
-
const multipleSelect = event.ctrlKey || event.metaKey;
|
|
845
|
-
if (!multipleSelect) {
|
|
846
|
-
this.__unselectFeatures();
|
|
847
|
-
}
|
|
848
897
|
if (feature !== undefined) {
|
|
849
|
-
const
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
898
|
+
const clickedFeatureId = feature.id;
|
|
899
|
+
const dim = !('properties' in feature
|
|
900
|
+
&& 'kind' in feature.properties
|
|
901
|
+
&& ['cell-type', 'scaffold', 'tissue'].indexOf(feature.properties.kind) >= 0);
|
|
902
|
+
if (!(event.ctrlKey || event.metaKey)) {
|
|
903
|
+
let selecting = true;
|
|
904
|
+
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
905
|
+
if (featureId === clickedFeatureId) {
|
|
906
|
+
selecting = false;
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
this.__unselectFeatures();
|
|
911
|
+
if (selecting) {
|
|
912
|
+
for (const feature of this._activeFeatures) {
|
|
913
|
+
this.selectFeature_(feature.id, dim);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
const clickedSelected = this.featureSelected_(clickedFeatureId);
|
|
854
918
|
for (const feature of this._activeFeatures) {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
this.selectFeature_(featureId);
|
|
919
|
+
if (clickedSelected) {
|
|
920
|
+
this.unselectFeature_(feature.id);
|
|
858
921
|
} else {
|
|
859
|
-
this.
|
|
922
|
+
this.selectFeature_(feature.id, dim);
|
|
860
923
|
}
|
|
861
924
|
}
|
|
862
|
-
} else if (selecting) {
|
|
863
|
-
const dim = !('properties' in feature
|
|
864
|
-
&& 'kind' in feature.properties
|
|
865
|
-
&& ['cell-type', 'scaffold', 'tissue'].indexOf(feature.properties.kind) >= 0);
|
|
866
|
-
this.selectFeature_(featureId, dim);
|
|
867
|
-
} else {
|
|
868
|
-
this.unselectFeature_(featureId);
|
|
869
925
|
}
|
|
870
926
|
}
|
|
871
927
|
}
|
|
872
928
|
|
|
929
|
+
__annotationEvent(feature)
|
|
930
|
+
//========================
|
|
931
|
+
{
|
|
932
|
+
event.preventDefault();
|
|
933
|
+
|
|
934
|
+
// Remove any tooltip
|
|
935
|
+
this.removeTooltip_();
|
|
936
|
+
|
|
937
|
+
// Select the feature
|
|
938
|
+
this.selectFeature_(feature.id);
|
|
939
|
+
|
|
940
|
+
// Don't respond to mouse events while the dialog is open
|
|
941
|
+
this.setModal_();
|
|
942
|
+
|
|
943
|
+
// The annotation dialog...
|
|
944
|
+
this.__annotator.annotate(feature, e => {
|
|
945
|
+
this.__unselectFeatures();
|
|
946
|
+
this.__clearModal();
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
873
950
|
clickEvent_(event)
|
|
874
951
|
//================
|
|
875
952
|
{
|
|
953
|
+
if (this._modal) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
876
957
|
this.clearActiveMarker_();
|
|
877
958
|
const clickedFeatures = this._map.queryRenderedFeatures(event.point)
|
|
878
959
|
.filter(feature => this.__enabledFeature(feature));
|
|
879
960
|
if (clickedFeatures.length == 0){
|
|
961
|
+
this.__unselectFeatures();
|
|
880
962
|
return;
|
|
881
963
|
}
|
|
882
964
|
const clickedFeature = clickedFeatures[0];
|
|
883
965
|
const originalEvent = event.originalEvent;
|
|
884
|
-
if (
|
|
885
|
-
this.
|
|
886
|
-
|
|
887
|
-
const multipleSelect = originalEvent.ctrlKey || originalEvent.metaKey;
|
|
888
|
-
if (!multipleSelect) {
|
|
889
|
-
this.__unselectFeatures();
|
|
890
|
-
}
|
|
891
|
-
for (const feature of this._activeFeatures) {
|
|
892
|
-
this.selectFeature_(feature.id);
|
|
893
|
-
}
|
|
966
|
+
if (originalEvent.altKey) {
|
|
967
|
+
this.__annotationEvent(clickedFeature);
|
|
968
|
+
return;
|
|
894
969
|
}
|
|
970
|
+
|
|
971
|
+
this.selectionEvent_(originalEvent, clickedFeature);
|
|
895
972
|
if (this._modal) {
|
|
896
|
-
|
|
973
|
+
// Remove tooltip, reset active features, etc
|
|
897
974
|
this.__resetFeatureDisplay();
|
|
898
975
|
this.__unselectFeatures();
|
|
899
976
|
this.__clearModal();
|
|
@@ -932,7 +1009,6 @@ export class UserInteractions
|
|
|
932
1009
|
enablePaths_(enable, event)
|
|
933
1010
|
//=========================
|
|
934
1011
|
{
|
|
935
|
-
this._contextMenu.hide();
|
|
936
1012
|
const nodeId = event.target.getAttribute('featureId');
|
|
937
1013
|
this.enablePathFeatures_(enable, this._pathways.pathFeatureIds(nodeId));
|
|
938
1014
|
this.__clearModal();
|
|
@@ -957,6 +1033,7 @@ export class UserInteractions
|
|
|
957
1033
|
togglePaths()
|
|
958
1034
|
//===========
|
|
959
1035
|
{
|
|
1036
|
+
console.log('Depracated API function called: togglePaths()')
|
|
960
1037
|
if (this._disabledPathFeatures){
|
|
961
1038
|
this.enablePathFeatures_(true, this._pathways.allFeatureIds());
|
|
962
1039
|
this._disabledPathFeatures = false;
|
|
@@ -965,36 +1042,12 @@ export class UserInteractions
|
|
|
965
1042
|
}
|
|
966
1043
|
}
|
|
967
1044
|
|
|
968
|
-
pathTypes()
|
|
969
|
-
//=========
|
|
970
|
-
{
|
|
971
|
-
return this._pathways.pathTypes;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
1045
|
enablePath(pathType, enable=true)
|
|
975
1046
|
//===============================
|
|
976
1047
|
{
|
|
977
1048
|
this.enablePathFeatures_(enable, this._pathways.typeFeatureIds(pathType));
|
|
978
1049
|
}
|
|
979
1050
|
|
|
980
|
-
showPaths(pathTypes, enable=true)
|
|
981
|
-
//===============================
|
|
982
|
-
{
|
|
983
|
-
// Disable/enable all paths except those with `pathTypes`
|
|
984
|
-
|
|
985
|
-
this.enablePathFeatures_(!enable, this._pathways.allFeatureIds());
|
|
986
|
-
|
|
987
|
-
if (Array.isArray(pathTypes)) {
|
|
988
|
-
for (const pathType of pathTypes) {
|
|
989
|
-
this.enablePath(pathType, enable);
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
this.enablePath(pathTypes, enable);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
this._disabledPathFeatures = true;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
1051
|
pathwaysFeatureIds(externalIds)
|
|
999
1052
|
//=============================
|
|
1000
1053
|
{
|
|
@@ -1010,46 +1063,57 @@ export class UserInteractions
|
|
|
1010
1063
|
return this._pathways.nodePathModels(nodeId);
|
|
1011
1064
|
}
|
|
1012
1065
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1066
|
+
enableCentrelines(show=true)
|
|
1067
|
+
//==========================
|
|
1068
|
+
{
|
|
1069
|
+
this.enablePath('centreline', show);
|
|
1070
|
+
this._layerManager.setPaint({showCentrelines: show});
|
|
1071
|
+
}
|
|
1016
1072
|
|
|
1017
|
-
|
|
1073
|
+
enableSckanPath(sckanState, enable=true)
|
|
1018
1074
|
//======================================
|
|
1019
1075
|
{
|
|
1020
|
-
|
|
1021
|
-
return this.__centralPositions.get(featureId);
|
|
1022
|
-
}
|
|
1023
|
-
let position = annotation.centroid;
|
|
1024
|
-
const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
|
|
1025
|
-
'sourceLayer': this._flatmap.options.separateLayers
|
|
1026
|
-
? `${annotation['layer']}_${annotation['tile-layer']}`
|
|
1027
|
-
: annotation['tile-layer'],
|
|
1028
|
-
'filter': [
|
|
1029
|
-
'all',
|
|
1030
|
-
[ '==', ['id'], parseInt(featureId) ],
|
|
1031
|
-
[ '==', ['geometry-type'], 'Polygon' ]
|
|
1032
|
-
]
|
|
1033
|
-
});
|
|
1034
|
-
if (features.length > 0) {
|
|
1035
|
-
const feature = features[0];
|
|
1036
|
-
const polygon = feature.geometry.coordinates;
|
|
1037
|
-
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
1038
|
-
const precision = ('area' in feature.properties)
|
|
1039
|
-
? Math.sqrt(feature.properties.area)/500000
|
|
1040
|
-
: 0.1;
|
|
1041
|
-
position = polylabel(polygon, precision);
|
|
1042
|
-
}
|
|
1043
|
-
this.__centralPositions.set(featureId, position);
|
|
1044
|
-
return position;
|
|
1076
|
+
this._layerManager.enableSckanPath(sckanState, enable);
|
|
1045
1077
|
}
|
|
1046
1078
|
|
|
1047
1079
|
//==============================================================================
|
|
1048
1080
|
|
|
1049
1081
|
// Marker handling
|
|
1050
1082
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1083
|
+
__markerPosition(featureId, annotation)
|
|
1084
|
+
{
|
|
1085
|
+
if (this.__markerPositions.has(featureId)) {
|
|
1086
|
+
return this.__markerPositions.get(featureId);
|
|
1087
|
+
}
|
|
1088
|
+
let position = annotation.markerPosition || annotation.centroid;
|
|
1089
|
+
if (position === null || position == undefined) {
|
|
1090
|
+
// Find where to place a label or popup on a feature
|
|
1091
|
+
const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
|
|
1092
|
+
'sourceLayer': this._flatmap.options.separateLayers
|
|
1093
|
+
? `${annotation['layer']}_${annotation['tile-layer']}`
|
|
1094
|
+
: annotation['tile-layer'],
|
|
1095
|
+
'filter': [
|
|
1096
|
+
'all',
|
|
1097
|
+
[ '==', ['id'], parseInt(featureId) ],
|
|
1098
|
+
[ '==', ['geometry-type'], 'Polygon' ]
|
|
1099
|
+
]
|
|
1100
|
+
});
|
|
1101
|
+
if (features.length > 0) {
|
|
1102
|
+
const feature = features[0];
|
|
1103
|
+
const polygon = feature.geometry.coordinates;
|
|
1104
|
+
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
1105
|
+
const precision = ('area' in feature.properties)
|
|
1106
|
+
? Math.sqrt(feature.properties.area)/500000
|
|
1107
|
+
: 0.1;
|
|
1108
|
+
position = polylabel(polygon, precision);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
this.__markerPositions.set(featureId, position);
|
|
1112
|
+
return position;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
addMarker(anatomicalId, options={})
|
|
1116
|
+
//=================================
|
|
1053
1117
|
{
|
|
1054
1118
|
const featureIds = this._flatmap.modelFeatureIds(anatomicalId);
|
|
1055
1119
|
let markerId = -1;
|
|
@@ -1065,19 +1129,21 @@ export class UserInteractions
|
|
|
1065
1129
|
markerId = this.__lastMarkerId;
|
|
1066
1130
|
}
|
|
1067
1131
|
|
|
1132
|
+
// MapLibre dynamically sets a transform on marker elements so in
|
|
1133
|
+
// order to apply a scale transform we need to create marker icons
|
|
1134
|
+
// inside the marker container <div>.
|
|
1135
|
+
const colour = options.colour || '#005974';
|
|
1136
|
+
const markerHTML = options.element ? new maplibre.Marker({element: options.element})
|
|
1137
|
+
: new maplibre.Marker({color: colour});
|
|
1138
|
+
|
|
1068
1139
|
const markerElement = document.createElement('div');
|
|
1069
1140
|
const markerIcon = document.createElement('div');
|
|
1070
|
-
|
|
1071
|
-
markerIcon.innerHTML = this._simulationMarkerHTML;
|
|
1072
|
-
} else {
|
|
1073
|
-
markerIcon.innerHTML = this._defaultMarkerHTML;
|
|
1074
|
-
}
|
|
1141
|
+
markerIcon.innerHTML = markerHTML.getElement().innerHTML;
|
|
1075
1142
|
markerIcon.className = 'flatmap-marker';
|
|
1143
|
+
markerElement.id = `marker-${markerId}`;
|
|
1076
1144
|
markerElement.appendChild(markerIcon);
|
|
1077
1145
|
|
|
1078
|
-
const markerPosition = (annotation
|
|
1079
|
-
? this.__centralPosition(featureId, annotation)
|
|
1080
|
-
: annotation.centroid;
|
|
1146
|
+
const markerPosition = this.__markerPosition(featureId, annotation);
|
|
1081
1147
|
const marker = new maplibre.Marker(markerElement)
|
|
1082
1148
|
.setLngLat(markerPosition)
|
|
1083
1149
|
.addTo(this._map);
|
|
@@ -1091,6 +1157,7 @@ export class UserInteractions
|
|
|
1091
1157
|
this.markerMouseEvent_.bind(this, marker, anatomicalId));
|
|
1092
1158
|
|
|
1093
1159
|
this.__markerIdByMarker.set(marker, markerId);
|
|
1160
|
+
this.__markerIdByFeatureId.set(+featureId, markerId);
|
|
1094
1161
|
this.__annotationByMarkerId.set(markerId, annotation);
|
|
1095
1162
|
}
|
|
1096
1163
|
}
|