@abi-software/flatmap-viewer 2.2.10-devel.2 → 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-devel.2",
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
@@ -22,7 +22,6 @@ limitations under the License.
22
22
 
23
23
  //==============================================================================
24
24
 
25
- import * as pathways from './pathways.js';
26
25
 
27
26
  //==============================================================================
28
27
 
@@ -91,10 +90,11 @@ export class NavigationControl
91
90
 
92
91
  export class PathControl
93
92
  {
94
- constructor(flatmap)
93
+ constructor(flatmap, pathTypes)
95
94
  {
96
95
  this._flatmap = flatmap;
97
96
  this._map = undefined;
97
+ this.__pathTypes = pathTypes;
98
98
  }
99
99
 
100
100
  getDefaultPosition()
@@ -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
- for (const path of pathways.PATH_TYPES) {
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/>`);
120
+ this.__checkedCount = 0;
121
+ for (const path of this.__pathTypes) {
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 = pathways.PATH_TYPES.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);
@@ -164,11 +171,11 @@ export class PathControl
164
171
  event.target.indeterminate = false;
165
172
  }
166
173
  if (event.target.checked) {
167
- this.__checkedCount = pathways.PATH_TYPES.length;
174
+ this.__checkedCount = this.__pathTypes.length;
168
175
  } else {
169
176
  this.__checkedCount = 0;
170
177
  }
171
- for (const path of pathways.PATH_TYPES) {
178
+ for (const path of this.__pathTypes) {
172
179
  const pathCheckbox = document.getElementById(`path-${path.type}`);
173
180
  if (pathCheckbox) {
174
181
  pathCheckbox.checked = event.target.checked;
@@ -187,7 +194,7 @@ export class PathControl
187
194
  if (this.__checkedCount === 0) {
188
195
  allPathsCheckbox.checked = false;
189
196
  allPathsCheckbox.indeterminate = false;
190
- } else if (this.__checkedCount === pathways.PATH_TYPES.length) {
197
+ } else if (this.__checkedCount === this.__pathTypes.length) {
191
198
  allPathsCheckbox.checked = true;
192
199
  allPathsCheckbox.indeterminate = false;
193
200
  } else {
@@ -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
 
@@ -37,8 +37,8 @@ import {ContextMenu} from './contextmenu.js';
37
37
  import {displayedProperties} from './info.js';
38
38
  import {InfoControl} from './info.js';
39
39
  import {LayerManager} from './layers.js';
40
- import {PATHWAYS_LAYER, Pathways} from './pathways.js';
41
- import {BackgroundControl, LayerControl, PathControl} from './controls.js';
40
+ import {PATH_TYPES, PATHWAYS_LAYER, Pathways} from './pathways.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,46 +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
- this._map.addControl(new PathControl(flatmap));
167
- }
161
+ // Control background colour (NB. this depends on having map layers created)
162
+ this._map.addControl(new BackgroundControl(flatmap));
168
163
 
169
- // Add a control to manage our layers
164
+ // Add a control to manage our paths
165
+ this._map.addControl(new PathControl(flatmap, mapPathTypes));
170
166
 
171
- if (flatmap.options.layerControl) {
167
+ // Add a control to manage our layers
172
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
+ }
173
174
  }
174
175
 
175
176
  // Flag features that have annotations
@@ -220,7 +221,7 @@ export class UserInteractions
220
221
  return {
221
222
  center: this._map.getCenter().toArray(),
222
223
  zoom: this._map.getZoom(),
223
- layers: this.activeLayerNames
224
+ layers: this.layers
224
225
  };
225
226
  }
226
227
 
@@ -252,10 +253,16 @@ export class UserInteractions
252
253
  this._layerManager.setColour(options);
253
254
  }
254
255
 
255
- get activeLayerNames()
256
- //====================
256
+ getLayers()
257
+ //=========
257
258
  {
258
- return this._layerManager.activeLayerNames;
259
+ return this._layerManager.layers;
260
+ }
261
+
262
+ enableLayer(layerId, enable=true)
263
+ //===============================
264
+ {
265
+ this._layerManager.activate(layerId, enable);
259
266
  }
260
267
 
261
268
  mapFeature_(featureId)
@@ -672,9 +679,9 @@ export class UserInteractions
672
679
  : '';
673
680
  if ('hyperlink' in properties) {
674
681
  if (label === '') {
675
- return `<div class='flatmap-feature-label'><a href='{properties.hyperlink}'>${properties.hyperlink}</a></div>`;
682
+ return `<div class='flatmap-feature-label'><a href='${properties.hyperlink}'>${properties.hyperlink}</a></div>`;
676
683
  } else {
677
- return `<div class='flatmap-feature-label'><a href='{properties.hyperlink}'>${properties.hyperlink}</a><br/>${label}</div>`;
684
+ return `<div class='flatmap-feature-label'><a href='${properties.hyperlink}'>${label}</a></div>`;
678
685
  }
679
686
  } else {
680
687
  return `<div class='flatmap-feature-label'>${label}</div>`;
@@ -1021,6 +1028,12 @@ export class UserInteractions
1021
1028
  return this._pathways.nodePathModels(nodeId);
1022
1029
  }
1023
1030
 
1031
+ showSckanPaths(state='valid')
1032
+ //===========================
1033
+ {
1034
+ this._layerManager.setFilter({sckan: state});
1035
+ }
1036
+
1024
1037
  //==============================================================================
1025
1038
 
1026
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,16 +28,22 @@ 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
 
44
+ export const PATH_STYLE_RULES =
45
+ PATH_TYPES.flatMap(pathType => [['==', ['get', 'kind'], pathType.type], pathType.colour]);
46
+
41
47
  //==============================================================================
42
48
 
43
49
  function reverseMap(mapping)
@@ -138,6 +144,19 @@ export class Pathways
138
144
  }
139
145
  }
140
146
 
147
+ get pathTypes()
148
+ //=============
149
+ {
150
+ const pathTypes = [];
151
+ for (const pathType of PATH_TYPES) {
152
+ if (pathType.type in this.__typePaths
153
+ && this.__typePaths[pathType.type].length > 0) {
154
+ pathTypes.push(pathType);
155
+ }
156
+ }
157
+ return pathTypes;
158
+ }
159
+
141
160
  addPathsToFeatureSet_(paths, featureSet)
142
161
  //======================================
143
162
  {
package/src/styling.js CHANGED
@@ -26,6 +26,10 @@ export const VECTOR_TILES_SOURCE = 'vector-tiles';
26
26
 
27
27
  //==============================================================================
28
28
 
29
+ import {PATH_STYLE_RULES} from './pathways.js';
30
+
31
+ //==============================================================================
32
+
29
33
  class VectorStyleLayer
30
34
  {
31
35
  constructor(id, suffix, sourceLayer)
@@ -40,6 +44,11 @@ class VectorStyleLayer
40
44
  return this.__id;
41
45
  }
42
46
 
47
+ makeFilter(options)
48
+ {
49
+ return null;
50
+ }
51
+
43
52
  paintStyle(options, changes=false)
44
53
  {
45
54
  return {};
@@ -130,7 +139,8 @@ export class FeatureFillLayer extends VectorStyleLayer
130
139
  'fill-opacity': [
131
140
  'case',
132
141
  ['boolean', ['feature-state', 'selected'], false], 0.7,
133
- ['has', 'colour'], activeRasterLayer ? 0.008 : 1.0,
142
+ ['has', 'opacity'], ['get', 'opacity'],
143
+ ['has', 'colour'], 1.0,
134
144
  ['boolean', ['feature-state', 'active'], false], 0.7,
135
145
  ['has', 'node'], 0.3,
136
146
  ['any',
@@ -179,7 +189,7 @@ export class FeatureBorderLayer extends VectorStyleLayer
179
189
  const activeRasterLayer = 'activeRasterLayer' in options && options.activeRasterLayer;
180
190
  const lineColour = [ 'case' ];
181
191
  lineColour.push(['boolean', ['feature-state', 'selected'], false]);
182
- lineColour.push('red');
192
+ lineColour.push('black');
183
193
  if (coloured && outlined) {
184
194
  lineColour.push(['boolean', ['feature-state', 'active'], false]);
185
195
  lineColour.push('blue');
@@ -245,26 +255,32 @@ export class FeatureBorderLayer extends VectorStyleLayer
245
255
 
246
256
  export class FeatureLineLayer extends VectorStyleLayer
247
257
  {
248
- constructor(id, sourceLayer, dashed=false)
258
+ constructor(id, sourceLayer, options={})
249
259
  {
260
+ const dashed = ('dashed' in options && options.dashed);
250
261
  const filterType = dashed ? 'line-dash' : 'line';
251
262
  super(id, `feature-${filterType}`, sourceLayer);
252
- this.__filter = dashed ?
253
- [
254
- 'any',
255
- ['==', 'type', `line-dash`]
256
- ]
257
- :
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'],
258
275
  [
259
276
  'any',
260
- ['has', 'centreline'],
261
277
  ['==', 'type', 'bezier'],
262
278
  ['==', 'type', `line`]
263
- ];
264
- this.__dashed = dashed;
279
+ ]
280
+ ];
265
281
  }
266
282
 
267
- paintStyle(options)
283
+ paintStyle(options, changes=false)
268
284
  {
269
285
  const coloured = !('colour' in options) || options.colour;
270
286
  const paintStyle = {
@@ -274,7 +290,6 @@ export class FeatureLineLayer extends VectorStyleLayer
274
290
  ['has', 'colour'], ['get', 'colour'],
275
291
  ['boolean', ['feature-state', 'active'], false], coloured ? '#888' : '#CCC',
276
292
  ['==', ['get', 'type'], 'network'], '#AFA202',
277
- ['has', 'centreline'], '#888',
278
293
  options.authoring ? '#C44' : '#444'
279
294
  ],
280
295
  'line-opacity': [
@@ -288,7 +303,6 @@ export class FeatureLineLayer extends VectorStyleLayer
288
303
  'let',
289
304
  'width', [
290
305
  'case',
291
- ['has', 'centreline'], 1.2,
292
306
  ['==', ['get', 'type'], 'network'], 1.2,
293
307
  ['boolean', ['feature-state', 'selected'], false], 1.2,
294
308
  ['boolean', ['feature-state', 'active'], false], 1.2,
@@ -308,7 +322,7 @@ export class FeatureLineLayer extends VectorStyleLayer
308
322
  if (this.__dashed) {
309
323
  paintStyle['line-dasharray'] = [3, 2];
310
324
  }
311
- return paintStyle;
325
+ return super.changedPaintStyle(paintStyle, changes);
312
326
  }
313
327
 
314
328
  style(options)
@@ -316,12 +330,7 @@ export class FeatureLineLayer extends VectorStyleLayer
316
330
  return {
317
331
  ...super.style(),
318
332
  'type': 'line',
319
- 'filter': [
320
- 'all',
321
- ['==', '$type', 'LineString'],
322
- this.__filter
323
- // not for paths...
324
- ],
333
+ 'filter': this.makeFilter(options),
325
334
  'paint': this.paintStyle(options)
326
335
  };
327
336
  }
@@ -333,7 +342,7 @@ export class FeatureDashLineLayer extends FeatureLineLayer
333
342
  {
334
343
  constructor(id, sourceLayer)
335
344
  {
336
- super(id, sourceLayer, true);
345
+ super(id, sourceLayer, {dashed: true});
337
346
  }
338
347
  }
339
348
 
@@ -341,25 +350,63 @@ export class FeatureDashLineLayer extends FeatureLineLayer
341
350
 
342
351
  export class PathLineLayer extends VectorStyleLayer
343
352
  {
344
- constructor(id, sourceLayer, dashed=false)
353
+ constructor(id, sourceLayer, options={})
345
354
  {
355
+ const dashed = ('dashed' in options && options.dashed);
346
356
  const filterType = dashed ? 'line-dash' : 'line';
347
357
  super(id, `path-${filterType}`, sourceLayer);
348
- this.__filter = dashed ?
349
- [
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' ? [[
350
370
  'any',
351
- ['==', 'type', `line-dash`]
352
- ]
353
- :
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'],
354
397
  [
355
398
  'any',
356
399
  ['==', 'type', 'bezier'],
357
- ['==', 'type', `line`]
358
- ];
359
- this.__dashed = dashed;
400
+ [
401
+ 'all',
402
+ ['==', 'type', `line`],
403
+ ...sckan_filter
404
+ ]
405
+ ]
406
+ ];
360
407
  }
361
408
 
362
- paintStyle(options, changes=false)
409
+ paintStyle(options={}, changes=false)
363
410
  {
364
411
  const dimmed = 'dimmed' in options && options.dimmed;
365
412
  const paintStyle = {
@@ -368,40 +415,34 @@ export class PathLineLayer extends VectorStyleLayer
368
415
  ['boolean', ['feature-state', 'selected'], false], '#0F0',
369
416
  ['boolean', ['feature-state', 'hidden'], false], '#CCC',
370
417
  ['==', ['get', 'type'], 'bezier'], 'red',
371
- ['==', ['get', 'kind'], 'error'], '#FFFE0E',
418
+ ['has', 'error'], '#FFFE0E',
372
419
  ['==', ['get', 'kind'], 'unknown'], '#888',
373
- ['==', ['get', 'kind'], 'cns'], '#9B1FC1',
374
- ['==', ['get', 'kind'], 'lcn'], '#F19E38',
375
- ['==', ['get', 'kind'], 'para-post'], '#3F8F4A',
376
- ['==', ['get', 'kind'], 'para-pre'], '#3F8F4A',
377
- ['==', ['get', 'kind'], 'somatic'], '#98561D',
378
- ['==', ['get', 'kind'], 'sensory'], '#2A62F6',
379
- ['==', ['get', 'kind'], 'symp-post'], '#EA3423',
380
- ['==', ['get', 'kind'], 'symp-pre'], '#EA3423',
420
+ ...PATH_STYLE_RULES,
381
421
  '#888'
382
422
  ],
383
423
  'line-opacity': [
384
424
  'case',
385
- ['boolean', ['feature-state', 'hidden'], false], 0.1,
425
+ ['boolean', ['feature-state', 'hidden'], false], 0.05,
386
426
  ['==', ['get', 'type'], 'bezier'], 1.0,
387
427
  ['boolean', ['get', 'invisible'], false], 0.001,
388
428
  ['boolean', ['feature-state', 'selected'], false], 1.0,
389
- ['boolean', ['feature-state', 'active'], false], 0.8,
390
- dimmed ? 0.1 : 0.5
429
+ ['boolean', ['feature-state', 'active'], false], 1.0,
430
+ dimmed ? 0.1 : 0.8
391
431
  ],
392
432
  'line-width': [
393
433
  'let',
394
- 'width', [
434
+ 'width', ["*", [
395
435
  'case',
396
436
  ['==', ['get', 'type'], 'bezier'], 0.1,
397
- ['==', ['get', 'kind'], 'error'], 1,
437
+ ['has', 'error'], 1,
398
438
  ['==', ['get', 'kind'], 'unknown'], 1,
399
439
  ['boolean', ['get', 'invisible'], false], 0.1,
400
- ['boolean', ['feature-state', 'selected'], false], 1.2,
440
+ ['boolean', ['feature-state', 'selected'], false], 0.6,
401
441
  ['boolean', ['feature-state', 'active'], false], 0.9,
402
- 0.8
403
- ], [
404
- 'interpolate',
442
+ 0.6
443
+ ],
444
+ ['case', ['has', 'stroke-width'], ['get', 'stroke-width'], 1.0]],
445
+ ['interpolate',
405
446
  ['exponential', 2],
406
447
  ['zoom'],
407
448
  2, ["*", ['var', 'width'], ["^", 2, -0.5]],
@@ -411,21 +452,17 @@ export class PathLineLayer extends VectorStyleLayer
411
452
  ]
412
453
  };
413
454
  if (this.__dashed) {
414
- paintStyle['line-dasharray'] = [3, 2];
455
+ paintStyle['line-dasharray'] = [1, 1];
415
456
  }
416
457
  return super.changedPaintStyle(paintStyle, changes);
417
458
  }
418
459
 
419
- style(options)
460
+ style(options={})
420
461
  {
421
462
  return {
422
463
  ...super.style(),
423
464
  'type': 'line',
424
- 'filter': [
425
- 'all',
426
- ['==', '$type', 'LineString'],
427
- this.__filter
428
- ],
465
+ 'filter': this.makeFilter(options),
429
466
  'layout': {
430
467
  'line-cap': 'butt'
431
468
  },
@@ -440,8 +477,93 @@ export class PathDashlineLayer extends PathLineLayer
440
477
  {
441
478
  constructor(id, sourceLayer)
442
479
  {
443
- 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;
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);
444
564
  }
565
+
566
+
445
567
  }
446
568
 
447
569
  //==============================================================================
@@ -461,6 +583,7 @@ export class FeatureNerveLayer extends VectorStyleLayer
461
583
  'filter': [
462
584
  'all',
463
585
  ['==', '$type', 'LineString'],
586
+ ['!=', 'kind', 'centreline'],
464
587
  ['==', 'type', 'nerve']
465
588
  ],
466
589
  'paint': {
@@ -570,14 +693,7 @@ export class NervePolygonFill extends VectorStyleLayer
570
693
  'case',
571
694
  ['==', ['get', 'kind'], 'bezier-end'], 'red',
572
695
  ['==', ['get', 'kind'], 'bezier-control'], 'green',
573
- ['==', ['get', 'kind'], 'cns'], '#9B1FC1',
574
- ['==', ['get', 'kind'], 'lcn'], '#F19E38',
575
- ['==', ['get', 'kind'], 'para-post'], '#3F8F4A',
576
- ['==', ['get', 'kind'], 'para-pre'], '#3F8F4A',
577
- ['==', ['get', 'kind'], 'somatic'], '#98561D',
578
- ['==', ['get', 'kind'], 'sensory'], '#2A62F6',
579
- ['==', ['get', 'kind'], 'symp-post'], '#EA3423',
580
- ['==', ['get', 'kind'], 'symp-pre'], '#EA3423',
696
+ ...PATH_STYLE_RULES,
581
697
  'white'
582
698
  ],
583
699
  'fill-opacity': [
@@ -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 {