@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.
@@ -40,11 +40,13 @@ import {Paths3DLayer} from './layers/paths3d'
40
40
  import {SystemsManager} from './systems';
41
41
 
42
42
  import {displayedProperties, InfoControl} from './controls/info';
43
- import {BackgroundControl, LayerControl, NerveControl,
43
+ import {AnnotatorControl, BackgroundControl, LayerControl, NerveControl,
44
44
  SCKANControl} from './controls/controls';
45
+ import {AnnotationDrawControl, DRAW_ANNOTATION_LAYERS} from './controls/annotation'
45
46
  import {PathControl} from './controls/paths';
46
47
  import {Path3DControl} from './controls/paths3d'
47
48
  import {SearchControl} from './controls/search';
49
+ import {MinimapControl} from './controls/minimap';
48
50
  import {SystemsControl} from './controls/systems';
49
51
  import {TaxonsControl} from './controls/taxons';
50
52
  import {latex2Svg} from './mathjax';
@@ -110,10 +112,13 @@ function labelPosition(feature)
110
112
  function getRenderedLabel(properties)
111
113
  {
112
114
  if (!('renderedLabel' in properties)) {
113
- const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
114
- + properties.label.substr(1)).replaceAll("\n", "<br/>")
115
- : '';
116
- properties.renderedLabel = label.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
115
+ const label = ('label' in properties) ? properties.label
116
+ : ('user_label' in properties) ? properties.user_label
117
+ : ''
118
+ const uppercaseLabel = (label !== '') ? (label.substr(0, 1).toUpperCase()
119
+ + label.substr(1)).replaceAll("\n", "<br/>")
120
+ : ''
121
+ properties.renderedLabel = uppercaseLabel.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
117
122
  }
118
123
  return properties.renderedLabel;
119
124
  }
@@ -122,14 +127,16 @@ function getRenderedLabel(properties)
122
127
 
123
128
  export class UserInteractions
124
129
  {
125
- #paths3dLayer
130
+ #annotationDrawControl = null
131
+ #minimap = null
132
+ #paths3dLayer = null
126
133
 
127
134
  constructor(flatmap)
128
135
  {
129
136
  this._flatmap = flatmap;
130
137
  this._map = flatmap.map;
131
138
 
132
- this._activeFeatures = [];
139
+ this._activeFeatures = new Set()
133
140
  this._selectedFeatureIds = new Map();
134
141
  this._currentPopup = null;
135
142
  this._infoControl = null;
@@ -139,7 +146,6 @@ export class UserInteractions
139
146
  this._modal = false;
140
147
 
141
148
  // Default colour settings
142
-
143
149
  this.__colourOptions = {colour: true, outline: true};
144
150
 
145
151
  // Marker placement and interaction
@@ -187,6 +193,26 @@ export class UserInteractions
187
193
  // All taxons of connectivity paths are enabled by default
188
194
  this.__enabledConnectivityTaxons = new Set(this._flatmap.taxonIdentifiers);
189
195
 
196
+ // Add a minimap if option set
197
+ if (flatmap.options.minimap) {
198
+ this.#minimap = new MinimapControl(flatmap, flatmap.options.minimap);
199
+ this._map.addControl(this.#minimap);
200
+ }
201
+
202
+ // Do we want a fullscreen control?
203
+ if (flatmap.options.fullscreenControl === true) {
204
+ this._map.addControl(new maplibregl.FullscreenControl(), 'top-right');
205
+ }
206
+
207
+ // Add navigation controls if option set
208
+ if (flatmap.options.navigationControl) {
209
+ const value = flatmap.options.navigationControl;
210
+ const position = ((typeof value === 'string')
211
+ && ['top-left', 'top-right', 'bottom-right', 'bottom-left'].includes(value))
212
+ ? value : 'bottom-right';
213
+ this._map.addControl(new NavigationControl(flatmap), position);
214
+ }
215
+
190
216
  // Support 3D path view
191
217
  this.#paths3dLayer = new Paths3DLayer(flatmap, this)
192
218
 
@@ -222,9 +248,17 @@ export class UserInteractions
222
248
  this._map.addControl(new TaxonsControl(flatmap));
223
249
  }
224
250
 
251
+ if (flatmap.options.annotator) {
252
+ this._map.addControl(new AnnotatorControl(flatmap));
253
+ }
254
+
225
255
  this._map.addControl(new Path3DControl(this));
226
256
  }
227
257
 
258
+ // Add an initially hidden tool for drawing on the map.
259
+ this.#annotationDrawControl = new AnnotationDrawControl(flatmap, false)
260
+ this._map.addControl(this.#annotationDrawControl)
261
+
228
262
  // Handle mouse events
229
263
 
230
264
  this._map.on('click', this.clickEvent_.bind(this));
@@ -239,6 +273,12 @@ export class UserInteractions
239
273
  this.__pan_zoom_enabled = false;
240
274
  }
241
275
 
276
+ get minimap()
277
+ //===========
278
+ {
279
+ return this.#minimap
280
+ }
281
+
242
282
  get pathManager()
243
283
  //===============
244
284
  {
@@ -278,11 +318,40 @@ export class UserInteractions
278
318
  }
279
319
  }
280
320
 
321
+ showAnnotator(visible=true)
322
+ //=========================
323
+ {
324
+ if (this.#annotationDrawControl) {
325
+ this.#annotationDrawControl.show(visible)
326
+ }
327
+ }
328
+
329
+ modifyDrawnAnnotatorFeature(operation, feature)
330
+ //=============================================
331
+ {
332
+ if (this.#annotationDrawControl) {
333
+ if (operation === 'add') {
334
+ this.#annotationDrawControl.addFeature(feature)
335
+ } else if (operation === 'remove') {
336
+ this.#annotationDrawControl.removeFeature(feature)
337
+ }
338
+ }
339
+ }
340
+
341
+ #setPaint(options)
342
+ //================
343
+ {
344
+ this._layerManager.setPaint(options)
345
+ if (this.#paths3dLayer) {
346
+ this.#paths3dLayer.setPaint(options)
347
+ }
348
+ }
349
+
281
350
  setPaint(options)
282
351
  //===============
283
352
  {
284
353
  this.__colourOptions = options;
285
- this._layerManager.setPaint(options);
354
+ this.#setPaint(options);
286
355
  }
287
356
 
288
357
  getLayers()
@@ -300,7 +369,9 @@ export class UserInteractions
300
369
  enable3dPaths(enable=true)
301
370
  //========================
302
371
  {
303
- this.#paths3dLayer.enable(enable)
372
+ if (this.#paths3dLayer) {
373
+ this.#paths3dLayer.enable(enable)
374
+ }
304
375
  }
305
376
 
306
377
  getSystems()
@@ -332,19 +403,43 @@ export class UserInteractions
332
403
  return undefined;
333
404
  }
334
405
 
406
+ #getFeatureState(feature)
407
+ //=======================
408
+ {
409
+ return this._map.getFeatureState(feature)
410
+ }
411
+
412
+ #removeFeatureState(feature, key)
413
+ //===============================
414
+ {
415
+ this._map.removeFeatureState(feature, key)
416
+ if (this.#paths3dLayer) {
417
+ this.#paths3dLayer.removeFeatureState(feature.id, key)
418
+ }
419
+ }
420
+
421
+ #setFeatureState(feature, state)
422
+ //==============================
423
+ {
424
+ this._map.setFeatureState(feature, state)
425
+ if (this.#paths3dLayer) {
426
+ this.#paths3dLayer.setFeatureState(feature.id, state)
427
+ }
428
+ }
429
+
335
430
  enableMapFeature(feature, enable=true)
336
431
  //====================================
337
432
  {
338
433
  if (feature !== undefined) {
339
- const state = this._map.getFeatureState(feature);
434
+ const state = this.#getFeatureState(feature);
340
435
  if ('hidden' in state) {
341
436
  if (enable) {
342
- this._map.removeFeatureState(feature, 'hidden');
437
+ this.#removeFeatureState(feature, 'hidden');
343
438
  } else if (!state.hidden) {
344
- this._map.setFeatureState(feature, { 'hidden': true });
439
+ this.#setFeatureState(feature, { hidden: true });
345
440
  }
346
441
  } else if (!enable) {
347
- this._map.setFeatureState(feature, { 'hidden': true });
442
+ this.#setFeatureState(feature, { hidden: true });
348
443
  }
349
444
  this.__enableFeatureMarker(feature.id, enable);
350
445
  }
@@ -392,11 +487,11 @@ export class UserInteractions
392
487
  //=======================
393
488
  {
394
489
  if (feature.id) {
395
- const state = this._map.getFeatureState(feature);
490
+ const state = this.#getFeatureState(feature);
396
491
  return (state !== undefined
397
492
  && (!('hidden' in state) || !state.hidden));
398
493
  }
399
- return false;
494
+ return DRAW_ANNOTATION_LAYERS.includes(feature.layer.id)
400
495
  }
401
496
 
402
497
  featureSelected_(featureId)
@@ -409,7 +504,7 @@ export class UserInteractions
409
504
  //================================
410
505
  {
411
506
  const ann = this._flatmap.annotation(featureId);
412
- if ('sckan' in ann) {
507
+ if (ann && 'sckan' in ann) {
413
508
  const sckanState = this._layerManager.sckanState;
414
509
  if (sckanState === 'none'
415
510
  || sckanState === 'valid' && !ann.sckan
@@ -426,16 +521,16 @@ export class UserInteractions
426
521
  } else {
427
522
  const feature = this.mapFeature(featureId);
428
523
  if (feature !== undefined) {
429
- const state = this._map.getFeatureState(feature);
524
+ const state = this.#getFeatureState(feature);
430
525
  if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
431
- this._map.setFeatureState(feature, { 'selected': true });
526
+ this.#setFeatureState(feature, { selected: true });
432
527
  this._selectedFeatureIds.set(featureId, 1);
433
528
  result = true;
434
529
  }
435
530
  }
436
531
  }
437
532
  if (result && noSelection) {
438
- this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
533
+ this.#setPaint({...this.__colourOptions, dimmed: dim});
439
534
  }
440
535
  return result;
441
536
  }
@@ -451,13 +546,13 @@ export class UserInteractions
451
546
  } else {
452
547
  const feature = this.mapFeature(featureId);
453
548
  if (feature !== undefined) {
454
- this._map.removeFeatureState(feature, 'selected');
549
+ this.#removeFeatureState(feature, 'selected');
455
550
  this._selectedFeatureIds.delete(+featureId);
456
551
  }
457
552
  }
458
553
  }
459
554
  if (this._selectedFeatureIds.size === 0) {
460
- this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
555
+ this.#setPaint({...this.__colourOptions, dimmed: false});
461
556
  }
462
557
  }
463
558
 
@@ -467,28 +562,42 @@ export class UserInteractions
467
562
  for (const featureId of this._selectedFeatureIds.keys()) {
468
563
  const feature = this.mapFeature(featureId);
469
564
  if (feature !== undefined) {
470
- this._map.removeFeatureState(feature, 'selected');
565
+ this.#removeFeatureState(feature, 'selected');
471
566
  }
472
567
  }
473
568
  this._selectedFeatureIds.clear();
474
- this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
569
+ this.#setPaint({...this.__colourOptions, dimmed: false});
475
570
  }
476
571
 
477
572
  activateFeature(feature)
478
573
  //======================
479
574
  {
480
575
  if (feature !== undefined) {
481
- this._map.setFeatureState(feature, { active: true });
482
- this._activeFeatures.push(feature);
576
+ this.#setFeatureState(feature, { active: true });
577
+ this._activeFeatures.add(feature);
578
+ }
579
+ }
580
+
581
+ activateLineFeatures(lineFeatures)
582
+ //================================
583
+ {
584
+ for (const lineFeature of lineFeatures) {
585
+ const lineFeatureId = +lineFeature.properties.featureId // Ensure numeric
586
+ this.activateFeature(lineFeature)
587
+ const lineIds = new Set(lineFeatures.map(f => f.properties.featureId))
588
+ for (const featureId of this.__pathManager.lineFeatureIds(lineIds)) {
589
+ this.activateFeature(this.mapFeature(featureId))
590
+ }
483
591
  }
484
592
  }
485
593
 
486
594
  resetActiveFeatures_()
487
595
  //====================
488
596
  {
489
- while (this._activeFeatures.length > 0) {
490
- this._map.removeFeatureState(this._activeFeatures.pop(), 'active');
597
+ for (const feature of this._activeFeatures) {
598
+ this.#removeFeatureState(feature, 'active');
491
599
  }
600
+ this._activeFeatures.clear()
492
601
  }
493
602
 
494
603
  smallestAnnotatedPolygonFeature_(features)
@@ -500,7 +609,7 @@ export class UserInteractions
500
609
  let smallestFeature = null;
501
610
  for (const feature of features) {
502
611
  if (feature.geometry.type.includes('Polygon')
503
- && this._map.getFeatureState(feature)['map-annotation']) {
612
+ && this.#getFeatureState(feature)['map-annotation']) {
504
613
  const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
505
614
  const area = turfArea(polygon);
506
615
  if (smallestFeature === null || smallestArea > area) {
@@ -733,7 +842,9 @@ export class UserInteractions
733
842
  tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
734
843
  }
735
844
  let renderedLabel;
736
- if (('label' in properties || 'hyperlink' in properties)
845
+ if (('label' in properties
846
+ || 'hyperlink' in properties
847
+ || 'user_label' in properties)
737
848
  && (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
738
849
  const renderedLabel = getRenderedLabel(properties);
739
850
  if ('hyperlink' in properties) {
@@ -775,6 +886,19 @@ export class UserInteractions
775
886
  this.resetActiveFeatures_();
776
887
  }
777
888
 
889
+ #renderedFeatures(point)
890
+ //======================
891
+ {
892
+ let features = []
893
+ if (this.#paths3dLayer) {
894
+ features = this.#paths3dLayer.queryFeaturesAtPoint(point)
895
+ }
896
+ if (features.length === 0) {
897
+ features = this._map.queryRenderedFeatures(point)
898
+ }
899
+ return features.filter(feature => this.__featureEnabled(feature));
900
+ }
901
+
778
902
  mouseMoveEvent_(event)
779
903
  //====================
780
904
  {
@@ -793,8 +917,7 @@ export class UserInteractions
793
917
  }
794
918
 
795
919
  // Get all the features at the current point
796
- const features = this._map.queryRenderedFeatures(event.point)
797
- .filter(feature => this.__featureEnabled(feature));
920
+ const features = this.#renderedFeatures(event.point)
798
921
  if (features.length === 0) {
799
922
  this._lastFeatureMouseEntered = null;
800
923
  this._lastFeatureModelsMouse = null;
@@ -834,19 +957,11 @@ export class UserInteractions
834
957
  if (lineFeatures.length > 0) {
835
958
  tooltip = this.lineTooltip_(lineFeatures);
836
959
  tooltipFeature = lineFeatures[0];
837
- for (const lineFeature of lineFeatures) {
838
- const lineFeatureId = +lineFeature.properties.featureId; // Ensure numeric
839
- this.activateFeature(lineFeature);
840
- const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
841
- for (const featureId of this.__pathManager.lineFeatureIds(lineIds)) {
842
- if (+featureId !== lineFeatureId) {
843
- this.activateFeature(this.mapFeature(featureId));
844
- }
845
- }
846
- }
960
+ this.activateLineFeatures(lineFeatures)
847
961
  } else {
848
962
  let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
849
- || 'label' in feature.properties)
963
+ || 'label' in feature.properties
964
+ || 'user_label' in feature.properties)
850
965
  && (!('tooltip' in feature.properties)
851
966
  || feature.properties.tooltip)))
852
967
  .sort((a, b) => (a.properties.area - b.properties.area));
@@ -859,6 +974,9 @@ export class UserInteractions
859
974
  labelledFeatures = groupFeatures;
860
975
  }
861
976
  const feature = labelledFeatures[0];
977
+ if (feature.properties.user_drawn) {
978
+ feature.id = feature.properties.id
979
+ }
862
980
  tooltip = this.tooltipHtml_(feature.properties);
863
981
  tooltipFeature = feature;
864
982
  if (this._flatmap.options.debug) { // Do this when Info on and not debug??
@@ -943,7 +1061,7 @@ export class UserInteractions
943
1061
  //=============================
944
1062
  {
945
1063
  if (feature !== undefined) {
946
- const clickedFeatureId = feature.id;
1064
+ const clickedFeatureId = +feature.id;
947
1065
  const dim = !('properties' in feature
948
1066
  && 'kind' in feature.properties
949
1067
  && ['cell-type', 'scaffold', 'tissue'].includes(feature.properties.kind));
@@ -982,9 +1100,9 @@ export class UserInteractions
982
1100
  }
983
1101
 
984
1102
  this.__clearActiveMarker();
985
- const clickedFeatures = this._map.queryRenderedFeatures(event.point)
986
- .filter(feature => this.__featureEnabled(feature));
987
- if (clickedFeatures.length == 0){
1103
+
1104
+ const clickedFeatures = this.#renderedFeatures(event.point)
1105
+ if (clickedFeatures.length == 0) {
988
1106
  this.unselectFeatures();
989
1107
  return;
990
1108
  }
@@ -1060,7 +1178,7 @@ export class UserInteractions
1060
1178
  //=========================================
1061
1179
  {
1062
1180
  this.__pathManager.enablePathsByType('centreline', enable, force);
1063
- this._layerManager.setPaint({showCentrelines: enable});
1181
+ this.#setPaint({showCentrelines: enable});
1064
1182
  }
1065
1183
 
1066
1184
  enableSckanPaths(sckanState, enable=true)
@@ -1087,7 +1205,7 @@ export class UserInteractions
1087
1205
  excludeAnnotated(exclude=false)
1088
1206
  //=============================
1089
1207
  {
1090
- this._layerManager.setPaint({excludeAnnotated: exclude});
1208
+ this.#setPaint({excludeAnnotated: exclude});
1091
1209
  }
1092
1210
 
1093
1211
  //==============================================================================