@abi-software/flatmap-viewer 2.4.2-b.5 → 2.4.4

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 CHANGED
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
38
38
  Package Installation
39
39
  ====================
40
40
 
41
- * ``npm install @abi-software/flatmap-viewer@2.4.2-b.5``
41
+ * ``npm install @abi-software/flatmap-viewer@2.4.4``
42
42
 
43
43
  Documentation
44
44
  -------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/flatmap-viewer",
3
- "version": "2.4.2-b.5",
3
+ "version": "2.4.4",
4
4
  "description": "Flatmap viewer using Maplibre GL",
5
5
  "repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
6
6
  "main": "src/main.js",
@@ -19,14 +19,14 @@
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.10.4",
21
21
  "@fortawesome/fontawesome-free": "^6.4.0",
22
- "@turf/area": "^6.0.1",
23
- "@turf/bbox": "^6.0.1",
24
- "@turf/helpers": "^6.1.4",
22
+ "@turf/area": "^6.5.0",
23
+ "@turf/bbox": "^6.5.0",
24
+ "@turf/helpers": "^6.5.0",
25
25
  "@turf/projection": "^6.5.0",
26
26
  "bezier-js": "^6.1.0",
27
27
  "html-es6cape": "^2.0.2",
28
- "jspanel4": "^4.16.1",
29
- "maplibre-gl": ">=3.0.0",
28
+ "maplibre-gl": ">=3.6.0",
29
+ "mathjax-full": "^3.2.2",
30
30
  "minisearch": "^2.2.1",
31
31
  "polylabel": "^1.1.0"
32
32
  },
@@ -526,73 +526,6 @@ export class NerveControl
526
526
 
527
527
  //==============================================================================
528
528
 
529
- export class AnnotatedControl
530
- {
531
- constructor(ui, options={excludeAnnotated: false})
532
- {
533
- this.__ui = ui;
534
- this.__map = undefined;
535
- this.__exclude = options.excludeAnnotated || false;
536
- }
537
-
538
- getDefaultPosition()
539
- //==================
540
- {
541
- return 'top-right';
542
- }
543
-
544
- onAdd(map)
545
- //========
546
- {
547
- this.__map = map;
548
- this.__container = document.createElement('div');
549
- this.__container.className = 'maplibregl-ctrl';
550
-
551
- this.__button = document.createElement('button');
552
- this.__button.id = 'map-annotated-button';
553
- this.__button.className = 'control-button text-button';
554
- this.__button.setAttribute('type', 'button');
555
- this.__button.setAttribute('aria-label', 'Show/hide annotated paths');
556
- this.__button.textContent = 'UNANN';
557
- this.__button.title = 'Show/hide annotated paths';
558
- this.__container.appendChild(this.__button);
559
-
560
- this.__container.addEventListener('click', this.onClick_.bind(this));
561
- this.__setBackground();
562
- return this.__container;
563
- }
564
-
565
- __setBackground()
566
- //===============
567
- {
568
- if (this.__exclude) {
569
- this.__button.setAttribute('style', 'background: red');
570
- } else {
571
- this.__button.removeAttribute('style');
572
- }
573
- }
574
-
575
- onRemove()
576
- //========
577
- {
578
- this.__container.parentNode.removeChild(this.__container);
579
- this.__map = undefined;
580
- }
581
-
582
- onClick_(event)
583
- //=============
584
- {
585
- if (event.target.id === 'map-annotated-button') {
586
- this.__exclude = !this.__exclude;
587
- this.__setBackground();
588
- this.__ui.excludeAnnotated(this.__exclude);
589
- }
590
- event.stopPropagation();
591
- }
592
- }
593
-
594
- //==============================================================================
595
-
596
529
  export class BackgroundControl
597
530
  {
598
531
  constructor(flatmap)
@@ -95,9 +95,6 @@ class FlatMap
95
95
  this.__addAnnotation(featureId, annotation);
96
96
  this.__searchIndex.indexMetadata(featureId, annotation);
97
97
  }
98
- if (this.options.annotator) {
99
- this.__addAnnotatedComments();
100
- }
101
98
 
102
99
  // Set base of source URLs in map's style
103
100
 
@@ -223,25 +220,6 @@ class FlatMap
223
220
  });
224
221
  }
225
222
 
226
- async __addAnnotatedComments()
227
- //============================
228
- {
229
- const url = this.makeServerUrl('', 'annotator/')
230
- const annotatedFeatures = await loadJSON(url);
231
- for (const annotatedId of annotatedFeatures) {
232
- const featureId = this.__annIdToFeatureId.get(annotatedId);
233
- if (featureId) {
234
- const url = this.makeServerUrl(annotatedId, 'annotator/')
235
- const annotations = await loadJSON(url);
236
- for (const annotation of annotations) { // In order of most recent to oldest
237
- if ('rdfs:comment' in annotation) {
238
- this.__searchIndex.indexText(featureId, annotation['rdfs:comment']);
239
- }
240
- }
241
- }
242
- }
243
- }
244
-
245
223
  async setupUserInteractions_()
246
224
  //============================
247
225
  {
@@ -1237,20 +1215,6 @@ class FlatMap
1237
1215
 
1238
1216
  //==========================================================================
1239
1217
 
1240
- /**
1241
- * Highlight features on the map.
1242
- *
1243
- * @param {Array.<string>} externalIds An array of anaotomical terms identifing features to highlight
1244
- */
1245
- highlightFeatures(externalIds)
1246
- //============================
1247
- {
1248
- if (this._userInteractions !== null) {
1249
- const featureIds = this.modelFeatureIdList(externalIds);
1250
- this._userInteractions.highlightFeatures(featureIds);
1251
- }
1252
- }
1253
-
1254
1218
  /**
1255
1219
  * Select features on the map.
1256
1220
  *
@@ -1435,7 +1399,6 @@ export class MapManager
1435
1399
  * @arg options.showPosition {boolean} Show ``position`` of tooltip.
1436
1400
  * @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
1437
1401
  * another application so show a number of controls. Defaults to ``false``.
1438
- * @arg options.annotator {boolean} Allow interactive annotation of features and paths.
1439
1402
  * @example
1440
1403
  * const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
1441
1404
  *
@@ -33,19 +33,19 @@ import polylabel from 'polylabel';
33
33
 
34
34
  //==============================================================================
35
35
 
36
- import {Annotator} from './annotation';
37
36
  import {LayerManager} from './layers';
38
37
  import {PATHWAYS_LAYER, PathManager} from './pathways';
39
38
  import {VECTOR_TILES_SOURCE} from './styling';
40
39
  import {SystemsManager} from './systems';
41
40
 
42
41
  import {displayedProperties, InfoControl} from './controls/info';
43
- import {AnnotatedControl, BackgroundControl, LayerControl, NerveControl,
42
+ import {BackgroundControl, LayerControl, NerveControl,
44
43
  SCKANControl} from './controls/controls';
45
44
  import {PathControl} from './controls/paths';
46
45
  import {SearchControl} from './controls/search';
47
46
  import {SystemsControl} from './controls/systems';
48
47
  import {TaxonsControl} from './controls/taxons';
48
+ import {latex2Svg} from './mathjax';
49
49
 
50
50
  import * as utils from './utils';
51
51
 
@@ -93,6 +93,31 @@ function expandBounds(bbox1, bbox2, padding)
93
93
 
94
94
  //==============================================================================
95
95
 
96
+ function labelPosition(feature)
97
+ {
98
+ const polygon = feature.geometry.coordinates;
99
+ // Rough heuristic. Area is in km^2; below appears to be good enough.
100
+ const precision = ('area' in feature.properties)
101
+ ? Math.sqrt(feature.properties.area)/500000
102
+ : 0.1;
103
+ return polylabel(polygon, precision);
104
+ }
105
+
106
+ //==============================================================================
107
+
108
+ function getRenderedLabel(properties)
109
+ {
110
+ if (!('renderedLabel' in properties)) {
111
+ const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
112
+ + properties.label.substr(1)).replaceAll("\n", "<br/>")
113
+ : '';
114
+ properties.renderedLabel = label.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
115
+ }
116
+ return properties.renderedLabel;
117
+ }
118
+
119
+ //==============================================================================
120
+
96
121
  export class UserInteractions
97
122
  {
98
123
  constructor(flatmap)
@@ -152,13 +177,6 @@ export class UserInteractions
152
177
  this.enableCentrelines(this.__pathManager.enabledCentrelines, true);
153
178
  }
154
179
 
155
- // Add annotation capability
156
- if (flatmap.options.annotator) {
157
- this.__setupAnnotation();
158
- } else {
159
- this.__annotator = null;
160
- }
161
-
162
180
  // Note features that are FC systems
163
181
  this.__systemsManager = new SystemsManager(this._flatmap, this, featuresEnabled);
164
182
 
@@ -196,11 +214,6 @@ export class UserInteractions
196
214
  // Connectivity taxon control for AC maps
197
215
  this._map.addControl(new TaxonsControl(flatmap));
198
216
  }
199
-
200
- if (flatmap.options.annotator) {
201
- // Show/hide annotated paths
202
- this._map.addControl(new AnnotatedControl(this, flatmap.options.layerOptions));
203
- }
204
217
  }
205
218
 
206
219
  // Handle mouse events
@@ -256,41 +269,6 @@ export class UserInteractions
256
269
  }
257
270
  }
258
271
 
259
- async __setupAnnotation()
260
- //=======================
261
- {
262
- // Add annotation capability
263
-
264
- this.__annotator = new Annotator(this._flatmap, this);
265
- const annotated_features = await this.__annotator.annotated_features();
266
-
267
- // Flag features that have annotations
268
- this.__featureIdToMapId = new Map();
269
- for (const [mapId, ann] of this._flatmap.annotations) {
270
- this.__featureIdToMapId.set(ann.id, mapId);
271
- const feature = this.mapFeature(mapId);
272
- if (feature !== undefined) {
273
- this._map.setFeatureState(feature, { 'map-annotation': true });
274
- if (annotated_features.includes(ann.id)) {
275
- this._map.setFeatureState(feature, { 'annotated': true });
276
- }
277
- }
278
- }
279
- }
280
-
281
- setFeatureAnnotated(featureId)
282
- //============================
283
- {
284
- if (this.__annotator) {
285
- // featureId v's geoJSON id
286
- const mapId = this.__featureIdToMapId.get(featureId);
287
- const feature = this.mapFeature(mapId);
288
- if (feature !== undefined) {
289
- this._map.setFeatureState(feature, { 'annotated': true });
290
- }
291
- }
292
- }
293
-
294
272
  setPaint(options)
295
273
  //===============
296
274
  {
@@ -398,9 +376,12 @@ export class UserInteractions
398
376
  __featureEnabled(feature)
399
377
  //=======================
400
378
  {
401
- const state = this._map.getFeatureState(feature);
402
- return (state !== undefined
403
- && (!('hidden' in state) || !state.hidden));
379
+ if (feature.id) {
380
+ const state = this._map.getFeatureState(feature);
381
+ return (state !== undefined
382
+ && (!('hidden' in state) || !state.hidden));
383
+ }
384
+ return false;
404
385
  }
405
386
 
406
387
  featureSelected_(featureId)
@@ -495,19 +476,6 @@ export class UserInteractions
495
476
  }
496
477
  }
497
478
 
498
- highlightFeature_(featureId)
499
- //==========================
500
- {
501
- featureId = +featureId; // Ensure numeric
502
- this.__activateFeature(this.mapFeature(featureId));
503
- }
504
-
505
- unhighlightFeatures_()
506
- //====================
507
- {
508
- this.resetActiveFeatures_();
509
- }
510
-
511
479
  smallestAnnotatedPolygonFeature_(features)
512
480
  //========================================
513
481
  {
@@ -556,30 +524,6 @@ export class UserInteractions
556
524
  this.unselectFeatures();
557
525
  }
558
526
 
559
- /**
560
- * Highlight features on the map.
561
- *
562
- * @param {Array.<string>} featureIds An array of feature identifiers to highlight
563
- */
564
- highlightFeatures(featureIds)
565
- //===========================
566
- {
567
- if (featureIds.length) {
568
- this.unhighlightFeatures_();
569
- for (const featureId of featureIds) {
570
- const annotation = this._flatmap.annotation(featureId);
571
- if (annotation) {
572
- this.highlightFeature_(featureId);
573
- if ('type' in annotation && annotation.type.startsWith('line')) {
574
- for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
575
- this.highlightFeature_(pathFeatureId);
576
- }
577
- }
578
- }
579
- }
580
- }
581
- }
582
-
583
527
  /**
584
528
  * Select features on the map.
585
529
  *
@@ -753,7 +697,6 @@ export class UserInteractions
753
697
  tooltips.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
754
698
  }
755
699
  if ('label' in properties && (!('tooltip' in properties) || properties.tooltip)) {
756
- let tooltip = '';
757
700
  const label = properties.label;
758
701
  const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
759
702
  if (!tooltips.includes(cleanLabel)) {
@@ -775,19 +718,18 @@ export class UserInteractions
775
718
  if ('warning' in properties) {
776
719
  tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
777
720
  }
721
+ let renderedLabel;
778
722
  if (('label' in properties || 'hyperlink' in properties)
779
723
  && (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
780
- const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
781
- + properties.label.substr(1)).replaceAll("\n", "<br/>")
782
- : '';
724
+ const renderedLabel = getRenderedLabel(properties);
783
725
  if ('hyperlink' in properties) {
784
- if (label === '') {
726
+ if (renderedLabel === '') {
785
727
  tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
786
728
  } else {
787
- tooltip.push(`<a href='${properties.hyperlink}'>${label}</a></div>`);
729
+ tooltip.push(`<a href='${properties.hyperlink}'>${renderedLabel}</a></div>`);
788
730
  }
789
731
  } else {
790
- tooltip.push(label);
732
+ tooltip.push(renderedLabel);
791
733
  }
792
734
  }
793
735
  return (tooltip.length === 0) ? ''
@@ -1018,28 +960,6 @@ export class UserInteractions
1018
960
  }
1019
961
  }
1020
962
 
1021
- __annotationEvent(features)
1022
- //=========================
1023
- {
1024
- if (!this.__annotator) {
1025
- return;
1026
- }
1027
-
1028
- event.preventDefault();
1029
-
1030
- // Remove any tooltip
1031
- this.removeTooltip_();
1032
-
1033
- // Don't respond to mouse events while the dialog is open
1034
- this.setModal_();
1035
-
1036
- // The annotation dialog...
1037
- this.__annotator.annotate(features, () => {
1038
- this.unselectFeatures();
1039
- this.__clearModal();
1040
- });
1041
- }
1042
-
1043
963
  clickEvent_(event)
1044
964
  //================
1045
965
  {
@@ -1054,14 +974,8 @@ export class UserInteractions
1054
974
  this.unselectFeatures();
1055
975
  return;
1056
976
  }
1057
- const originalEvent = event.originalEvent;
1058
- if (originalEvent.altKey) {
1059
- this.__annotationEvent(clickedFeatures);
1060
- return;
1061
- }
1062
-
1063
977
  const clickedFeature = clickedFeatures[0];
1064
- this.selectionEvent_(originalEvent, clickedFeature);
978
+ this.selectionEvent_(event.originalEvent, clickedFeature);
1065
979
  if (this._modal) {
1066
980
  // Remove tooltip, reset active features, etc
1067
981
  this.__resetFeatureDisplay();
@@ -1191,13 +1105,7 @@ export class UserInteractions
1191
1105
  ]
1192
1106
  });
1193
1107
  if (features.length > 0) {
1194
- const feature = features[0];
1195
- const polygon = feature.geometry.coordinates;
1196
- // Rough heuristic. Area is in km^2; below appears to be good enough.
1197
- const precision = ('area' in feature.properties)
1198
- ? Math.sqrt(feature.properties.area)/500000
1199
- : 0.1;
1200
- position = polylabel(polygon, precision);
1108
+ position = labelPosition(features[0]);
1201
1109
  }
1202
1110
  }
1203
1111
  this.__markerPositions.set(featureId, position);
package/src/main.js CHANGED
@@ -62,7 +62,6 @@ export async function standaloneViewer(map_endpoint=null, options={})
62
62
  showId: true,
63
63
  showPosition: false,
64
64
  standalone: true,
65
- annotator: true,
66
65
  }, options);
67
66
 
68
67
  function loadMap(id, taxon, sex)
package/src/mathjax.js ADDED
@@ -0,0 +1,100 @@
1
+
2
+ /******************************************************************************
3
+
4
+ Flatmap viewer and annotation tool
5
+
6
+ Copyright (c) 2019-2023 David Brooks
7
+
8
+ Licensed under the Apache License, Version 2.0 (the "License");
9
+ you may not use this file except in compliance with the License.
10
+ You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing, software
15
+ distributed under the License is distributed on an "AS IS" BASIS,
16
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ See the License for the specific language governing permissions and
18
+ limitations under the License.
19
+
20
+ ******************************************************************************/
21
+
22
+ /*************************************************************************
23
+ *
24
+ * direct/tex2svg
25
+ *
26
+ * Uses MathJax v3 to convert a TeX string to an SVG string.
27
+ *
28
+ * ----------------------------------------------------------------------
29
+ *
30
+ * Copyright (c) 2018 The MathJax Consortium
31
+ *
32
+ * Licensed under the Apache License, Version 2.0 (the "License");
33
+ * you may not use this file except in compliance with the License.
34
+ * You may obtain a copy of the License at
35
+ *
36
+ * http://www.apache.org/licenses/LICENSE-2.0
37
+ *
38
+ * Unless required by applicable law or agreed to in writing, software
39
+ * distributed under the License is distributed on an "AS IS" BASIS,
40
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
41
+ * See the License for the specific language governing permissions and
42
+ * limitations under the License.
43
+ */
44
+
45
+ //==============================================================================
46
+
47
+ const EM_SIZE = 16;
48
+ const EX_SIZE = 8;
49
+
50
+ //==============================================================================
51
+
52
+ // Load the packages needed for MathJax
53
+ import {mathjax} from 'mathjax-full/js/mathjax';
54
+ import {TeX} from 'mathjax-full/js/input/tex';
55
+ import {SVG} from 'mathjax-full/js/output/svg';
56
+ import {browserAdaptor} from 'mathjax-full/js/adaptors/browserAdaptor';
57
+ import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html';
58
+ import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages'; // required to load `textmacros`
59
+
60
+ //==============================================================================
61
+
62
+ // Minimal CSS needed for stand-alone image
63
+ export const LatexStyleRules = [
64
+ 'svg a{fill:blue;stroke:blue}',
65
+ // Round the corners of filled background rectangles
66
+ '[data-mml-node="mstyle"]>rect[data-bgcolor="true"]{rx: 8%; ry: 12%}',
67
+ '[data-mml-node="merror"]>g{fill:red;stroke:red}',
68
+ '[data-mml-node="merror"]>rect[data-background]{fill:yellow;stroke:none}',
69
+ '[data-frame],[data-line]{stroke-width:70px;fill:none}',
70
+ '.mjx-dashed{stroke-dasharray:140}',
71
+ '.mjx-dotted{stroke-linecap:round;stroke-dasharray:0,140}',
72
+ 'use[data-c]{stroke-width:3px}'
73
+ ].join('');
74
+
75
+ //==============================================================================
76
+
77
+ // Create DOM adaptor and register it for HTML documents
78
+ const adaptor = browserAdaptor();
79
+ RegisterHTMLHandler(adaptor);
80
+
81
+ //==============================================================================
82
+
83
+ const tex = new TeX({packages: AllPackages}); // ['base', 'textmacros']});
84
+ const svg = new SVG({fontCache: 'local'});
85
+ const html = mathjax.document('', {InputJax: tex, OutputJax: svg});
86
+
87
+ //==============================================================================
88
+
89
+ export function latex2Svg(latex)
90
+ {
91
+ const node = html.convert(latex, {
92
+ display: false, // process as inline math
93
+ em: 2*EM_SIZE,
94
+ ex: 2*EX_SIZE,
95
+ });
96
+ let result = adaptor.innerHTML(node);
97
+ return result.replace(/<defs>/, `<defs><style>${LatexStyleRules}</style>`);
98
+ }
99
+
100
+ //==============================================================================