@abi-software/flatmap-viewer 2.4.3 → 2.5.0-a.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.
- package/README.rst +1 -1
- package/package.json +6 -1
- package/src/flatmap-viewer.js +2 -2
- package/src/interactions.js +116 -19
- package/src/mathjax.js +100 -0
- package/src/pathways.js +8 -0
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.
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.5.0-a.1``
|
|
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.
|
|
3
|
+
"version": "2.5.0-a.1",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
|
|
6
6
|
"main": "src/main.js",
|
|
@@ -18,14 +18,19 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.10.4",
|
|
21
|
+
"@deck.gl/core": "^8.9.33",
|
|
22
|
+
"@deck.gl/layers": "^8.9.33",
|
|
23
|
+
"@deck.gl/mapbox": "^8.9.33",
|
|
21
24
|
"@fortawesome/fontawesome-free": "^6.4.0",
|
|
22
25
|
"@turf/area": "^6.5.0",
|
|
23
26
|
"@turf/bbox": "^6.5.0",
|
|
24
27
|
"@turf/helpers": "^6.5.0",
|
|
25
28
|
"@turf/projection": "^6.5.0",
|
|
26
29
|
"bezier-js": "^6.1.0",
|
|
30
|
+
"colord": "^2.9.3",
|
|
27
31
|
"html-es6cape": "^2.0.2",
|
|
28
32
|
"maplibre-gl": ">=3.6.0",
|
|
33
|
+
"mathjax-full": "^3.2.2",
|
|
29
34
|
"minisearch": "^2.2.1",
|
|
30
35
|
"polylabel": "^1.1.0"
|
|
31
36
|
},
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -170,8 +170,8 @@ class FlatMap
|
|
|
170
170
|
|
|
171
171
|
// Disable map rotation
|
|
172
172
|
|
|
173
|
-
this._map.dragRotate.disable();
|
|
174
|
-
this._map.touchZoomRotate.disableRotation();
|
|
173
|
+
//this._map.dragRotate.disable();
|
|
174
|
+
//this._map.touchZoomRotate.disableRotation();
|
|
175
175
|
|
|
176
176
|
// Add navigation controls if option set
|
|
177
177
|
|
package/src/interactions.js
CHANGED
|
@@ -22,6 +22,10 @@ limitations under the License.
|
|
|
22
22
|
|
|
23
23
|
//==============================================================================
|
|
24
24
|
|
|
25
|
+
import {colord} from "colord";
|
|
26
|
+
|
|
27
|
+
import {MapboxOverlay as DeckOverlay} from '@deck.gl/mapbox';
|
|
28
|
+
import {ArcLayer} from '@deck.gl/layers';
|
|
25
29
|
import maplibregl from 'maplibre-gl';
|
|
26
30
|
|
|
27
31
|
import {default as turfArea} from '@turf/area';
|
|
@@ -34,7 +38,7 @@ import polylabel from 'polylabel';
|
|
|
34
38
|
//==============================================================================
|
|
35
39
|
|
|
36
40
|
import {LayerManager} from './layers';
|
|
37
|
-
import {PATHWAYS_LAYER, PathManager} from './pathways';
|
|
41
|
+
import {pathColour, PATHWAYS_LAYER, PathManager} from './pathways';
|
|
38
42
|
import {VECTOR_TILES_SOURCE} from './styling';
|
|
39
43
|
import {SystemsManager} from './systems';
|
|
40
44
|
|
|
@@ -45,6 +49,7 @@ import {PathControl} from './controls/paths';
|
|
|
45
49
|
import {SearchControl} from './controls/search';
|
|
46
50
|
import {SystemsControl} from './controls/systems';
|
|
47
51
|
import {TaxonsControl} from './controls/taxons';
|
|
52
|
+
import {latex2Svg} from './mathjax';
|
|
48
53
|
|
|
49
54
|
import * as utils from './utils';
|
|
50
55
|
|
|
@@ -92,6 +97,38 @@ function expandBounds(bbox1, bbox2, padding)
|
|
|
92
97
|
|
|
93
98
|
//==============================================================================
|
|
94
99
|
|
|
100
|
+
function labelPosition(feature)
|
|
101
|
+
{
|
|
102
|
+
const polygon = feature.geometry.coordinates;
|
|
103
|
+
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
104
|
+
const precision = ('area' in feature.properties)
|
|
105
|
+
? Math.sqrt(feature.properties.area)/500000
|
|
106
|
+
: 0.1;
|
|
107
|
+
return polylabel(polygon, precision);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//==============================================================================
|
|
111
|
+
|
|
112
|
+
function getRenderedLabel(properties)
|
|
113
|
+
{
|
|
114
|
+
if (!('renderedLabel' in properties)) {
|
|
115
|
+
const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
|
|
116
|
+
+ properties.label.substr(1)).replaceAll("\n", "<br/>")
|
|
117
|
+
: '';
|
|
118
|
+
properties.renderedLabel = label.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
|
|
119
|
+
}
|
|
120
|
+
return properties.renderedLabel;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Should this be in `pathways.js` ??
|
|
124
|
+
function pathColourRGB(pathType, alpha=255)
|
|
125
|
+
{
|
|
126
|
+
const rgb = colord(pathColour(pathType)).toRgb();
|
|
127
|
+
return [rgb.r, rgb.g, rgb.b, alpha];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//==============================================================================
|
|
131
|
+
|
|
95
132
|
export class UserInteractions
|
|
96
133
|
{
|
|
97
134
|
constructor(flatmap)
|
|
@@ -190,6 +227,72 @@ export class UserInteractions
|
|
|
190
227
|
}
|
|
191
228
|
}
|
|
192
229
|
|
|
230
|
+
const pathData = [...flatmap.annotations.values()]
|
|
231
|
+
.filter(ann => ann['tile-layer'] === 'pathways'
|
|
232
|
+
&& ann['geometry'] === 'LineString'
|
|
233
|
+
&& 'type' in ann && ann['type'].startsWith('line')
|
|
234
|
+
&& 'kind' in ann && !ann['kind'].includes('arterial') && !ann['kind'].includes('venous')
|
|
235
|
+
&& 'pathStartPosition' in ann
|
|
236
|
+
&& 'pathEndPosition' in ann)
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
{
|
|
240
|
+
"featureId": 37,
|
|
241
|
+
"id": "ilxtr_neuron-type-keast-10",
|
|
242
|
+
"kind": "sensory",
|
|
243
|
+
"label": "L6-S1 sensory neuron innervating bladder",
|
|
244
|
+
"models": "ilxtr:neuron-type-keast-10",
|
|
245
|
+
"sckan": true,
|
|
246
|
+
"source": "https://apinatomy.org/uris/models/keast-bladder",
|
|
247
|
+
"taxons": ["NCBITaxon:10116"],
|
|
248
|
+
"tile-layer": "pathways",
|
|
249
|
+
"type": "line",
|
|
250
|
+
"bounds": [1.5454426659162825, -1.6254174813389017, 1.7478459571498208, -1.3632864333949712],
|
|
251
|
+
"markerPosition": [1.6466443115330516, -1.4943519573669364],
|
|
252
|
+
"geometry": "LineString",
|
|
253
|
+
"layer": "neural-routes",
|
|
254
|
+
"pathStartPosition": [1.7478459571498208, -1.3632864333949712],
|
|
255
|
+
"pathEndPosition": [1.5454426659162825, -1.6254174813389017]
|
|
256
|
+
}
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
const deckOverlay = new DeckOverlay({
|
|
260
|
+
layers: [
|
|
261
|
+
new ArcLayer({
|
|
262
|
+
id: 'arcs',
|
|
263
|
+
data: pathData,
|
|
264
|
+
pickable: true,
|
|
265
|
+
autoHighlight: true,
|
|
266
|
+
numSegments: 100,
|
|
267
|
+
onHover: (i, e) => {
|
|
268
|
+
//console.log('hover', i, e)
|
|
269
|
+
if (i.object) {
|
|
270
|
+
const lineFeatureId = +i.object.featureId;
|
|
271
|
+
this.__activateFeature(this.mapFeature(lineFeatureId));
|
|
272
|
+
for (const featureId of this.__pathManager.lineFeatureIds([lineFeatureId])) {
|
|
273
|
+
if (+featureId !== lineFeatureId) {
|
|
274
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
onClick: (i, e) => {
|
|
280
|
+
console.log('click', i, e)
|
|
281
|
+
},
|
|
282
|
+
// Styles
|
|
283
|
+
getSourcePosition: f => f.pathStartPosition,
|
|
284
|
+
getTargetPosition: f => f.pathEndPosition,
|
|
285
|
+
getSourceColor: f => pathColourRGB(f.kind, 160),
|
|
286
|
+
getTargetColor: f => pathColourRGB(f.kind, 160),
|
|
287
|
+
highlightColor: o => pathColourRGB(o.object.kind),
|
|
288
|
+
getWidth: 3
|
|
289
|
+
})
|
|
290
|
+
],
|
|
291
|
+
getTooltip: ({object}) => object && object.label
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this._map.addControl(deckOverlay);
|
|
295
|
+
|
|
193
296
|
// Handle mouse events
|
|
194
297
|
|
|
195
298
|
this._map.on('click', this.clickEvent_.bind(this));
|
|
@@ -350,9 +453,12 @@ export class UserInteractions
|
|
|
350
453
|
__featureEnabled(feature)
|
|
351
454
|
//=======================
|
|
352
455
|
{
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
456
|
+
if (feature.id) {
|
|
457
|
+
const state = this._map.getFeatureState(feature);
|
|
458
|
+
return (state !== undefined
|
|
459
|
+
&& (!('hidden' in state) || !state.hidden));
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
356
462
|
}
|
|
357
463
|
|
|
358
464
|
featureSelected_(featureId)
|
|
@@ -486,7 +592,6 @@ export class UserInteractions
|
|
|
486
592
|
this.__clearModal();
|
|
487
593
|
this.__clearActiveMarker();
|
|
488
594
|
this.unselectFeatures();
|
|
489
|
-
this.__enablePathFeatures(this.__pathManager.allFeatureIds(), true);
|
|
490
595
|
}
|
|
491
596
|
|
|
492
597
|
clearSearchResults(reset=true)
|
|
@@ -668,7 +773,6 @@ export class UserInteractions
|
|
|
668
773
|
tooltips.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
669
774
|
}
|
|
670
775
|
if ('label' in properties && (!('tooltip' in properties) || properties.tooltip)) {
|
|
671
|
-
let tooltip = '';
|
|
672
776
|
const label = properties.label;
|
|
673
777
|
const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
|
|
674
778
|
if (!tooltips.includes(cleanLabel)) {
|
|
@@ -690,19 +794,18 @@ export class UserInteractions
|
|
|
690
794
|
if ('warning' in properties) {
|
|
691
795
|
tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
692
796
|
}
|
|
797
|
+
let renderedLabel;
|
|
693
798
|
if (('label' in properties || 'hyperlink' in properties)
|
|
694
799
|
&& (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
|
|
695
|
-
const
|
|
696
|
-
+ properties.label.substr(1)).replaceAll("\n", "<br/>")
|
|
697
|
-
: '';
|
|
800
|
+
const renderedLabel = getRenderedLabel(properties);
|
|
698
801
|
if ('hyperlink' in properties) {
|
|
699
|
-
if (
|
|
802
|
+
if (renderedLabel === '') {
|
|
700
803
|
tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
|
|
701
804
|
} else {
|
|
702
|
-
tooltip.push(`<a href='${properties.hyperlink}'>${
|
|
805
|
+
tooltip.push(`<a href='${properties.hyperlink}'>${renderedLabel}</a></div>`);
|
|
703
806
|
}
|
|
704
807
|
} else {
|
|
705
|
-
tooltip.push(
|
|
808
|
+
tooltip.push(renderedLabel);
|
|
706
809
|
}
|
|
707
810
|
}
|
|
708
811
|
return (tooltip.length === 0) ? ''
|
|
@@ -1078,13 +1181,7 @@ export class UserInteractions
|
|
|
1078
1181
|
]
|
|
1079
1182
|
});
|
|
1080
1183
|
if (features.length > 0) {
|
|
1081
|
-
|
|
1082
|
-
const polygon = feature.geometry.coordinates;
|
|
1083
|
-
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
1084
|
-
const precision = ('area' in feature.properties)
|
|
1085
|
-
? Math.sqrt(feature.properties.area)/500000
|
|
1086
|
-
: 0.1;
|
|
1087
|
-
position = polylabel(polygon, precision);
|
|
1184
|
+
position = labelPosition(features[0]);
|
|
1088
1185
|
}
|
|
1089
1186
|
}
|
|
1090
1187
|
this.__markerPositions.set(featureId, position);
|
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
|
+
//==============================================================================
|
package/src/pathways.js
CHANGED
|
@@ -52,6 +52,14 @@ const PATH_TYPES = [
|
|
|
52
52
|
export const PATH_STYLE_RULES =
|
|
53
53
|
PATH_TYPES.flatMap(pathType => [['==', ['get', 'kind'], pathType.type], pathType.colour]);
|
|
54
54
|
|
|
55
|
+
export const PATH_COLOURS =
|
|
56
|
+
Object.fromEntries(PATH_TYPES.flatMap(pathType => [[pathType.type, pathType.colour]]));
|
|
57
|
+
|
|
58
|
+
export function pathColour(pathType)
|
|
59
|
+
{
|
|
60
|
+
return PATH_COLOURS[pathType] || '#FF0';
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
//==============================================================================
|
|
56
64
|
|
|
57
65
|
export class PathManager
|