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

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
 
@@ -223,8 +249,16 @@ export class UserInteractions
223
249
  }
224
250
 
225
251
  this._map.addControl(new Path3DControl(this));
252
+
253
+ if (flatmap.options.annotator) {
254
+ this._map.addControl(new AnnotatorControl(flatmap));
255
+ }
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,60 @@ 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
+ commitAnnotationEvent(event)
330
+ //==========================
331
+ {
332
+ if (this.#annotationDrawControl) {
333
+ this.#annotationDrawControl.commitEvent(event)
334
+ }
335
+ }
336
+
337
+ rollbackAnnotationEvent(event)
338
+ //==========================
339
+ {
340
+ if (this.#annotationDrawControl) {
341
+ this.#annotationDrawControl.rollbackEvent(event)
342
+ }
343
+ }
344
+
345
+ addAnnotationFeature(feature)
346
+ //===========================
347
+ {
348
+ if (this.#annotationDrawControl) {
349
+ this.#annotationDrawControl.addFeature(feature)
350
+ }
351
+ }
352
+
353
+ refreshAnnotationFeatureGeometry(feature)
354
+ //=======================================
355
+ {
356
+ if (this.#annotationDrawControl) {
357
+ this.#annotationDrawControl.refreshGeometry(feature)
358
+ }
359
+ }
360
+
361
+ #setPaint(options)
362
+ //================
363
+ {
364
+ this._layerManager.setPaint(options)
365
+ if (this.#paths3dLayer) {
366
+ this.#paths3dLayer.setPaint(options)
367
+ }
368
+ }
369
+
281
370
  setPaint(options)
282
371
  //===============
283
372
  {
284
373
  this.__colourOptions = options;
285
- this._layerManager.setPaint(options);
374
+ this.#setPaint(options);
286
375
  }
287
376
 
288
377
  getLayers()
@@ -300,7 +389,9 @@ export class UserInteractions
300
389
  enable3dPaths(enable=true)
301
390
  //========================
302
391
  {
303
- this.#paths3dLayer.enable(enable)
392
+ if (this.#paths3dLayer) {
393
+ this.#paths3dLayer.enable(enable)
394
+ }
304
395
  }
305
396
 
306
397
  getSystems()
@@ -332,19 +423,43 @@ export class UserInteractions
332
423
  return undefined;
333
424
  }
334
425
 
426
+ #getFeatureState(feature)
427
+ //=======================
428
+ {
429
+ return this._map.getFeatureState(feature)
430
+ }
431
+
432
+ #removeFeatureState(feature, key)
433
+ //===============================
434
+ {
435
+ this._map.removeFeatureState(feature, key)
436
+ if (this.#paths3dLayer) {
437
+ this.#paths3dLayer.removeFeatureState(feature.id, key)
438
+ }
439
+ }
440
+
441
+ #setFeatureState(feature, state)
442
+ //==============================
443
+ {
444
+ this._map.setFeatureState(feature, state)
445
+ if (this.#paths3dLayer) {
446
+ this.#paths3dLayer.setFeatureState(feature.id, state)
447
+ }
448
+ }
449
+
335
450
  enableMapFeature(feature, enable=true)
336
451
  //====================================
337
452
  {
338
453
  if (feature !== undefined) {
339
- const state = this._map.getFeatureState(feature);
454
+ const state = this.#getFeatureState(feature);
340
455
  if ('hidden' in state) {
341
456
  if (enable) {
342
- this._map.removeFeatureState(feature, 'hidden');
457
+ this.#removeFeatureState(feature, 'hidden');
343
458
  } else if (!state.hidden) {
344
- this._map.setFeatureState(feature, { 'hidden': true });
459
+ this.#setFeatureState(feature, { hidden: true });
345
460
  }
346
461
  } else if (!enable) {
347
- this._map.setFeatureState(feature, { 'hidden': true });
462
+ this.#setFeatureState(feature, { hidden: true });
348
463
  }
349
464
  this.__enableFeatureMarker(feature.id, enable);
350
465
  }
@@ -392,11 +507,11 @@ export class UserInteractions
392
507
  //=======================
393
508
  {
394
509
  if (feature.id) {
395
- const state = this._map.getFeatureState(feature);
510
+ const state = this.#getFeatureState(feature);
396
511
  return (state !== undefined
397
512
  && (!('hidden' in state) || !state.hidden));
398
513
  }
399
- return false;
514
+ return DRAW_ANNOTATION_LAYERS.includes(feature.layer.id)
400
515
  }
401
516
 
402
517
  featureSelected_(featureId)
@@ -409,7 +524,7 @@ export class UserInteractions
409
524
  //================================
410
525
  {
411
526
  const ann = this._flatmap.annotation(featureId);
412
- if ('sckan' in ann) {
527
+ if (ann && 'sckan' in ann) {
413
528
  const sckanState = this._layerManager.sckanState;
414
529
  if (sckanState === 'none'
415
530
  || sckanState === 'valid' && !ann.sckan
@@ -426,16 +541,16 @@ export class UserInteractions
426
541
  } else {
427
542
  const feature = this.mapFeature(featureId);
428
543
  if (feature !== undefined) {
429
- const state = this._map.getFeatureState(feature);
544
+ const state = this.#getFeatureState(feature);
430
545
  if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
431
- this._map.setFeatureState(feature, { 'selected': true });
546
+ this.#setFeatureState(feature, { selected: true });
432
547
  this._selectedFeatureIds.set(featureId, 1);
433
548
  result = true;
434
549
  }
435
550
  }
436
551
  }
437
552
  if (result && noSelection) {
438
- this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
553
+ this.#setPaint({...this.__colourOptions, dimmed: dim});
439
554
  }
440
555
  return result;
441
556
  }
@@ -451,13 +566,13 @@ export class UserInteractions
451
566
  } else {
452
567
  const feature = this.mapFeature(featureId);
453
568
  if (feature !== undefined) {
454
- this._map.removeFeatureState(feature, 'selected');
569
+ this.#removeFeatureState(feature, 'selected');
455
570
  this._selectedFeatureIds.delete(+featureId);
456
571
  }
457
572
  }
458
573
  }
459
574
  if (this._selectedFeatureIds.size === 0) {
460
- this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
575
+ this.#setPaint({...this.__colourOptions, dimmed: false});
461
576
  }
462
577
  }
463
578
 
@@ -467,28 +582,42 @@ export class UserInteractions
467
582
  for (const featureId of this._selectedFeatureIds.keys()) {
468
583
  const feature = this.mapFeature(featureId);
469
584
  if (feature !== undefined) {
470
- this._map.removeFeatureState(feature, 'selected');
585
+ this.#removeFeatureState(feature, 'selected');
471
586
  }
472
587
  }
473
588
  this._selectedFeatureIds.clear();
474
- this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
589
+ this.#setPaint({...this.__colourOptions, dimmed: false});
475
590
  }
476
591
 
477
592
  activateFeature(feature)
478
593
  //======================
479
594
  {
480
595
  if (feature !== undefined) {
481
- this._map.setFeatureState(feature, { active: true });
482
- this._activeFeatures.push(feature);
596
+ this.#setFeatureState(feature, { active: true });
597
+ this._activeFeatures.add(feature);
598
+ }
599
+ }
600
+
601
+ activateLineFeatures(lineFeatures)
602
+ //================================
603
+ {
604
+ for (const lineFeature of lineFeatures) {
605
+ const lineFeatureId = +lineFeature.properties.featureId // Ensure numeric
606
+ this.activateFeature(lineFeature)
607
+ const lineIds = new Set(lineFeatures.map(f => f.properties.featureId))
608
+ for (const featureId of this.__pathManager.lineFeatureIds(lineIds)) {
609
+ this.activateFeature(this.mapFeature(featureId))
610
+ }
483
611
  }
484
612
  }
485
613
 
486
614
  resetActiveFeatures_()
487
615
  //====================
488
616
  {
489
- while (this._activeFeatures.length > 0) {
490
- this._map.removeFeatureState(this._activeFeatures.pop(), 'active');
617
+ for (const feature of this._activeFeatures) {
618
+ this.#removeFeatureState(feature, 'active');
491
619
  }
620
+ this._activeFeatures.clear()
492
621
  }
493
622
 
494
623
  smallestAnnotatedPolygonFeature_(features)
@@ -500,7 +629,7 @@ export class UserInteractions
500
629
  let smallestFeature = null;
501
630
  for (const feature of features) {
502
631
  if (feature.geometry.type.includes('Polygon')
503
- && this._map.getFeatureState(feature)['map-annotation']) {
632
+ && this.#getFeatureState(feature)['map-annotation']) {
504
633
  const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
505
634
  const area = turfArea(polygon);
506
635
  if (smallestFeature === null || smallestArea > area) {
@@ -733,7 +862,9 @@ export class UserInteractions
733
862
  tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
734
863
  }
735
864
  let renderedLabel;
736
- if (('label' in properties || 'hyperlink' in properties)
865
+ if (('label' in properties
866
+ || 'hyperlink' in properties
867
+ || 'user_label' in properties)
737
868
  && (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
738
869
  const renderedLabel = getRenderedLabel(properties);
739
870
  if ('hyperlink' in properties) {
@@ -775,6 +906,19 @@ export class UserInteractions
775
906
  this.resetActiveFeatures_();
776
907
  }
777
908
 
909
+ #renderedFeatures(point)
910
+ //======================
911
+ {
912
+ let features = []
913
+ if (this.#paths3dLayer) {
914
+ features = this.#paths3dLayer.queryFeaturesAtPoint(point)
915
+ }
916
+ if (features.length === 0) {
917
+ features = this._map.queryRenderedFeatures(point)
918
+ }
919
+ return features.filter(feature => this.__featureEnabled(feature));
920
+ }
921
+
778
922
  mouseMoveEvent_(event)
779
923
  //====================
780
924
  {
@@ -793,8 +937,7 @@ export class UserInteractions
793
937
  }
794
938
 
795
939
  // Get all the features at the current point
796
- const features = this._map.queryRenderedFeatures(event.point)
797
- .filter(feature => this.__featureEnabled(feature));
940
+ const features = this.#renderedFeatures(event.point)
798
941
  if (features.length === 0) {
799
942
  this._lastFeatureMouseEntered = null;
800
943
  this._lastFeatureModelsMouse = null;
@@ -834,19 +977,11 @@ export class UserInteractions
834
977
  if (lineFeatures.length > 0) {
835
978
  tooltip = this.lineTooltip_(lineFeatures);
836
979
  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
- }
980
+ this.activateLineFeatures(lineFeatures)
847
981
  } else {
848
982
  let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
849
- || 'label' in feature.properties)
983
+ || 'label' in feature.properties
984
+ || 'user_label' in feature.properties)
850
985
  && (!('tooltip' in feature.properties)
851
986
  || feature.properties.tooltip)))
852
987
  .sort((a, b) => (a.properties.area - b.properties.area));
@@ -859,6 +994,9 @@ export class UserInteractions
859
994
  labelledFeatures = groupFeatures;
860
995
  }
861
996
  const feature = labelledFeatures[0];
997
+ if (feature.properties.user_drawn) {
998
+ feature.id = feature.properties.id
999
+ }
862
1000
  tooltip = this.tooltipHtml_(feature.properties);
863
1001
  tooltipFeature = feature;
864
1002
  if (this._flatmap.options.debug) { // Do this when Info on and not debug??
@@ -943,7 +1081,7 @@ export class UserInteractions
943
1081
  //=============================
944
1082
  {
945
1083
  if (feature !== undefined) {
946
- const clickedFeatureId = feature.id;
1084
+ const clickedFeatureId = +feature.id;
947
1085
  const dim = !('properties' in feature
948
1086
  && 'kind' in feature.properties
949
1087
  && ['cell-type', 'scaffold', 'tissue'].includes(feature.properties.kind));
@@ -982,9 +1120,9 @@ export class UserInteractions
982
1120
  }
983
1121
 
984
1122
  this.__clearActiveMarker();
985
- const clickedFeatures = this._map.queryRenderedFeatures(event.point)
986
- .filter(feature => this.__featureEnabled(feature));
987
- if (clickedFeatures.length == 0){
1123
+
1124
+ const clickedFeatures = this.#renderedFeatures(event.point)
1125
+ if (clickedFeatures.length == 0) {
988
1126
  this.unselectFeatures();
989
1127
  return;
990
1128
  }
@@ -1060,7 +1198,7 @@ export class UserInteractions
1060
1198
  //=========================================
1061
1199
  {
1062
1200
  this.__pathManager.enablePathsByType('centreline', enable, force);
1063
- this._layerManager.setPaint({showCentrelines: enable});
1201
+ this.#setPaint({showCentrelines: enable});
1064
1202
  }
1065
1203
 
1066
1204
  enableSckanPaths(sckanState, enable=true)
@@ -1087,7 +1225,7 @@ export class UserInteractions
1087
1225
  excludeAnnotated(exclude=false)
1088
1226
  //=============================
1089
1227
  {
1090
- this._layerManager.setPaint({excludeAnnotated: exclude});
1228
+ this.#setPaint({excludeAnnotated: exclude});
1091
1229
  }
1092
1230
 
1093
1231
  //==============================================================================