@abi-software/flatmap-viewer 2.3.3-b.4 → 2.4.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 +1 -1
- package/src/annotation.js +2 -2
- package/src/controls/info.js +4 -1
- package/src/controls/minimap.js +1 -1
- package/src/flatmap-viewer.js +27 -3
- package/src/interactions.js +64 -36
- package/src/layers.js +10 -5
- package/src/main.js +1 -0
- package/src/mapserver.js +19 -12
- package/src/pathways.js +6 -7
- package/src/search.js +6 -5
- package/src/styling.js +10 -4
- package/src/systems.js +35 -5
- package/src/utils.js +3 -3
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.4.0-a.1``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
package/src/annotation.js
CHANGED
|
@@ -508,7 +508,7 @@ export class Annotator
|
|
|
508
508
|
border: '2px solid #080',
|
|
509
509
|
borderRadius: '.5rem',
|
|
510
510
|
panelSize: 'auto auto',
|
|
511
|
-
position: 'left-top',
|
|
511
|
+
position: 'left-top 50 70',
|
|
512
512
|
content: panelContent,
|
|
513
513
|
data: features[0].properties,
|
|
514
514
|
closeOnEscape: true,
|
|
@@ -588,7 +588,7 @@ export class Annotator
|
|
|
588
588
|
border: '2px solid #080',
|
|
589
589
|
borderRadius: '.5rem',
|
|
590
590
|
panelSize: '725px auto',
|
|
591
|
-
position: 'left-top',
|
|
591
|
+
position: 'left-top 50 70',
|
|
592
592
|
data: {
|
|
593
593
|
flatmap: this.__flatmap
|
|
594
594
|
},
|
package/src/controls/info.js
CHANGED
|
@@ -25,8 +25,10 @@ import { indexedProperties } from '../search.js';
|
|
|
25
25
|
export const displayedProperties = [
|
|
26
26
|
'id',
|
|
27
27
|
'class',
|
|
28
|
+
'cd-class',
|
|
28
29
|
'fc-class',
|
|
29
30
|
'fc-kind',
|
|
31
|
+
'name',
|
|
30
32
|
...indexedProperties
|
|
31
33
|
];
|
|
32
34
|
|
|
@@ -137,6 +139,7 @@ export class InfoControl
|
|
|
137
139
|
this._active = true;
|
|
138
140
|
button.classList.add('control-button-active');
|
|
139
141
|
} else {
|
|
142
|
+
this.reset();
|
|
140
143
|
this._active = false;
|
|
141
144
|
button.classList.remove('control-button-active');
|
|
142
145
|
}
|
|
@@ -194,7 +197,7 @@ export class InfoControl
|
|
|
194
197
|
const featureIds = [];
|
|
195
198
|
const displayFeatures = [];
|
|
196
199
|
for (const feat of featureList) {
|
|
197
|
-
if (featureIds.
|
|
200
|
+
if (!featureIds.includes(feat.id)) {
|
|
198
201
|
featureIds.push(feat.id);
|
|
199
202
|
const displayFeat = {};
|
|
200
203
|
displayProperties.forEach(prop => {
|
package/src/controls/minimap.js
CHANGED
|
@@ -132,7 +132,7 @@ export class MinimapControl
|
|
|
132
132
|
let width = DEFAULTS.width;
|
|
133
133
|
if (typeof this._options.width === 'string') {
|
|
134
134
|
width = parseInt(this._options.width);
|
|
135
|
-
if (this._options.width.
|
|
135
|
+
if (this._options.width.includes('%')) {
|
|
136
136
|
width = width*mapCanvasElement.width/100;
|
|
137
137
|
}
|
|
138
138
|
} else if (typeof this._options.width === 'number') {
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -33,7 +33,7 @@ import '../static/css/flatmap-viewer.css';
|
|
|
33
33
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
|
-
import {MapServer} from './mapserver.js';
|
|
36
|
+
import {MapServer, loadJSON} from './mapserver.js';
|
|
37
37
|
import {SearchIndex} from './search.js';
|
|
38
38
|
import {UserInteractions} from './interactions.js';
|
|
39
39
|
|
|
@@ -77,11 +77,15 @@ class FlatMap
|
|
|
77
77
|
this.__datasetToFeatureIds = new Map();
|
|
78
78
|
this.__modelToFeatureIds = new Map();
|
|
79
79
|
this.__mapSourceToFeatureIds = new Map();
|
|
80
|
+
this.__annIdToFeatureId = new Map();
|
|
80
81
|
|
|
81
82
|
for (const [featureId, annotation] of Object.entries(mapDescription.annotations)) {
|
|
82
83
|
this.__addAnnotation(featureId, annotation);
|
|
83
84
|
this.__searchIndex.indexMetadata(featureId, annotation);
|
|
84
85
|
}
|
|
86
|
+
if (this.options.annotator) {
|
|
87
|
+
this.__addAnnotatedComments();
|
|
88
|
+
}
|
|
85
89
|
|
|
86
90
|
// Set base of source URLs in map's style
|
|
87
91
|
|
|
@@ -165,7 +169,7 @@ class FlatMap
|
|
|
165
169
|
if (mapDescription.options.navigationControl) {
|
|
166
170
|
const value = mapDescription.options.navigationControl;
|
|
167
171
|
const position = ((typeof value === 'string')
|
|
168
|
-
&&
|
|
172
|
+
&& ['top-left', 'top-right', 'bottom-right', 'bottom-left'].includes(value))
|
|
169
173
|
? value : 'bottom-right';
|
|
170
174
|
this._map.addControl(new NavigationControl(this), position);
|
|
171
175
|
}
|
|
@@ -207,6 +211,25 @@ class FlatMap
|
|
|
207
211
|
});
|
|
208
212
|
}
|
|
209
213
|
|
|
214
|
+
async __addAnnotatedComments()
|
|
215
|
+
//============================
|
|
216
|
+
{
|
|
217
|
+
const url = this.makeServerUrl('', 'annotator/')
|
|
218
|
+
const annotatedFeatures = await loadJSON(url);
|
|
219
|
+
for (const annotatedId of annotatedFeatures) {
|
|
220
|
+
const featureId = this.__annIdToFeatureId.get(annotatedId);
|
|
221
|
+
if (featureId) {
|
|
222
|
+
const url = this.makeServerUrl(annotatedId, 'annotator/')
|
|
223
|
+
const annotations = await loadJSON(url);
|
|
224
|
+
for (const annotation of annotations) { // In order of most recent to oldest
|
|
225
|
+
if ('rdfs:comment' in annotation) {
|
|
226
|
+
this.__searchIndex.indexText(featureId, annotation['rdfs:comment']);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
210
233
|
async setupUserInteractions_()
|
|
211
234
|
//============================
|
|
212
235
|
{
|
|
@@ -498,6 +521,7 @@ class FlatMap
|
|
|
498
521
|
this.__updateFeatureIdMap('dataset', this.__datasetToFeatureIds, ann);
|
|
499
522
|
this.__updateFeatureIdMap('models', this.__modelToFeatureIds, ann);
|
|
500
523
|
this.__updateFeatureIdMap('source', this.__mapSourceToFeatureIds, ann);
|
|
524
|
+
this.__annIdToFeatureId.set(ann.id, featureId);
|
|
501
525
|
}
|
|
502
526
|
|
|
503
527
|
modelFeatureIds(anatomicalId)
|
|
@@ -943,7 +967,7 @@ class FlatMap
|
|
|
943
967
|
if (property in properties) {
|
|
944
968
|
const value = properties[property];
|
|
945
969
|
if (value !== undefined) {
|
|
946
|
-
if (jsonProperties.
|
|
970
|
+
if (jsonProperties.includes(property)) {
|
|
947
971
|
data[property] = JSON.parse(properties[property])
|
|
948
972
|
} else {
|
|
949
973
|
data[property] = properties[property];
|
package/src/interactions.js
CHANGED
|
@@ -261,7 +261,7 @@ export class UserInteractions
|
|
|
261
261
|
const feature = this.mapFeature(mapId);
|
|
262
262
|
if (feature !== undefined) {
|
|
263
263
|
this._map.setFeatureState(feature, { 'map-annotation': true });
|
|
264
|
-
if (annotated_features.
|
|
264
|
+
if (annotated_features.includes(ann.id)) {
|
|
265
265
|
this._map.setFeatureState(feature, { 'annotated': true });
|
|
266
266
|
}
|
|
267
267
|
}
|
|
@@ -402,19 +402,36 @@ export class UserInteractions
|
|
|
402
402
|
selectFeature(featureId, dim=true)
|
|
403
403
|
//================================
|
|
404
404
|
{
|
|
405
|
-
|
|
406
|
-
if (
|
|
407
|
-
this._layerManager.
|
|
405
|
+
const ann = this._flatmap.annotation(featureId);
|
|
406
|
+
if ('sckan' in ann) {
|
|
407
|
+
const sckanState = this._layerManager.sckanState;
|
|
408
|
+
if (sckanState === 'none'
|
|
409
|
+
|| sckanState === 'valid' && !ann.sckan
|
|
410
|
+
|| sckanState === 'invalid' && ann.sckan) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
408
413
|
}
|
|
414
|
+
featureId = +featureId; // Ensure numeric
|
|
415
|
+
let result = false;
|
|
416
|
+
const noSelection = (this._selectedFeatureIds.size === 0);
|
|
409
417
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
410
418
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
419
|
+
result = true;
|
|
411
420
|
} else {
|
|
412
421
|
const feature = this.mapFeature(featureId);
|
|
413
422
|
if (feature !== undefined) {
|
|
414
|
-
this._map.
|
|
415
|
-
|
|
423
|
+
const state = this._map.getFeatureState(feature);
|
|
424
|
+
if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
|
|
425
|
+
this._map.setFeatureState(feature, { 'selected': true });
|
|
426
|
+
this._selectedFeatureIds.set(featureId, 1);
|
|
427
|
+
result = true;
|
|
428
|
+
}
|
|
416
429
|
}
|
|
417
430
|
}
|
|
431
|
+
if (result && noSelection) {
|
|
432
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
418
435
|
}
|
|
419
436
|
|
|
420
437
|
unselectFeature(featureId)
|
|
@@ -566,10 +583,11 @@ export class UserInteractions
|
|
|
566
583
|
for (const featureId of featureIds) {
|
|
567
584
|
const annotation = this._flatmap.annotation(featureId);
|
|
568
585
|
if (annotation) {
|
|
569
|
-
this.selectFeature(featureId)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
586
|
+
if (this.selectFeature(featureId)) {
|
|
587
|
+
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
588
|
+
for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
|
|
589
|
+
this.selectFeature(pathFeatureId);
|
|
590
|
+
}
|
|
573
591
|
}
|
|
574
592
|
}
|
|
575
593
|
}
|
|
@@ -609,13 +627,15 @@ export class UserInteractions
|
|
|
609
627
|
for (const featureId of featureIds) {
|
|
610
628
|
const annotation = this._flatmap.annotation(featureId);
|
|
611
629
|
if (annotation) {
|
|
612
|
-
this.selectFeature(featureId)
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
630
|
+
if (this.selectFeature(featureId)) {
|
|
631
|
+
bbox = expandBounds(bbox, annotation.bounds);
|
|
632
|
+
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
633
|
+
for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
|
|
634
|
+
if (this.selectFeature(pathFeatureId)) {
|
|
635
|
+
const pathAnnotation = this._flatmap.annotation(pathFeatureId)
|
|
636
|
+
bbox = expandBounds(bbox, pathAnnotation.bounds);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
619
639
|
}
|
|
620
640
|
}
|
|
621
641
|
}
|
|
@@ -882,7 +902,7 @@ export class UserInteractions
|
|
|
882
902
|
const htmlList = [];
|
|
883
903
|
const featureIds = [];
|
|
884
904
|
for (const feature of labelledFeatures) {
|
|
885
|
-
if (featureIds.
|
|
905
|
+
if (!featureIds.includes(feature.id)) {
|
|
886
906
|
featureIds.push(feature.id);
|
|
887
907
|
for (const prop of debugProperties) {
|
|
888
908
|
if (prop in feature.properties) {
|
|
@@ -918,26 +938,34 @@ export class UserInteractions
|
|
|
918
938
|
//=======================================
|
|
919
939
|
{
|
|
920
940
|
// Show a tooltip
|
|
921
|
-
if (html !== '') {
|
|
922
|
-
|
|
923
|
-
closeButton: false,
|
|
924
|
-
closeOnClick: false,
|
|
925
|
-
maxWidth: 'none',
|
|
926
|
-
className: 'flatmap-tooltip-popup'
|
|
927
|
-
});
|
|
941
|
+
if (html !== '' || this._flatmap.options.showId && feature !== null) {
|
|
942
|
+
let header = '';
|
|
928
943
|
if (this._flatmap.options.showPosition) {
|
|
929
944
|
const pt = turf.point(lngLat.toArray());
|
|
930
945
|
const gps = turfProjection.toMercator(pt);
|
|
931
946
|
const coords = gps.geometry.coordinates;
|
|
932
|
-
|
|
947
|
+
header = (feature === null)
|
|
933
948
|
? JSON.stringify(coords)
|
|
934
|
-
: `${JSON.stringify(coords)} (${feature.id}
|
|
949
|
+
: `${JSON.stringify(coords)} (${feature.id})`;
|
|
950
|
+
}
|
|
951
|
+
if (this._flatmap.options.showId && feature !== null && 'id' in feature.properties) {
|
|
952
|
+
header = `${header} ${feature.properties.id}`;
|
|
953
|
+
}
|
|
954
|
+
if (header !== '') {
|
|
935
955
|
html = `<span>${header}</span><br/>${html}`;
|
|
936
956
|
}
|
|
937
|
-
|
|
938
|
-
.
|
|
939
|
-
|
|
940
|
-
|
|
957
|
+
if (html !== '') {
|
|
958
|
+
this._tooltip = new maplibre.Popup({
|
|
959
|
+
closeButton: false,
|
|
960
|
+
closeOnClick: false,
|
|
961
|
+
maxWidth: 'none',
|
|
962
|
+
className: 'flatmap-tooltip-popup'
|
|
963
|
+
});
|
|
964
|
+
this._tooltip
|
|
965
|
+
.setLngLat(lngLat)
|
|
966
|
+
.setHTML(html)
|
|
967
|
+
.addTo(this._map);
|
|
968
|
+
}
|
|
941
969
|
}
|
|
942
970
|
}
|
|
943
971
|
|
|
@@ -948,7 +976,7 @@ export class UserInteractions
|
|
|
948
976
|
const clickedFeatureId = feature.id;
|
|
949
977
|
const dim = !('properties' in feature
|
|
950
978
|
&& 'kind' in feature.properties
|
|
951
|
-
&& ['cell-type', 'scaffold', 'tissue'].
|
|
979
|
+
&& ['cell-type', 'scaffold', 'tissue'].includes(feature.properties.kind));
|
|
952
980
|
if (!(event.ctrlKey || event.metaKey)) {
|
|
953
981
|
let selecting = true;
|
|
954
982
|
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
@@ -1149,7 +1177,7 @@ export class UserInteractions
|
|
|
1149
1177
|
|
|
1150
1178
|
for (const featureId of featureIds) {
|
|
1151
1179
|
const annotation = this._flatmap.annotation(featureId);
|
|
1152
|
-
if (annotation.geometry.
|
|
1180
|
+
if (!annotation.geometry.includes('Polygon')) {
|
|
1153
1181
|
continue;
|
|
1154
1182
|
}
|
|
1155
1183
|
if (!('marker' in annotation)) {
|
|
@@ -1230,7 +1258,7 @@ export class UserInteractions
|
|
|
1230
1258
|
for (const [marker, id] of this.__markerIdByMarker.entries()) {
|
|
1231
1259
|
if (visibleBounds.contains(marker.getLngLat())) {
|
|
1232
1260
|
const annotation = this.__annotationByMarkerId.get(id);
|
|
1233
|
-
if (anatomicalIds.
|
|
1261
|
+
if (!anatomicalIds.includes(annotation.models)) {
|
|
1234
1262
|
anatomicalIds.push(annotation.models);
|
|
1235
1263
|
}
|
|
1236
1264
|
}
|
|
@@ -1247,7 +1275,7 @@ export class UserInteractions
|
|
|
1247
1275
|
return;
|
|
1248
1276
|
}
|
|
1249
1277
|
|
|
1250
|
-
if (['mouseenter', 'mouseleave', 'click'].
|
|
1278
|
+
if (['mouseenter', 'mouseleave', 'click'].includes(event.type)) {
|
|
1251
1279
|
this.__activeMarker = marker;
|
|
1252
1280
|
|
|
1253
1281
|
// Remove any existing tooltips
|
|
@@ -1257,7 +1285,7 @@ export class UserInteractions
|
|
|
1257
1285
|
// Reset cursor
|
|
1258
1286
|
marker.getElement().style.cursor = 'default';
|
|
1259
1287
|
|
|
1260
|
-
if (['mouseenter', 'click'].
|
|
1288
|
+
if (['mouseenter', 'click'].includes(event.type)) {
|
|
1261
1289
|
const markerId = this.__markerIdByMarker.get(marker);
|
|
1262
1290
|
const annotation = this.__annotationByMarkerId.get(markerId);
|
|
1263
1291
|
// The marker's feature
|
package/src/layers.js
CHANGED
|
@@ -115,8 +115,7 @@ class MapFeatureLayers extends MapStylingLayers
|
|
|
115
115
|
// if no image layers then make feature borders (and lines?) more visible...??
|
|
116
116
|
if (haveVectorLayers) {
|
|
117
117
|
const featuresVectorSource = this.vectorSourceId(FEATURES_LAYER);
|
|
118
|
-
const vectorFeatures = vectorTileSource.vectorLayerIds
|
|
119
|
-
.indexOf(featuresVectorSource) >= 0;
|
|
118
|
+
const vectorFeatures = vectorTileSource.vectorLayerIds.includes(featuresVectorSource);
|
|
120
119
|
if (vectorFeatures) {
|
|
121
120
|
this.__addStyleLayer(style.FeatureFillLayer);
|
|
122
121
|
this.__addStyleLayer(style.FeatureDashLineLayer);
|
|
@@ -152,7 +151,7 @@ class MapFeatureLayers extends MapStylingLayers
|
|
|
152
151
|
const pathwaysVectorSource = this.vectorSourceId(PATHWAYS_LAYER);
|
|
153
152
|
if (this.__map.getSource('vector-tiles')
|
|
154
153
|
.vectorLayerIds
|
|
155
|
-
.
|
|
154
|
+
.includes(pathwaysVectorSource)) {
|
|
156
155
|
this.__addStyleLayer(style.AnnotatedPathLayer, PATHWAYS_LAYER);
|
|
157
156
|
|
|
158
157
|
this.__addStyleLayer(style.CentrelineEdgeLayer, PATHWAYS_LAYER);
|
|
@@ -298,6 +297,12 @@ export class LayerManager
|
|
|
298
297
|
return layers;
|
|
299
298
|
}
|
|
300
299
|
|
|
300
|
+
get sckanState()
|
|
301
|
+
//==============
|
|
302
|
+
{
|
|
303
|
+
return this.__layerOptions.sckan;
|
|
304
|
+
}
|
|
305
|
+
|
|
301
306
|
activate(layerId, enable=true)
|
|
302
307
|
//============================
|
|
303
308
|
{
|
|
@@ -337,8 +342,8 @@ export class LayerManager
|
|
|
337
342
|
//=======================================
|
|
338
343
|
{
|
|
339
344
|
const currentState = this.__layerOptions.sckan;
|
|
340
|
-
const validEnabled = ['valid', 'all'].
|
|
341
|
-
const invalidEnabled = ['invalid', 'all'].
|
|
345
|
+
const validEnabled = ['valid', 'all'].includes(currentState);
|
|
346
|
+
const invalidEnabled = ['invalid', 'all'].includes(currentState);
|
|
342
347
|
let newState = sckanState.toLowerCase();
|
|
343
348
|
if (newState === 'valid') {
|
|
344
349
|
if (enable && !validEnabled) {
|
package/src/main.js
CHANGED
package/src/mapserver.js
CHANGED
|
@@ -22,6 +22,24 @@ limitations under the License.
|
|
|
22
22
|
|
|
23
23
|
//==============================================================================
|
|
24
24
|
|
|
25
|
+
export async function loadJSON(url)
|
|
26
|
+
//=================================
|
|
27
|
+
{
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
headers: {
|
|
31
|
+
"Accept": "application/json; charset=utf-8",
|
|
32
|
+
"Cache-Control": "no-store"
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Cannot access ${url}`);
|
|
37
|
+
}
|
|
38
|
+
return response.json();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//==============================================================================
|
|
42
|
+
|
|
25
43
|
export class MapServer
|
|
26
44
|
{
|
|
27
45
|
constructor(url)
|
|
@@ -39,18 +57,7 @@ export class MapServer
|
|
|
39
57
|
async loadJSON(relativePath)
|
|
40
58
|
//==========================
|
|
41
59
|
{
|
|
42
|
-
|
|
43
|
-
const response = await fetch(url, {
|
|
44
|
-
method: 'GET',
|
|
45
|
-
headers: {
|
|
46
|
-
"Accept": "application/json; charset=utf-8",
|
|
47
|
-
"Cache-Control": "no-store"
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
if (!response.ok) {
|
|
51
|
-
throw new Error(`Cannot access ${url}`);
|
|
52
|
-
}
|
|
53
|
-
return response.json();
|
|
60
|
+
return loadJSON(this.url(relativePath));
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
package/src/pathways.js
CHANGED
|
@@ -42,7 +42,7 @@ const PATH_TYPES = [
|
|
|
42
42
|
{ type: "arterial", label: "Arterial blood vessel", colour: "#F00", enabled: false},
|
|
43
43
|
{ type: "venous", label: "Venous blood vessel", colour: "#2F6EBA", enabled: false},
|
|
44
44
|
{ type: "centreline", label: "Nerve centrelines", colour: "#CCC", enabled: false},
|
|
45
|
-
{ type: "error", label: "Paths with errors or warnings", colour: "#FF0"}
|
|
45
|
+
{ type: "error", label: "Paths with errors or warnings", colour: "#FF0", enabled: false}
|
|
46
46
|
];
|
|
47
47
|
|
|
48
48
|
export const PATH_STYLE_RULES =
|
|
@@ -65,12 +65,11 @@ export class PathManager
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
this.__pathModelPaths = {};
|
|
69
|
-
this.__pathToPathModel = {};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const pathNerves = {}; // pathId: [nerveIds]
|
|
68
|
+
this.__pathModelPaths = {}; // pathModelId: [pathIds]
|
|
69
|
+
this.__pathToPathModel = {}; // pathId: pathModelId
|
|
70
|
+
this.__paths = {}; // pathId: path
|
|
71
|
+
const pathLines = {}; // pathId: [lineIds]
|
|
72
|
+
const pathNerves = {}; // pathId: [nerveIds]
|
|
74
73
|
if ('paths' in flatmap.pathways) {
|
|
75
74
|
for (const [pathId, path] of Object.entries(flatmap.pathways.paths)) {
|
|
76
75
|
pathLines[pathId] = path.lines;
|
package/src/search.js
CHANGED
|
@@ -56,18 +56,19 @@ export class SearchIndex
|
|
|
56
56
|
if (prop in metadata) {
|
|
57
57
|
const text = metadata[prop];
|
|
58
58
|
if (!textSeen.includes(text)) {
|
|
59
|
-
this.
|
|
59
|
+
this.indexText(featureId, text);
|
|
60
60
|
textSeen.push(text);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
indexText(featureId, text)
|
|
67
|
+
//========================
|
|
68
68
|
{
|
|
69
69
|
text = text.replace(new RegExp('<br/>', 'g'), ' ')
|
|
70
|
-
.replace('\n', ' ')
|
|
70
|
+
.replace(new RegExp('\n', 'g'), ' ')
|
|
71
|
+
;
|
|
71
72
|
if (text) {
|
|
72
73
|
this._searchEngine.add({
|
|
73
74
|
id: this._featureIds.length,
|
|
@@ -95,7 +96,7 @@ export class SearchIndex
|
|
|
95
96
|
const options = {};
|
|
96
97
|
let results = [];
|
|
97
98
|
text = text.trim()
|
|
98
|
-
if (text.length > 2 && ["'", '"'].
|
|
99
|
+
if (text.length > 2 && ["'", '"'].includes(text.slice(0, 1))) {
|
|
99
100
|
text = text.replaceAll(text.slice(0, 1), '');
|
|
100
101
|
results = this._searchEngine.search(text, {prefix: true, combineWith: 'AND'});
|
|
101
102
|
} else if (text.length > 1) {
|
package/src/styling.js
CHANGED
|
@@ -31,7 +31,7 @@ import {PATH_STYLE_RULES} from './pathways.js';
|
|
|
31
31
|
//==============================================================================
|
|
32
32
|
|
|
33
33
|
const COLOUR_ACTIVE = 'blue';
|
|
34
|
-
const COLOUR_ANNOTATED = '#
|
|
34
|
+
const COLOUR_ANNOTATED = '#C8F';
|
|
35
35
|
const COLOUR_SELECTED = '#0F0';
|
|
36
36
|
const COLOUR_HIDDEN = '#D8D8D8';
|
|
37
37
|
|
|
@@ -418,15 +418,18 @@ export class AnnotatedPathLayer extends VectorStyleLayer
|
|
|
418
418
|
|
|
419
419
|
paintStyle(options={}, changes=false)
|
|
420
420
|
{
|
|
421
|
+
const dimmed = 'dimmed' in options && options.dimmed;
|
|
421
422
|
const exclude = 'excludeAnnotated' in options && options.excludeAnnotated;
|
|
422
423
|
const paintStyle = {
|
|
423
424
|
'line-color': COLOUR_ANNOTATED,
|
|
424
425
|
'line-dasharray': [5, 0.5, 3, 0.5],
|
|
425
426
|
'line-opacity': [
|
|
426
427
|
'case',
|
|
428
|
+
['boolean', ['feature-state', 'active'], false], 0.8,
|
|
429
|
+
['boolean', ['feature-state', 'selected'], false], 0.8,
|
|
427
430
|
['boolean', ['feature-state', 'hidden'], false], 0.05,
|
|
428
431
|
['boolean', ['feature-state', 'annotated'], false],
|
|
429
|
-
(exclude ? 0.05 : 0.8),
|
|
432
|
+
((exclude || dimmed) ? 0.05 : 0.8),
|
|
430
433
|
0.6
|
|
431
434
|
],
|
|
432
435
|
'line-width': [
|
|
@@ -435,7 +438,11 @@ export class AnnotatedPathLayer extends VectorStyleLayer
|
|
|
435
438
|
['case',
|
|
436
439
|
['boolean', ['feature-state', 'hidden'], false], 0.0,
|
|
437
440
|
['boolean', ['feature-state', 'annotated'], false],
|
|
438
|
-
exclude ? 0.0 : (['*', 1.
|
|
441
|
+
exclude ? 0.0 : (['*', 1.1, ['case',
|
|
442
|
+
['has', 'stroke-width'], ['get', 'stroke-width'],
|
|
443
|
+
['boolean', ['feature-state', 'active'], false], 1.1,
|
|
444
|
+
['boolean', ['feature-state', 'active'], false], 1.1,
|
|
445
|
+
1.0]]),
|
|
439
446
|
0.0
|
|
440
447
|
],
|
|
441
448
|
STROKE_INTERPOLATION
|
|
@@ -446,7 +453,6 @@ export class AnnotatedPathLayer extends VectorStyleLayer
|
|
|
446
453
|
|
|
447
454
|
style(options)
|
|
448
455
|
{
|
|
449
|
-
const dimmed = 'dimmed' in options && options.dimmed;
|
|
450
456
|
return {
|
|
451
457
|
...super.style(),
|
|
452
458
|
'type': 'line',
|
package/src/systems.js
CHANGED
|
@@ -16,18 +16,26 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
16
16
|
See the License for the specific language governing permissions and
|
|
17
17
|
limitations under the License.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
******************************************************************************/
|
|
20
|
+
|
|
21
|
+
const FC_KIND = {
|
|
22
|
+
SYSTEM: ['fc:System', 'fc-class:System'],
|
|
23
|
+
ORGAN: ['fc:Organ', 'fc-class:Organ'],
|
|
24
|
+
FTU: ['fc:Ftu', 'fc-class:Ftu']
|
|
25
|
+
};
|
|
26
|
+
|
|
20
27
|
//==============================================================================
|
|
21
28
|
|
|
22
29
|
export class SystemsManager
|
|
23
30
|
{
|
|
24
31
|
constructor(flatmap, ui, enabled=false)
|
|
25
32
|
{
|
|
33
|
+
this.__flatmap = flatmap;
|
|
26
34
|
this.__ui = ui;
|
|
27
35
|
this.__systems = new Map();
|
|
28
36
|
this.__enabledChildren = new Map();
|
|
29
|
-
for (const [
|
|
30
|
-
if (ann['fc-class']
|
|
37
|
+
for (const [_, ann] of flatmap.annotations) {
|
|
38
|
+
if (FC_KIND.SYSTEM.includes(ann['fc-class'])) {
|
|
31
39
|
const systemId = ann.name.replaceAll(' ', '_');
|
|
32
40
|
if (this.__systems.has(systemId)) {
|
|
33
41
|
this.__systems.get(systemId).featureIds.push(ann.featureId)
|
|
@@ -37,7 +45,8 @@ export class SystemsManager
|
|
|
37
45
|
colour: ann.colour,
|
|
38
46
|
featureIds: [ ann.featureId ],
|
|
39
47
|
enabled: false,
|
|
40
|
-
pathIds: ('path-ids' in ann) ? ann['path-ids'] : []
|
|
48
|
+
pathIds: ('path-ids' in ann) ? ann['path-ids'] : [],
|
|
49
|
+
organs: this.__children(ann.children, FC_KIND.ORGAN)
|
|
41
50
|
});
|
|
42
51
|
this.__ui.enableFeature(ann.featureId, false, true);
|
|
43
52
|
}
|
|
@@ -57,6 +66,26 @@ export class SystemsManager
|
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
__children(childFeatureIds, childClass)
|
|
70
|
+
//=====================================
|
|
71
|
+
{
|
|
72
|
+
const children = [];
|
|
73
|
+
for (const childFeatureId of childFeatureIds || []) {
|
|
74
|
+
const childAnnotation = this.__flatmap.annotation(childFeatureId);
|
|
75
|
+
if (childAnnotation !== undefined && childClass.includes(childAnnotation['fc-class'])) {
|
|
76
|
+
const child = {
|
|
77
|
+
label: childAnnotation.label,
|
|
78
|
+
models: childAnnotation.models
|
|
79
|
+
};
|
|
80
|
+
if (childClass === FC_KIND.ORGAN) {
|
|
81
|
+
child.ftus = this.__children(childAnnotation.children, FC_KIND.FTU)
|
|
82
|
+
};
|
|
83
|
+
children.push(child);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return children;
|
|
87
|
+
}
|
|
88
|
+
|
|
60
89
|
get systems()
|
|
61
90
|
//===========
|
|
62
91
|
{
|
|
@@ -66,7 +95,8 @@ export class SystemsManager
|
|
|
66
95
|
id: systemId,
|
|
67
96
|
name: system.name,
|
|
68
97
|
colour: system.colour,
|
|
69
|
-
enabled: system.enabled
|
|
98
|
+
enabled: system.enabled,
|
|
99
|
+
organs: system.organs
|
|
70
100
|
});
|
|
71
101
|
}
|
|
72
102
|
return systems;
|
package/src/utils.js
CHANGED
|
@@ -35,7 +35,7 @@ export class List extends Array {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
contains(element) {
|
|
38
|
-
return (super.
|
|
38
|
+
return (super.includes(element));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
extend(other) {
|
|
@@ -96,12 +96,12 @@ export class Mutex
|
|
|
96
96
|
|
|
97
97
|
export function normaliseId(id)
|
|
98
98
|
{
|
|
99
|
-
if (id.
|
|
99
|
+
if (!id.includes(':')) {
|
|
100
100
|
return id;
|
|
101
101
|
}
|
|
102
102
|
const parts = id.split(':')
|
|
103
103
|
const lastPart = parts[parts.length - 1]
|
|
104
|
-
if (['http', 'https', 'urn'].
|
|
104
|
+
if (['http', 'https', 'urn'].includes(parts[0]) || !'0123456789'.includes(lastPart[0])) {
|
|
105
105
|
return id;
|
|
106
106
|
}
|
|
107
107
|
parts[parts.length - 1] = lastPart.padStart(8, '0');
|