@abi-software/flatmap-viewer 2.2.10 → 2.2.11-b.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/flatmap-viewer",
3
- "version": "2.2.10",
3
+ "version": "2.2.11-b.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",
@@ -23,7 +23,7 @@
23
23
  "@turf/helpers": "^6.1.4",
24
24
  "@turf/projection": "^6.5.0",
25
25
  "bezier-js": "^6.1.0",
26
- "maplibre-gl": ">=1.15.3",
26
+ "maplibre-gl": ">=2.4.0",
27
27
  "minisearch": "^2.2.1",
28
28
  "polylabel": "^1.1.0"
29
29
  },
package/src/controls.js CHANGED
@@ -117,12 +117,16 @@ export class PathControl
117
117
 
118
118
  const innerHTML = [];
119
119
  innerHTML.push(`<label for="path-all-paths">ALL PATHS:</label><div class="nerve-line"></div><input id="path-all-paths" type="checkbox" checked/>`);
120
+ this.__checkedCount = 0;
120
121
  for (const path of this.__pathTypes) {
121
- innerHTML.push(`<label for="path-${path.type}">${path.label}</label><div class="nerve-line nerve-${path.type}"></div><input id="path-${path.type}" type="checkbox" checked/>`);
122
+ const checked = !('enabled' in path) || path.enabled ? 'checked' : '';
123
+ if (checked != '') {
124
+ this.__checkedCount += 1;
125
+ }
126
+ innerHTML.push(`<label for="path-${path.type}">${path.label}</label><div class="nerve-line nerve-${path.type}"></div><input id="path-${path.type}" type="checkbox" ${checked}/>`);
122
127
  }
123
128
  this._legend.innerHTML = innerHTML.join('\n');
124
- this.__checkedCount = this.__pathTypes.length;
125
- this.__halfCount = Math.trunc(this.__checkedCount/2);
129
+ this.__halfCount = Math.trunc(this.__pathTypes.length/2);
126
130
 
127
131
  this._button = document.createElement('button');
128
132
  this._button.id = 'nerve-key-button';
@@ -152,6 +156,9 @@ export class PathControl
152
156
  if (this._button.getAttribute('control-visible') === 'false') {
153
157
  this._container.appendChild(this._legend);
154
158
  this._button.setAttribute('control-visible', 'true');
159
+ const allPathsCheckbox = document.getElementById('path-all-paths');
160
+ allPathsCheckbox.indeterminate = this.__checkedCount < this.__pathTypes.length
161
+ && this.__checkedCount > 0;
155
162
  this._legend.focus();
156
163
  } else {
157
164
  this._legend = this._container.removeChild(this._legend);
@@ -206,7 +213,7 @@ export class LayerControl
206
213
  constructor(flatmap, layerManager)
207
214
  {
208
215
  this.__flatmap = flatmap;
209
- this.__manager = layerManager;
216
+ this.__layers = layerManager.layers;
210
217
  this.__map = undefined;
211
218
  }
212
219
 
@@ -224,18 +231,18 @@ export class LayerControl
224
231
  this.__container.className = 'maplibregl-ctrl';
225
232
  this.__container.id = 'flatmap-layer-control';
226
233
 
227
- this.__layers = document.createElement('div');
228
- this.__layers.id = 'layer-control-text';
229
- this.__layers.className = 'flatmap-layer-grid';
234
+ this.__layersControl = document.createElement('div');
235
+ this.__layersControl.id = 'layer-control-text';
236
+ this.__layersControl.className = 'flatmap-layer-grid';
230
237
 
231
238
  const innerHTML = [];
232
239
  innerHTML.push(`<label for="layer-all-layers">ALL LAYERS:</label><input id="layer-all-layers" type="checkbox" checked/>`);
233
- for (const layer of this.__manager.layers) {
240
+ for (const layer of this.__layers) {
234
241
  innerHTML.push(`<label for="layer-${layer.id}">${layer.description}</label><input id="layer-${layer.id}" type="checkbox" checked/>`);
235
242
  }
236
- this.__layers.innerHTML = innerHTML.join('\n');
243
+ this.__layersControl.innerHTML = innerHTML.join('\n');
237
244
 
238
- this.__layersCount = this.__manager.layers.length;
245
+ this.__layersCount = this.__layers;
239
246
  this.__checkedCount = this.__layersCount;
240
247
  this.__halfCount = Math.trunc(this.__checkedCount/2);
241
248
 
@@ -265,11 +272,11 @@ export class LayerControl
265
272
  {
266
273
  if (event.target.id === 'map-layers-button') {
267
274
  if (this.__button.getAttribute('control-visible') === 'false') {
268
- this.__container.appendChild(this.__layers);
275
+ this.__container.appendChild(this.__layersControl);
269
276
  this.__button.setAttribute('control-visible', 'true');
270
- this.__layers.focus();
277
+ this.__layersControl.focus();
271
278
  } else {
272
- this.__layers = this.__container.removeChild(this.__layers);
279
+ this.__layersControl = this.__container.removeChild(this.__layersControl);
273
280
  this.__button.setAttribute('control-visible', 'false');
274
281
  }
275
282
  } else if (event.target.tagName === 'INPUT') {
@@ -283,16 +290,16 @@ export class LayerControl
283
290
  } else {
284
291
  this.__checkedCount = 0;
285
292
  }
286
- for (const layer of this.__manager.layers) {
293
+ for (const layer of this.__layers) {
287
294
  const layerCheckbox = document.getElementById(`layer-${layer.id}`);
288
295
  if (layerCheckbox) {
289
296
  layerCheckbox.checked = event.target.checked;
290
- this.__manager.activate(layer.id, event.target.checked);
297
+ this.__flatmap.enableLayer(layer.id, event.target.checked);
291
298
  }
292
299
  }
293
300
  } else if (event.target.id.startsWith('layer-')) {
294
301
  const layerId = event.target.id.substring(6);
295
- this.__manager.activate(layerId, event.target.checked);
302
+ this.__flatmap.enableLayer(layerId, event.target.checked);
296
303
  if (event.target.checked) {
297
304
  this.__checkedCount += 1;
298
305
  } else {
@@ -316,6 +323,151 @@ export class LayerControl
316
323
 
317
324
  //==============================================================================
318
325
 
326
+
327
+ const SCKAN_STATES = [
328
+ {
329
+ 'id': 'VALID',
330
+ 'description': 'Path consistent with SCKAN'
331
+ },
332
+ {
333
+ 'id': 'INVALID',
334
+ 'description': 'Path inconsistent with SCKAN'
335
+ }
336
+ ];
337
+
338
+
339
+ export class SCKANControl
340
+ {
341
+ constructor(flatmap)
342
+ {
343
+ this.__flatmap = flatmap;
344
+ this.__map = undefined;
345
+ this.__state = 'valid';
346
+ }
347
+
348
+ getDefaultPosition()
349
+ //==================
350
+ {
351
+ return 'top-right';
352
+ }
353
+
354
+ onAdd(map)
355
+ //========
356
+ {
357
+ this.__map = map;
358
+ this.__container = document.createElement('div');
359
+ this.__container.className = 'maplibregl-ctrl';
360
+ this.__container.id = 'flatmap-layer-control';
361
+
362
+ this.__sckan = document.createElement('div');
363
+ this.__sckan.id = 'sckan-control-text';
364
+ this.__sckan.className = 'flatmap-layer-grid';
365
+
366
+ const innerHTML = [];
367
+ let checked = (this.__state === 'all') ? 'checked' : '';
368
+ innerHTML.push(`<label for="sckan-all-paths">ALL PATHS:</label><input id="sckan-all-paths" type="checkbox" ${checked}/>`);
369
+ for (const state of SCKAN_STATES) {
370
+ checked = (this.__state.toUpperCase() === state.id) ? 'checked' : '';
371
+ innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" ${checked}/>`);
372
+ }
373
+ this.__sckan.innerHTML = innerHTML.join('\n');
374
+
375
+ this.__sckanCount = SCKAN_STATES.length;
376
+ this.__checkedCount = (this.__state === 'all') ? this.__sckanCount
377
+ : (this.__state === 'none') ? 0
378
+ : 1;
379
+ this.__halfCount = Math.trunc(this.__sckanCount/2);
380
+
381
+ this.__button = document.createElement('button');
382
+ this.__button.id = 'map-sckan-button';
383
+ this.__button.className = 'control-button text-button';
384
+ this.__button.setAttribute('type', 'button');
385
+ this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
386
+ this.__button.setAttribute('control-visible', 'false');
387
+ this.__button.textContent = 'SCKAN';
388
+ this.__button.title = 'Show/hide valid SCKAN paths';
389
+ this.__container.appendChild(this.__button);
390
+
391
+ this.__container.addEventListener('click', this.onClick_.bind(this));
392
+ return this.__container;
393
+ }
394
+
395
+ onRemove()
396
+ //========
397
+ {
398
+ this.__container.parentNode.removeChild(this.__container);
399
+ this.__map = undefined;
400
+ }
401
+
402
+ onClick_(event)
403
+ //=============
404
+ {
405
+ if (event.target.id === 'map-sckan-button') {
406
+ if (this.__button.getAttribute('control-visible') === 'false') {
407
+ this.__container.appendChild(this.__sckan);
408
+ this.__button.setAttribute('control-visible', 'true');
409
+ const allLayersCheckbox = document.getElementById('sckan-all-paths');
410
+ allLayersCheckbox.indeterminate = this.__state.toLowerCase().includes('valid');
411
+ this.__sckan.focus();
412
+ } else {
413
+ this.__sckan = this.__container.removeChild(this.__sckan);
414
+ this.__button.setAttribute('control-visible', 'false');
415
+ }
416
+ } else if (event.target.tagName === 'INPUT') {
417
+ if (event.target.id === 'sckan-all-paths') {
418
+ if (event.target.indeterminate) {
419
+ event.target.checked = (this.__checkedCount >= this.__halfCount);
420
+ event.target.indeterminate = false;
421
+ }
422
+ if (event.target.checked) {
423
+ this.__state = 'all';
424
+ this.__checkedCount = this.__sckanCount;
425
+ } else {
426
+ this.__state = 'none';
427
+ this.__checkedCount = 0;
428
+ }
429
+ for (const state of SCKAN_STATES) {
430
+ const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
431
+ if (sckanCheckbox) {
432
+ sckanCheckbox.checked = event.target.checked;
433
+ }
434
+ }
435
+ this.__flatmap.showSckanPaths(this.__state);
436
+ } else if (event.target.id.startsWith('sckan-')) {
437
+ const sckanId = event.target.id.substring(6);
438
+ if (event.target.checked) {
439
+ this.__checkedCount += 1;
440
+ } else {
441
+ this.__checkedCount -= 1;
442
+ }
443
+ const allLayersCheckbox = document.getElementById('sckan-all-paths');
444
+ if (this.__checkedCount === 0) {
445
+ this.__state = 'none';
446
+ allLayersCheckbox.checked = false;
447
+ allLayersCheckbox.indeterminate = false;
448
+ } else if (this.__checkedCount === this.__sckanCount) {
449
+ this.__state = 'all';
450
+ allLayersCheckbox.checked = true;
451
+ allLayersCheckbox.indeterminate = false;
452
+ } else {
453
+ if (event.target.checked) {
454
+ this.__state = sckanId;
455
+ } else if (sckanId === 'VALID') {
456
+ this.__state = 'invalid';
457
+ } else {
458
+ this.__state = 'valid';
459
+ }
460
+ allLayersCheckbox.indeterminate = true;
461
+ }
462
+ this.__flatmap.showSckanPaths(this.__state);
463
+ }
464
+ }
465
+ event.stopPropagation();
466
+ }
467
+ }
468
+
469
+ //==============================================================================
470
+
319
471
  export class BackgroundControl
320
472
  {
321
473
  constructor(flatmap)
@@ -314,6 +314,21 @@ class FlatMap
314
314
  }
315
315
  }
316
316
 
317
+ /**
318
+ * Hide or show all paths valid in SCKAN.
319
+ *
320
+ * @param {string} validity Either ``VALID`` or ``INVALID``
321
+ * @param {boolean} [enable=true] If ``true`` then only show the paths
322
+ * of the type(s) otherwise only hide the paths
323
+ */
324
+ showSckanPaths(state='valid')
325
+ //===========================
326
+ {
327
+ if (this._userInteractions !== null) {
328
+ this._userInteractions.showSckanPaths(state);
329
+ }
330
+ }
331
+
317
332
  /**
318
333
  * Load images and patterns/textures referenced in style rules.
319
334
  *
@@ -428,12 +443,6 @@ class FlatMap
428
443
  return `${this.__uuid}-${this._mapNumber}`;
429
444
  }
430
445
 
431
- get activeLayerNames()
432
- //====================
433
- {
434
- return this._userInteractions.activeLayerNames;
435
- }
436
-
437
446
  get annotations()
438
447
  //===============
439
448
  {
@@ -739,6 +748,34 @@ class FlatMap
739
748
 
740
749
  //==========================================================================
741
750
 
751
+ /**
752
+ * Get a list of the flatmap's layers.
753
+ *
754
+ * @return {Array.Object.<{id: string, description: string, enabled: boolean}>} An array with layer details
755
+ */
756
+ getLayers()
757
+ //=========
758
+ {
759
+ if (this._userInteractions !== null) {
760
+ return this._userInteractions.getLayers();
761
+ }
762
+ }
763
+
764
+ /**
765
+ * @param {string} layerId The layer identifier to enable
766
+ * @param {boolean} enable Show or hide the layer. Defaults to ``true`` (show)
767
+ *
768
+ */
769
+ enableLayer(layerId, enable=true)
770
+ //===============================
771
+ {
772
+ if (this._userInteractions !== null) {
773
+ return this._userInteractions.enableLayer(layerId, enable);
774
+ }
775
+ }
776
+
777
+ //==========================================================================
778
+
742
779
  /**
743
780
  * Add a marker to the map.
744
781
  *
@@ -836,13 +873,21 @@ class FlatMap
836
873
  'label',
837
874
  'models',
838
875
  'nodeId',
839
- 'source'
876
+ 'source',
877
+ 'hyperlinks'
878
+ ];
879
+ const jsonProperties = [
880
+ 'hyperlinks'
840
881
  ];
841
882
  for (const property of exportedProperties) {
842
883
  if (property in properties) {
843
884
  const value = properties[property];
844
885
  if (value !== undefined) {
845
- data[property] = properties[property];
886
+ if (jsonProperties.indexOf(property) >= 0) {
887
+ data[property] = JSON.parse(properties[property])
888
+ } else {
889
+ data[property] = properties[property];
890
+ }
846
891
  }
847
892
  }
848
893
  }
@@ -1168,13 +1213,12 @@ export class MapManager
1168
1213
  * @arg options {Object} Configurable options for the map.
1169
1214
  * @arg options.background {string} Background colour of flatmap. Defaults to ``white``.
1170
1215
  * @arg options.debug {boolean} Enable debugging mode.
1171
- * @arg options.featureInfo {boolean} Show information about features as a tooltip. The tooltip is active
1172
- * on selected features and, for non-selected features, when the
1173
- * ``info`` control is enabled. More details are shown in debug mode.
1174
1216
  * @arg options.fullscreenControl {boolean} Add a ``Show full screen`` button to the map.
1175
1217
  * @arg options.layerOptions {Object} Options to control colour and outlines of features
1176
1218
  * @arg options.layerOptions.colour {boolean} Use colour fill (if available) for features. Defaults to ``true``.
1177
1219
  * @arg options.layerOptions.outline {boolean} Show the border of features. Defaults to ``true``.
1220
+ * @arg options.layerOptions.sckan {string} Show neuron paths known to SCKAN: values are ``valid`` (default),
1221
+ * ``invalid``, ``all`` or ``none``.
1178
1222
  * @arg options.minimap {boolean|Object} Display a MiniMap of the flatmap. Defaults to ``false``.
1179
1223
  * @arg options.minimap.position {string} The minimap's position: ``bottom-left`` (default), ``bottom-right``,
1180
1224
  * ``top-left`` or ``top-right``.
@@ -1185,9 +1229,9 @@ export class MapManager
1185
1229
  * @arg options.maxZoom {number} The maximum zoom level of the map.
1186
1230
  * @arg options.minZoom {number} The minimum zoom level of the map.
1187
1231
  * @arg options.navigationControl {boolean} Add navigation controls (zoom buttons) to the map.
1188
- * @arg options.pathControl {boolean} Add buttons to control pathways including via a color-coded legend.
1189
- * @arg options.searchable {boolean} Add a control to search for features on a map.
1190
1232
  * @arg options.showPosition {boolean} Show ``position`` of tooltip.
1233
+ * @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
1234
+ * another application so show a number of controls. Defaults to ``false``.
1191
1235
  * @example
1192
1236
  * const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
1193
1237
  *
@@ -1225,6 +1269,14 @@ export class MapManager
1225
1269
  mapOptions['bounds'] = mapIndex['bounds'];
1226
1270
  }
1227
1271
 
1272
+ // Note the kind of map
1273
+
1274
+ if ('style' in mapIndex) {
1275
+ mapOptions.style = mapIndex.style; // Currently ``flatmap`` or ``fcdiagram``
1276
+ } else {
1277
+ mapOptions.style = 'flatmap';
1278
+ }
1279
+
1228
1280
  // Mapmaker has changed the name of the field to indicate that indicates if
1229
1281
  // there are raster layers
1230
1282
  if (!('image-layers' in mapIndex) && ('image_layer' in mapIndex)) {
@@ -1300,11 +1352,6 @@ export class MapManager
1300
1352
  };
1301
1353
  }
1302
1354
  mapOptions.layerOptions.authoring = ('authoring' in mapIndex) ? mapIndex.authoring : false;
1303
- if ('style' in mapIndex) {
1304
- mapOptions.layerOptions.style = mapIndex.style;
1305
- } else {
1306
- mapOptions.layerOptions.style = 'flatmap';
1307
- }
1308
1355
 
1309
1356
  // Are features in separate vector tile source layers?
1310
1357
 
@@ -38,7 +38,7 @@ import {displayedProperties} from './info.js';
38
38
  import {InfoControl} from './info.js';
39
39
  import {LayerManager} from './layers.js';
40
40
  import {PATH_TYPES, PATHWAYS_LAYER, Pathways} from './pathways.js';
41
- import {BackgroundControl, LayerControl, PathControl} from './controls.js';
41
+ import {BackgroundControl, LayerControl, PathControl, SCKANControl} from './controls.js';
42
42
  import {SearchControl} from './search.js';
43
43
  import {VECTOR_TILES_SOURCE} from './styling.js';
44
44
 
@@ -130,47 +130,47 @@ export class UserInteractions
130
130
 
131
131
  flatmap.setInitialPosition();
132
132
 
133
- // Add a control to search annotations if option set
134
-
135
- if (flatmap.options.searchable) {
136
- this._map.addControl(new SearchControl(flatmap));
137
- }
138
-
139
- // Show information about features
140
-
141
- if (flatmap.options.featureInfo || flatmap.options.searchable) {
142
- this._infoControl = new InfoControl(flatmap);
143
- if (flatmap.options.featureInfo) {
144
- this._map.addControl(this._infoControl);
145
- }
146
- }
147
-
148
133
  // Add and manage our layers
149
134
 
150
135
  this._layerManager = new LayerManager(flatmap);
151
136
 
152
- // Control background colour (NB. this depends on having map layers created)
137
+ // Path visibility is either controlled externally or by a local control
153
138
 
154
- if (flatmap.options.backgroundControl) {
155
- this._map.addControl(new BackgroundControl(flatmap));
139
+ this._pathways = new Pathways(flatmap);
140
+
141
+ // The path types in this map
142
+ const mapPathTypes = this._pathways.pathTypes;
143
+
144
+ // Disable paths that are not initially shown
145
+ for (const path of mapPathTypes) {
146
+ if ('enabled' in path && !path.enabled) {
147
+ this.enablePath(path.type, false);
148
+ }
156
149
  }
157
150
 
158
- // Neural pathways which are either controlled externally
159
- // or by our local controls
151
+ // Add various controls when running standalone
160
152
 
161
- this._pathways = new Pathways(flatmap);
153
+ if (flatmap.options.standalone) {
154
+ // Add a control to search annotations if option set
155
+ this._map.addControl(new SearchControl(flatmap));
162
156
 
163
- // Add a control to manage our pathways
157
+ // Show information about features
158
+ this._infoControl = new InfoControl(flatmap);
159
+ this._map.addControl(this._infoControl);
164
160
 
165
- if (flatmap.options.pathControls) {
166
- // Restrict to path types that are on the map...
167
- this._map.addControl(new PathControl(flatmap, this._pathways.pathTypes));
168
- }
161
+ // Control background colour (NB. this depends on having map layers created)
162
+ this._map.addControl(new BackgroundControl(flatmap));
169
163
 
170
- // Add a control to manage our layers
164
+ // Add a control to manage our paths
165
+ this._map.addControl(new PathControl(flatmap, mapPathTypes));
171
166
 
172
- if (flatmap.options.layerControl) {
167
+ // Add a control to manage our layers
173
168
  this._map.addControl(new LayerControl(flatmap, this._layerManager));
169
+
170
+ // A SCKAN path control for FC maps
171
+ if (flatmap.options.style === 'fcdiagram') {
172
+ this._map.addControl(new SCKANControl(flatmap));
173
+ }
174
174
  }
175
175
 
176
176
  // Flag features that have annotations
@@ -221,7 +221,7 @@ export class UserInteractions
221
221
  return {
222
222
  center: this._map.getCenter().toArray(),
223
223
  zoom: this._map.getZoom(),
224
- layers: this.activeLayerNames
224
+ layers: this.layers
225
225
  };
226
226
  }
227
227
 
@@ -253,10 +253,16 @@ export class UserInteractions
253
253
  this._layerManager.setColour(options);
254
254
  }
255
255
 
256
- get activeLayerNames()
257
- //====================
256
+ getLayers()
257
+ //=========
258
258
  {
259
- return this._layerManager.activeLayerNames;
259
+ return this._layerManager.layers;
260
+ }
261
+
262
+ enableLayer(layerId, enable=true)
263
+ //===============================
264
+ {
265
+ this._layerManager.activate(layerId, enable);
260
266
  }
261
267
 
262
268
  mapFeature_(featureId)
@@ -1022,6 +1028,12 @@ export class UserInteractions
1022
1028
  return this._pathways.nodePathModels(nodeId);
1023
1029
  }
1024
1030
 
1031
+ showSckanPaths(state='valid')
1032
+ //===========================
1033
+ {
1034
+ this._layerManager.setFilter({sckan: state});
1035
+ }
1036
+
1025
1037
  //==============================================================================
1026
1038
 
1027
1039
  // Find where to place a label or popup on a feature
package/src/layers.js CHANGED
@@ -35,12 +35,13 @@ const RASTER_LAYERS_ID = 'background-image-layer';
35
35
 
36
36
  class MapStylingLayers
37
37
  {
38
- constructor(flatmap, layerId, options)
38
+ constructor(flatmap, layer, options)
39
39
  {
40
40
  this.__map = flatmap.map;
41
- this.__id = layerId;
42
- this.__layers = [];
41
+ this.__id = layer.id;
42
+ this.__description = layer.description;
43
43
  this.__active = true;
44
+ this.__layers = [];
44
45
  this.__layerOptions = options;
45
46
  this.__separateLayers = flatmap.options.separateLayers;
46
47
  }
@@ -51,6 +52,12 @@ class MapStylingLayers
51
52
  return this.__id;
52
53
  }
53
54
 
55
+ get description()
56
+ //===============
57
+ {
58
+ return this.__description;
59
+ }
60
+
54
61
  get active()
55
62
  //==========
56
63
  {
@@ -85,6 +92,16 @@ class MapStylingLayers
85
92
  return this.__separateLayers ? `${this.__id}_${sourceLayer}`
86
93
  : sourceLayer;
87
94
  }
95
+
96
+ setColour(options)
97
+ {
98
+
99
+ }
100
+
101
+ setFilter(options)
102
+ {
103
+
104
+ }
88
105
  }
89
106
 
90
107
  //==============================================================================
@@ -93,7 +110,7 @@ class MapFeatureLayers extends MapStylingLayers
93
110
  {
94
111
  constructor(flatmap, layer, options)
95
112
  {
96
- super(flatmap, layer.id, options);
113
+ super(flatmap, layer, options);
97
114
  const vectorTileSource = this.__map.getSource('vector-tiles');
98
115
  const haveVectorLayers = (typeof vectorTileSource !== 'undefined');
99
116
 
@@ -108,7 +125,7 @@ class MapFeatureLayers extends MapStylingLayers
108
125
  this.__addStyleLayer(style.FeatureLineLayer);
109
126
  this.__addStyleLayer(style.FeatureBorderLayer);
110
127
  }
111
- this.__addPathwayStyleLayers(this.__layerOptions);
128
+ this.__addPathwayStyleLayers();
112
129
  if (vectorFeatures) {
113
130
  this.__addStyleLayer(style.FeatureLargeSymbolLayer);
114
131
  if (!flatmap.options.tooltips) {
@@ -138,8 +155,12 @@ class MapFeatureLayers extends MapStylingLayers
138
155
  if (this.__map.getSource('vector-tiles')
139
156
  .vectorLayerIds
140
157
  .indexOf(pathwaysVectorSource) >= 0) {
158
+ this.__addStyleLayer(style.CentrelineEdgeLayer, PATHWAYS_LAYER);
159
+ this.__addStyleLayer(style.CentrelineTrackLayer, PATHWAYS_LAYER);
160
+
141
161
  this.__addStyleLayer(style.PathLineLayer, PATHWAYS_LAYER);
142
162
  this.__addStyleLayer(style.PathDashlineLayer, PATHWAYS_LAYER);
163
+
143
164
  this.__addStyleLayer(style.NervePolygonBorder, PATHWAYS_LAYER);
144
165
  this.__addStyleLayer(style.NervePolygonFill, PATHWAYS_LAYER);
145
166
  this.__addStyleLayer(style.FeatureNerveLayer, PATHWAYS_LAYER);
@@ -156,6 +177,17 @@ class MapFeatureLayers extends MapStylingLayers
156
177
  }
157
178
  }
158
179
  }
180
+
181
+ setFilter(options)
182
+ //================
183
+ {
184
+ for (const layer of this.__layers) {
185
+ const filter = layer.makeFilter(options);
186
+ if (filter !== null) {
187
+ this.__map.setFilter(layer.id, filter, {validate: true});
188
+ }
189
+ }
190
+ }
159
191
  }
160
192
 
161
193
  //==============================================================================
@@ -164,7 +196,11 @@ class MapRasterLayers extends MapStylingLayers
164
196
  {
165
197
  constructor(flatmap, options, bodyLayerId=null)
166
198
  {
167
- super(flatmap, RASTER_LAYERS_ID, options);
199
+ const rasterLayer = {
200
+ id: RASTER_LAYERS_ID,
201
+ description: RASTER_LAYERS_NAME
202
+ };
203
+ super(flatmap, rasterLayer, options);
168
204
  if (bodyLayerId !== null) {
169
205
  const layerId = `${bodyLayerId}_${FEATURES_LAYER}`;
170
206
  const source = flatmap.options.separateLayers ? layerId : FEATURES_LAYER;
@@ -209,11 +245,11 @@ export class LayerManager
209
245
  {
210
246
  this.__flatmap = flatmap;
211
247
  this.__map = flatmap.map;
212
- this.__layers = new Map;
213
248
  this.__mapLayers = new Map;
214
249
  this.__layerOptions = utils.setDefaults(flatmap.options.layerOptions, {
215
250
  colour: true,
216
- outline: true
251
+ outline: true,
252
+ sckan: 'valid'
217
253
  });;
218
254
  const backgroundLayer = new style.BackgroundLayer();
219
255
  if ('background' in flatmap.options) {
@@ -234,44 +270,29 @@ export class LayerManager
234
270
  for (const layer of flatmap.layers) {
235
271
  rasterLayers.addLayer(layer);
236
272
  }
237
- this.__layers.set(RASTER_LAYERS_ID, {
238
- id: RASTER_LAYERS_ID,
239
- description: RASTER_LAYERS_NAME
240
- });
241
273
  this.__mapLayers.set(RASTER_LAYERS_ID, rasterLayers);
242
274
  } else {
243
275
  this.__layerOptions.activeRasterLayer = false;
244
276
  }
245
277
  for (const layer of flatmap.layers) {
246
- this.__addFeatureLayer(layer);
278
+ this.__mapLayers.set(layer.id, new MapFeatureLayers(this.__flatmap,
279
+ layer,
280
+ this.__layerOptions));
247
281
  }
248
282
  }
249
283
 
250
- get activeLayerNames()
251
- //====================
252
- {
253
- const activeNames = [];
254
- for (const mapLayer of this.__mapLayers.values()) {
255
- if (mapLayer.active) {
256
- activeNames.push(mapLayer.id);
257
- }
258
- }
259
- return activeNames;
260
- }
261
-
262
- __addFeatureLayer(layer)
263
- //======================
264
- {
265
- this.__layers.set(layer.id, layer);
266
- this.__mapLayers.set(layer.id, new MapFeatureLayers(this.__flatmap,
267
- layer,
268
- this.__layerOptions));
269
- }
270
-
271
284
  get layers()
272
285
  //==========
273
286
  {
274
- return Array.from(this.__layers.values());
287
+ const layers = [];
288
+ for (const mapLayer of this.__mapLayers.values()) {
289
+ layers.push({
290
+ id: mapLayer.id,
291
+ description: mapLayer.description,
292
+ enabled: mapLayer.active
293
+ });
294
+ }
295
+ return layers;
275
296
  }
276
297
 
277
298
  activate(layerId, enable=true)
@@ -291,14 +312,23 @@ export class LayerManager
291
312
  }
292
313
  }
293
314
 
294
- setColour(options=null)
295
- //=====================
315
+ setColour(options={})
316
+ //===================
296
317
  {
297
318
  this.__layerOptions = utils.setDefaults(options, this.__layerOptions);
298
319
  for (const mapLayer of this.__mapLayers.values()) {
299
320
  mapLayer.setColour(this.__layerOptions);
300
321
  }
301
322
  }
323
+
324
+ setFilter(options={})
325
+ //===================
326
+ {
327
+ this.__layerOptions = utils.setDefaults(options, this.__layerOptions);
328
+ for (const mapLayer of this.__mapLayers.values()) {
329
+ mapLayer.setFilter(this.__layerOptions);
330
+ }
331
+ }
302
332
  }
303
333
 
304
334
  //==============================================================================
package/src/main.js CHANGED
@@ -57,14 +57,10 @@ export async function standaloneViewer(map_endpoint=null, options={})
57
57
  const mapOptions = Object.assign({
58
58
  tooltips: true,
59
59
  background: defaultBackground,
60
- backgroundControl: true,
61
60
  debug: false,
62
61
  minimap: false,
63
- searchable: true,
64
- featureInfo: true,
65
62
  showPosition: false,
66
- pathControls: true,
67
- layerControl: true
63
+ standalone: true
68
64
  }, options);
69
65
 
70
66
  function loadMap(id, taxon, sex)
package/src/pathways.js CHANGED
@@ -28,14 +28,17 @@ export const PATHWAYS_LAYER = 'pathways';
28
28
 
29
29
  export const PATH_TYPES = [
30
30
  { type: "cns", label: "CNS", colour: "#9B1FC1"},
31
- { type: "lcn", label: "Local circuit neuron", colour: "#F19E38"},
31
+ { type: "intracardiac", label: "Local circuit neuron", colour: "#F19E38"},
32
32
  { type: "para-pre", label: "Parasympathetic pre-ganglionic", colour: "#3F8F4A"},
33
33
  { type: "para-post", label: "Parasympathetic post-ganglionic", colour: "#3F8F4A"},
34
34
  { type: "sensory", label: "Sensory (afferent) neuron", colour: "#2A62F6"},
35
35
  { type: "somatic", label: "Somatic lower motor", colour: "#98561D"},
36
36
  { type: "symp-pre", label: "Sympathetic pre-ganglionic", colour: "#EA3423"},
37
37
  { type: "symp-post", label: "Sympathetic post-ganglionic", colour: "#EA3423"},
38
- { type: "other", label: "Other neuron type", colour: "#888"}
38
+ { type: "other", label: "Other neuron type", colour: "#888"},
39
+ { type: "arterial", label: "Arterial blood vessel", colour: "#F00"},
40
+ { type: "venous", label: "Venous blood vessel", colour: "#2F6EBA"},
41
+ { type: "centreline", label: "Nerve centrelines", colour: "#2F6EBA", enabled: false}
39
42
  ];
40
43
 
41
44
  export const PATH_STYLE_RULES =
package/src/styling.js CHANGED
@@ -44,6 +44,11 @@ class VectorStyleLayer
44
44
  return this.__id;
45
45
  }
46
46
 
47
+ makeFilter(options)
48
+ {
49
+ return null;
50
+ }
51
+
47
52
  paintStyle(options, changes=false)
48
53
  {
49
54
  return {};
@@ -134,7 +139,8 @@ export class FeatureFillLayer extends VectorStyleLayer
134
139
  'fill-opacity': [
135
140
  'case',
136
141
  ['boolean', ['feature-state', 'selected'], false], 0.7,
137
- ['has', 'colour'], activeRasterLayer ? 0.008 : 1.0,
142
+ ['has', 'opacity'], ['get', 'opacity'],
143
+ ['has', 'colour'], 1.0,
138
144
  ['boolean', ['feature-state', 'active'], false], 0.7,
139
145
  ['has', 'node'], 0.3,
140
146
  ['any',
@@ -183,7 +189,7 @@ export class FeatureBorderLayer extends VectorStyleLayer
183
189
  const activeRasterLayer = 'activeRasterLayer' in options && options.activeRasterLayer;
184
190
  const lineColour = [ 'case' ];
185
191
  lineColour.push(['boolean', ['feature-state', 'selected'], false]);
186
- lineColour.push('red');
192
+ lineColour.push('black');
187
193
  if (coloured && outlined) {
188
194
  lineColour.push(['boolean', ['feature-state', 'active'], false]);
189
195
  lineColour.push('blue');
@@ -249,26 +255,32 @@ export class FeatureBorderLayer extends VectorStyleLayer
249
255
 
250
256
  export class FeatureLineLayer extends VectorStyleLayer
251
257
  {
252
- constructor(id, sourceLayer, dashed=false)
258
+ constructor(id, sourceLayer, options={})
253
259
  {
260
+ const dashed = ('dashed' in options && options.dashed);
254
261
  const filterType = dashed ? 'line-dash' : 'line';
255
262
  super(id, `feature-${filterType}`, sourceLayer);
256
- this.__filter = dashed ?
257
- [
258
- 'any',
259
- ['==', 'type', `line-dash`]
260
- ]
261
- :
263
+ this.__dashed = dashed;
264
+ }
265
+
266
+ makeFilter(options={})
267
+ {
268
+ return this.__dashed ? [
269
+ 'all',
270
+ ['==', '$type', 'LineString'],
271
+ ['==', 'type', `line-dash`]
272
+ ] : [
273
+ 'all',
274
+ ['==', '$type', 'LineString'],
262
275
  [
263
276
  'any',
264
- ['has', 'centreline'],
265
277
  ['==', 'type', 'bezier'],
266
278
  ['==', 'type', `line`]
267
- ];
268
- this.__dashed = dashed;
279
+ ]
280
+ ];
269
281
  }
270
282
 
271
- paintStyle(options)
283
+ paintStyle(options, changes=false)
272
284
  {
273
285
  const coloured = !('colour' in options) || options.colour;
274
286
  const paintStyle = {
@@ -278,7 +290,6 @@ export class FeatureLineLayer extends VectorStyleLayer
278
290
  ['has', 'colour'], ['get', 'colour'],
279
291
  ['boolean', ['feature-state', 'active'], false], coloured ? '#888' : '#CCC',
280
292
  ['==', ['get', 'type'], 'network'], '#AFA202',
281
- ['has', 'centreline'], '#888',
282
293
  options.authoring ? '#C44' : '#444'
283
294
  ],
284
295
  'line-opacity': [
@@ -292,7 +303,6 @@ export class FeatureLineLayer extends VectorStyleLayer
292
303
  'let',
293
304
  'width', [
294
305
  'case',
295
- ['has', 'centreline'], 1.2,
296
306
  ['==', ['get', 'type'], 'network'], 1.2,
297
307
  ['boolean', ['feature-state', 'selected'], false], 1.2,
298
308
  ['boolean', ['feature-state', 'active'], false], 1.2,
@@ -312,7 +322,7 @@ export class FeatureLineLayer extends VectorStyleLayer
312
322
  if (this.__dashed) {
313
323
  paintStyle['line-dasharray'] = [3, 2];
314
324
  }
315
- return paintStyle;
325
+ return super.changedPaintStyle(paintStyle, changes);
316
326
  }
317
327
 
318
328
  style(options)
@@ -320,12 +330,7 @@ export class FeatureLineLayer extends VectorStyleLayer
320
330
  return {
321
331
  ...super.style(),
322
332
  'type': 'line',
323
- 'filter': [
324
- 'all',
325
- ['==', '$type', 'LineString'],
326
- this.__filter
327
- // not for paths...
328
- ],
333
+ 'filter': this.makeFilter(options),
329
334
  'paint': this.paintStyle(options)
330
335
  };
331
336
  }
@@ -337,7 +342,7 @@ export class FeatureDashLineLayer extends FeatureLineLayer
337
342
  {
338
343
  constructor(id, sourceLayer)
339
344
  {
340
- super(id, sourceLayer, true);
345
+ super(id, sourceLayer, {dashed: true});
341
346
  }
342
347
  }
343
348
 
@@ -345,25 +350,63 @@ export class FeatureDashLineLayer extends FeatureLineLayer
345
350
 
346
351
  export class PathLineLayer extends VectorStyleLayer
347
352
  {
348
- constructor(id, sourceLayer, dashed=false)
353
+ constructor(id, sourceLayer, options={})
349
354
  {
355
+ const dashed = ('dashed' in options && options.dashed);
350
356
  const filterType = dashed ? 'line-dash' : 'line';
351
357
  super(id, `path-${filterType}`, sourceLayer);
352
- this.__filter = dashed ?
353
- [
358
+ this.__dashed = dashed;
359
+ }
360
+
361
+ makeFilter(options={})
362
+ {
363
+ const sckanState = !'sckan' in options ? 'all'
364
+ : options.sckan.toLowerCase();
365
+ const sckan_filter =
366
+ sckanState == 'none' ? [
367
+ ['!has', 'sckan']
368
+ ] :
369
+ sckanState == 'valid' ? [[
354
370
  'any',
355
- ['==', 'type', `line-dash`]
356
- ]
357
- :
371
+ ['!has', 'sckan'],
372
+ [
373
+ 'all',
374
+ ['has', 'sckan'],
375
+ ['==', 'sckan', true]
376
+ ]
377
+ ]] :
378
+ sckanState == 'invalid' ? [[
379
+ 'any',
380
+ ['!has', 'sckan'],
381
+ [
382
+ 'all',
383
+ ['has', 'sckan'],
384
+ ['!=', 'sckan', true]
385
+ ]
386
+ ]] :
387
+ [ ];
388
+
389
+ return this.__dashed ? [
390
+ 'all',
391
+ ['==', '$type', 'LineString'],
392
+ ['==', 'type', `line-dash`],
393
+ ...sckan_filter
394
+ ] : [
395
+ 'all',
396
+ ['==', '$type', 'LineString'],
358
397
  [
359
398
  'any',
360
399
  ['==', 'type', 'bezier'],
361
- ['==', 'type', `line`]
362
- ];
363
- this.__dashed = dashed;
400
+ [
401
+ 'all',
402
+ ['==', 'type', `line`],
403
+ ...sckan_filter
404
+ ]
405
+ ]
406
+ ];
364
407
  }
365
408
 
366
- paintStyle(options, changes=false)
409
+ paintStyle(options={}, changes=false)
367
410
  {
368
411
  const dimmed = 'dimmed' in options && options.dimmed;
369
412
  const paintStyle = {
@@ -372,7 +415,7 @@ export class PathLineLayer extends VectorStyleLayer
372
415
  ['boolean', ['feature-state', 'selected'], false], '#0F0',
373
416
  ['boolean', ['feature-state', 'hidden'], false], '#CCC',
374
417
  ['==', ['get', 'type'], 'bezier'], 'red',
375
- ['==', ['get', 'kind'], 'error'], '#FFFE0E',
418
+ ['has', 'error'], '#FFFE0E',
376
419
  ['==', ['get', 'kind'], 'unknown'], '#888',
377
420
  ...PATH_STYLE_RULES,
378
421
  '#888'
@@ -388,17 +431,18 @@ export class PathLineLayer extends VectorStyleLayer
388
431
  ],
389
432
  'line-width': [
390
433
  'let',
391
- 'width', [
434
+ 'width', ["*", [
392
435
  'case',
393
436
  ['==', ['get', 'type'], 'bezier'], 0.1,
394
- ['==', ['get', 'kind'], 'error'], 1,
437
+ ['has', 'error'], 1,
395
438
  ['==', ['get', 'kind'], 'unknown'], 1,
396
439
  ['boolean', ['get', 'invisible'], false], 0.1,
397
- ['boolean', ['feature-state', 'selected'], false], 1.2,
398
- ['boolean', ['feature-state', 'active'], false], 1.2,
399
- 0.8
400
- ], [
401
- 'interpolate',
440
+ ['boolean', ['feature-state', 'selected'], false], 0.6,
441
+ ['boolean', ['feature-state', 'active'], false], 0.9,
442
+ 0.6
443
+ ],
444
+ ['case', ['has', 'stroke-width'], ['get', 'stroke-width'], 1.0]],
445
+ ['interpolate',
402
446
  ['exponential', 2],
403
447
  ['zoom'],
404
448
  2, ["*", ['var', 'width'], ["^", 2, -0.5]],
@@ -408,21 +452,17 @@ export class PathLineLayer extends VectorStyleLayer
408
452
  ]
409
453
  };
410
454
  if (this.__dashed) {
411
- paintStyle['line-dasharray'] = [3, 2];
455
+ paintStyle['line-dasharray'] = [1, 1];
412
456
  }
413
457
  return super.changedPaintStyle(paintStyle, changes);
414
458
  }
415
459
 
416
- style(options)
460
+ style(options={})
417
461
  {
418
462
  return {
419
463
  ...super.style(),
420
464
  'type': 'line',
421
- 'filter': [
422
- 'all',
423
- ['==', '$type', 'LineString'],
424
- this.__filter
425
- ],
465
+ 'filter': this.makeFilter(options),
426
466
  'layout': {
427
467
  'line-cap': 'butt'
428
468
  },
@@ -437,8 +477,93 @@ export class PathDashlineLayer extends PathLineLayer
437
477
  {
438
478
  constructor(id, sourceLayer)
439
479
  {
440
- super(id, sourceLayer, true);
480
+ super(id, sourceLayer, {dashed: true});
481
+ }
482
+ }
483
+
484
+ //==============================================================================
485
+
486
+ class CentrelineLayer extends VectorStyleLayer
487
+ {
488
+ constructor(id, type, sourceLayer)
489
+ {
490
+ super(id, `centreline-${type}`, sourceLayer);
491
+ this.__type = type;
441
492
  }
493
+
494
+ paintStyle(options, changes=false)
495
+ {
496
+ const coloured = !('colour' in options) || options.colour;
497
+ const paintStyle = {
498
+ 'line-color': (this.__type == 'edge') ? '#000' : [
499
+ 'case',
500
+ ['boolean', ['feature-state', 'selected'], false], '#0F0',
501
+ ['boolean', ['feature-state', 'active'], false], '#444',
502
+ '#CCC'
503
+ ],
504
+ 'line-opacity': [
505
+ 'case',
506
+ ['boolean', ['feature-state', 'hidden'], false], 0.01,
507
+ ['boolean', ['feature-state', 'selected'], false], 1.0,
508
+ ['boolean', ['feature-state', 'active'], false], 1.0,
509
+ 0.8
510
+ ],
511
+ 'line-width': [
512
+ 'let',
513
+ 'width',
514
+ (this.__type == 'edge') ? 1.6 : 1.2,
515
+ [
516
+ 'interpolate',
517
+ ['exponential', 2],
518
+ ['zoom'],
519
+ 2, ["*", ['var', 'width'], ["^", 2, -0.5]],
520
+ 7, ["*", ['var', 'width'], ["^", 2, 2.5]],
521
+ 9, ["*", ['var', 'width'], ["^", 2, 4.0]]
522
+ ]
523
+ ]
524
+ // Need to vary width based on zoom??
525
+ // Or opacity??
526
+ };
527
+ return super.changedPaintStyle(paintStyle, changes);
528
+ }
529
+
530
+ style(options)
531
+ {
532
+ return {
533
+ ...super.style(),
534
+ 'type': 'line',
535
+ 'filter': [
536
+ 'all',
537
+ ['==', '$type', 'LineString'],
538
+ ['==', 'kind', 'centreline']
539
+ ],
540
+ 'paint': this.paintStyle(options),
541
+ 'layout': {
542
+ 'line-cap': 'square',
543
+ 'line-join': 'bevel'
544
+ }
545
+ };
546
+ }
547
+ }
548
+
549
+
550
+ export class CentrelineEdgeLayer extends CentrelineLayer
551
+ {
552
+ constructor(id, sourceLayer)
553
+ {
554
+ super(id, 'edge', sourceLayer);
555
+ }
556
+
557
+ }
558
+
559
+ export class CentrelineTrackLayer extends CentrelineLayer
560
+ {
561
+ constructor(id, sourceLayer)
562
+ {
563
+ super(id, 'track', sourceLayer);
564
+ }
565
+
566
+
442
567
  }
443
568
 
444
569
  //==============================================================================
@@ -458,6 +583,7 @@ export class FeatureNerveLayer extends VectorStyleLayer
458
583
  'filter': [
459
584
  'all',
460
585
  ['==', '$type', 'LineString'],
586
+ ['!=', 'kind', 'centreline'],
461
587
  ['==', 'type', 'nerve']
462
588
  ],
463
589
  'paint': {
@@ -205,7 +205,7 @@ label[for=path-all-paths] {
205
205
  .nerve-cns {
206
206
  background: #9B1FC1;
207
207
  }
208
- .nerve-lcn {
208
+ .nerve-intracardiac {
209
209
  background: #F19E38;
210
210
  }
211
211
  .nerve-other {