@abi-software/flatmap-viewer 2.2.10 → 2.2.11-devel.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 +1 -1
- package/src/controls.js +132 -0
- package/src/editor.js +198 -0
- package/src/flatmap-viewer.js +29 -3
- package/src/info.js +4 -0
- package/src/interactions.js +16 -1
- package/src/layers.js +3 -0
- package/src/main.js +2 -0
- package/src/minimap.js +1 -1
- package/src/newcontrols.js +617 -0
- package/src/pathways.js +4 -1
- package/src/search.js +1 -0
- package/src/styling.js +16 -13
package/package.json
CHANGED
package/src/controls.js
CHANGED
|
@@ -22,6 +22,8 @@ limitations under the License.
|
|
|
22
22
|
|
|
23
23
|
//==============================================================================
|
|
24
24
|
|
|
25
|
+
// Needed for Webpack
|
|
26
|
+
import zoomInButton from '../static/images/zoom-in-button.png'
|
|
25
27
|
|
|
26
28
|
//==============================================================================
|
|
27
29
|
|
|
@@ -118,6 +120,9 @@ export class PathControl
|
|
|
118
120
|
const innerHTML = [];
|
|
119
121
|
innerHTML.push(`<label for="path-all-paths">ALL PATHS:</label><div class="nerve-line"></div><input id="path-all-paths" type="checkbox" checked/>`);
|
|
120
122
|
for (const path of this.__pathTypes) {
|
|
123
|
+
// need to set style background instead of class...
|
|
124
|
+
// background: #2A62F6;
|
|
125
|
+
// background: repeating-linear-gradient(to right,#EA3423 0,#EA3423 6px,transparent 6px,transparent 9px)
|
|
121
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');
|
|
@@ -316,6 +321,133 @@ export class LayerControl
|
|
|
316
321
|
|
|
317
322
|
//==============================================================================
|
|
318
323
|
|
|
324
|
+
|
|
325
|
+
const SCKAN_STATES = [
|
|
326
|
+
{
|
|
327
|
+
'id': 'VALID',
|
|
328
|
+
'description': 'Path consistent with SCKAN'
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
'id': 'INVALID',
|
|
332
|
+
'description': 'Path inconsistent with SCKAN'
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
export class SCKANControl
|
|
338
|
+
{
|
|
339
|
+
constructor(flatmap)
|
|
340
|
+
{
|
|
341
|
+
this.__flatmap = flatmap;
|
|
342
|
+
this.__map = undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getDefaultPosition()
|
|
346
|
+
//==================
|
|
347
|
+
{
|
|
348
|
+
return 'top-right';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
onAdd(map)
|
|
352
|
+
//========
|
|
353
|
+
{
|
|
354
|
+
this.__map = map;
|
|
355
|
+
this.__container = document.createElement('div');
|
|
356
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
357
|
+
this.__container.id = 'flatmap-layer-control';
|
|
358
|
+
|
|
359
|
+
this.__sckan = document.createElement('div');
|
|
360
|
+
this.__sckan.id = 'sckan-control-text';
|
|
361
|
+
this.__sckan.className = 'flatmap-layer-grid';
|
|
362
|
+
|
|
363
|
+
const innerHTML = [];
|
|
364
|
+
innerHTML.push(`<label for="sckan-all-paths">ALL PATHS:</label><input id="sckan-all-paths" type="checkbox" checked/>`);
|
|
365
|
+
for (const state of SCKAN_STATES) {
|
|
366
|
+
innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" checked/>`);
|
|
367
|
+
}
|
|
368
|
+
this.__sckan.innerHTML = innerHTML.join('\n');
|
|
369
|
+
|
|
370
|
+
this.__sckanCount = SCKAN_STATES.length;
|
|
371
|
+
this.__checkedCount = this.__sckanCount;
|
|
372
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
373
|
+
|
|
374
|
+
this.__button = document.createElement('button');
|
|
375
|
+
this.__button.id = 'map-sckan-button';
|
|
376
|
+
this.__button.className = 'control-button text-button';
|
|
377
|
+
this.__button.setAttribute('type', 'button');
|
|
378
|
+
this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
|
|
379
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
380
|
+
this.__button.textContent = 'SCKAN';
|
|
381
|
+
this.__button.title = 'Show/hide valid SCKAN paths';
|
|
382
|
+
this.__container.appendChild(this.__button);
|
|
383
|
+
|
|
384
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
385
|
+
return this.__container;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
onRemove()
|
|
389
|
+
//========
|
|
390
|
+
{
|
|
391
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
392
|
+
this.__map = undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
onClick_(event)
|
|
396
|
+
//=============
|
|
397
|
+
{
|
|
398
|
+
if (event.target.id === 'map-sckan-button') {
|
|
399
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
400
|
+
this.__container.appendChild(this.__sckan);
|
|
401
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
402
|
+
this.__sckan.focus();
|
|
403
|
+
} else {
|
|
404
|
+
this.__sckan = this.__container.removeChild(this.__sckan);
|
|
405
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
406
|
+
}
|
|
407
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
408
|
+
if (event.target.id === 'sckan-all-paths') {
|
|
409
|
+
if (event.target.indeterminate) {
|
|
410
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
411
|
+
event.target.indeterminate = false;
|
|
412
|
+
}
|
|
413
|
+
if (event.target.checked) {
|
|
414
|
+
this.__checkedCount = this.__sckanCount;
|
|
415
|
+
} else {
|
|
416
|
+
this.__checkedCount = 0;
|
|
417
|
+
}
|
|
418
|
+
for (const state of SCKAN_STATES) {
|
|
419
|
+
const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
|
|
420
|
+
if (sckanCheckbox) {
|
|
421
|
+
sckanCheckbox.checked = event.target.checked;
|
|
422
|
+
this.__flatmap.showSckanPaths(state.id, event.target.checked);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} else if (event.target.id.startsWith('sckan-')) {
|
|
426
|
+
const sckanId = event.target.id.substring(6);
|
|
427
|
+
this.__flatmap.showSckanPaths(sckanId, event.target.checked);
|
|
428
|
+
if (event.target.checked) {
|
|
429
|
+
this.__checkedCount += 1;
|
|
430
|
+
} else {
|
|
431
|
+
this.__checkedCount -= 1;
|
|
432
|
+
}
|
|
433
|
+
const allLayersCheckbox = document.getElementById('sckan-all-paths');
|
|
434
|
+
if (this.__checkedCount === 0) {
|
|
435
|
+
allLayersCheckbox.checked = false;
|
|
436
|
+
allLayersCheckbox.indeterminate = false;
|
|
437
|
+
} else if (this.__checkedCount === this.__sckanCount) {
|
|
438
|
+
allLayersCheckbox.checked = true;
|
|
439
|
+
allLayersCheckbox.indeterminate = false;
|
|
440
|
+
} else {
|
|
441
|
+
allLayersCheckbox.indeterminate = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
event.stopPropagation();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
//==============================================================================
|
|
450
|
+
|
|
319
451
|
export class BackgroundControl
|
|
320
452
|
{
|
|
321
453
|
constructor(flatmap)
|
package/src/editor.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Bezier } from "bezier-js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BezierCurve
|
|
5
|
+
{
|
|
6
|
+
constructor(id, ...points) {
|
|
7
|
+
this.__id = id;
|
|
8
|
+
this.__bezier = new Bezier(...points);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
asGeoJSON(samples=100)
|
|
12
|
+
{
|
|
13
|
+
const coords = [];
|
|
14
|
+
for (let ts = 0; ts <= samples; ts++) {
|
|
15
|
+
const pt = this.__bezier.get(float(ts)/float(samples));
|
|
16
|
+
coords.push([pt.x, pt.y]);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
'type': 'Feature',
|
|
20
|
+
'geometry': {
|
|
21
|
+
'type': 'LineString',
|
|
22
|
+
'coordinates': coords
|
|
23
|
+
},
|
|
24
|
+
'properties': {
|
|
25
|
+
'bezier': this.__id
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
asPoints()
|
|
31
|
+
{
|
|
32
|
+
const geojson = [];
|
|
33
|
+
for (const n of [0, 1, 2, 3]) {
|
|
34
|
+
const pt = this.__bezier.points[n];
|
|
35
|
+
geojson.push({
|
|
36
|
+
'type': 'Feature',
|
|
37
|
+
'geometry': {
|
|
38
|
+
'type': 'Point',
|
|
39
|
+
'coordinates': [pt.x, pt.y]
|
|
40
|
+
},
|
|
41
|
+
'properties': {
|
|
42
|
+
'bezier': this.__id,
|
|
43
|
+
'point': n
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return geojson;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
export class NetworkEditor
|
|
53
|
+
{
|
|
54
|
+
constructor(flatmap)
|
|
55
|
+
{
|
|
56
|
+
this.__map = flatmap.map;
|
|
57
|
+
this.__canvas = this.__map.getCanvasContainer();
|
|
58
|
+
|
|
59
|
+
this.__geojson = { // lines and points as separate sources???
|
|
60
|
+
'type': 'FeatureCollection',
|
|
61
|
+
'features': [
|
|
62
|
+
{
|
|
63
|
+
'type': 'Feature',
|
|
64
|
+
'geometry': {
|
|
65
|
+
'type': 'Point',
|
|
66
|
+
'coordinates': [0, 0]
|
|
67
|
+
},
|
|
68
|
+
'properties': {
|
|
69
|
+
'id': 0
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Add a single point to the map
|
|
76
|
+
this.__map.addSource('curves', {
|
|
77
|
+
'type': 'geojson',
|
|
78
|
+
'data': this.__geojson
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.__map.addLayer({
|
|
82
|
+
'id': 'lines',
|
|
83
|
+
'type': 'line',
|
|
84
|
+
'source': 'curves',
|
|
85
|
+
'paint': {
|
|
86
|
+
'line-color': '#3887be'
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this.__map.addLayer({
|
|
90
|
+
'id': 'points',
|
|
91
|
+
'type': 'circle',
|
|
92
|
+
'source': 'curves',
|
|
93
|
+
'paint': {
|
|
94
|
+
'circle-radius': 10,
|
|
95
|
+
'circle-color': '#3887be'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.__currentPoint = null;
|
|
100
|
+
|
|
101
|
+
this.__map.on('mouseenter', this.mouseEnter.bind(this));
|
|
102
|
+
this.__map.on('mouseleave', this.mouseLeave.bind(this));
|
|
103
|
+
this.__map.on('mousedown', this.mouseDown.bind(this));
|
|
104
|
+
this.__map.on('touchstart', this.touchStart.bind(this));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
addPoint(coords)
|
|
108
|
+
{
|
|
109
|
+
const nextId = this.__geojson.features.length;
|
|
110
|
+
this.__geojson.features.push({
|
|
111
|
+
'type': 'Feature',
|
|
112
|
+
'geometry': {
|
|
113
|
+
'type': 'Point',
|
|
114
|
+
'coordinates': coords
|
|
115
|
+
},
|
|
116
|
+
'properties': {
|
|
117
|
+
'id': nextId
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
this.__map.getSource('curves').setData(this.__geojson);
|
|
121
|
+
return nextId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mouseEnter(e) {
|
|
125
|
+
console.log('Mouse enter...');
|
|
126
|
+
|
|
127
|
+
this.__map.setPaintProperty('lines', 'line-color', '#3bb2d0');
|
|
128
|
+
this.__map.setPaintProperty('points', 'circle-color', '#3bb2d0');
|
|
129
|
+
this.__canvas.style.cursor = 'move';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
mouseLeave(e) {
|
|
134
|
+
this.__map.setPaintProperty('lines', 'line-color', '#3887be');
|
|
135
|
+
this.__map.setPaintProperty('points', 'circle-color', '#3887be');
|
|
136
|
+
this.__canvas.style.cursor = '';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
mouseDown(e) {
|
|
141
|
+
console.log('Mouse down...')
|
|
142
|
+
// Prevent the default map drag behavior.
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
const features = this.__map.queryRenderedFeatures(e.point, {'layers': ['lines', 'points']});
|
|
147
|
+
if (features.length === 0) {
|
|
148
|
+
const coords = e.lngLat;
|
|
149
|
+
this.__currentPoint = this.addPoint([coords.lng, coords.lat]);
|
|
150
|
+
} else {
|
|
151
|
+
const currentPoint = features[0].properties.id;
|
|
152
|
+
if (this.__currentPoint === null) {
|
|
153
|
+
this.__currentPoint = currentPoint;
|
|
154
|
+
} else if (this.__currentPoint === currentPoint) {
|
|
155
|
+
this.__currentPoint = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.__canvas.style.cursor = 'grab';
|
|
160
|
+
|
|
161
|
+
this.__map.on('mousemove', this.onMove.bind(this));
|
|
162
|
+
this.__map.once('mouseup', this.onUp.bind(this));
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
touchStart(e) {
|
|
167
|
+
if (e.points.length !== 1) return;
|
|
168
|
+
|
|
169
|
+
// Prevent the default map drag behavior.
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
|
|
172
|
+
this.__map.on('touchmove', this.onMove.bind(this));
|
|
173
|
+
this.__map.once('touchend', this.onUp.bind(this));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
onMove(e) {
|
|
177
|
+
console.log('Mouse move...')
|
|
178
|
+
|
|
179
|
+
// Set a UI indicator for dragging.
|
|
180
|
+
this.__canvas.style.cursor = 'grabbing'; // ????
|
|
181
|
+
|
|
182
|
+
if (this.__currentPoint !== null) {
|
|
183
|
+
const coords = e.lngLat;
|
|
184
|
+
this.__geojson.features[this.__currentPoint].geometry.coordinates = [coords.lng, coords.lat];
|
|
185
|
+
this.__map.getSource('curves').setData(this.__geojson);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onUp(e) {
|
|
190
|
+
console.log('End draw at', e.lngLat)
|
|
191
|
+
|
|
192
|
+
this.__canvas.style.cursor = '';
|
|
193
|
+
|
|
194
|
+
// Unbind mouse/touch events
|
|
195
|
+
this.__map.off('mousemove', this.onMove.bind(this));
|
|
196
|
+
this.__map.off('touchmove', this.onMove.bind(this));
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -192,7 +192,8 @@ class FlatMap
|
|
|
192
192
|
this._initialState = this.getState();
|
|
193
193
|
|
|
194
194
|
// Add a minimap if option set
|
|
195
|
-
|
|
195
|
+
// Put this above search, info, etc...
|
|
196
|
+
// ==> add all controls here (via interactions.js...)
|
|
196
197
|
if (this.options.minimap) {
|
|
197
198
|
this._minimap = new MinimapControl(this, this.options.minimap);
|
|
198
199
|
this._map.addControl(this._minimap);
|
|
@@ -314,6 +315,21 @@ class FlatMap
|
|
|
314
315
|
}
|
|
315
316
|
}
|
|
316
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Hide or show all paths valid in SCKAN.
|
|
320
|
+
*
|
|
321
|
+
* @param {string} validity Either ``VALID`` or ``INVALID``
|
|
322
|
+
* @param {boolean} [enable=true] If ``true`` then only show the paths
|
|
323
|
+
* of the type(s) otherwise only hide the paths
|
|
324
|
+
*/
|
|
325
|
+
showSckanPaths(validity, enable=true)
|
|
326
|
+
//===================================
|
|
327
|
+
{
|
|
328
|
+
if (this._userInteractions !== null) {
|
|
329
|
+
this._userInteractions.showSckanPaths(validity, enable);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
317
333
|
/**
|
|
318
334
|
* Load images and patterns/textures referenced in style rules.
|
|
319
335
|
*
|
|
@@ -834,15 +850,24 @@ class FlatMap
|
|
|
834
850
|
'dataset',
|
|
835
851
|
'kind',
|
|
836
852
|
'label',
|
|
853
|
+
'markup',
|
|
837
854
|
'models',
|
|
838
855
|
'nodeId',
|
|
839
|
-
'source'
|
|
856
|
+
'source',
|
|
857
|
+
'hyperlinks'
|
|
858
|
+
];
|
|
859
|
+
const jsonProperties = [
|
|
860
|
+
'hyperlinks'
|
|
840
861
|
];
|
|
841
862
|
for (const property of exportedProperties) {
|
|
842
863
|
if (property in properties) {
|
|
843
864
|
const value = properties[property];
|
|
844
865
|
if (value !== undefined) {
|
|
845
|
-
|
|
866
|
+
if (jsonProperties.indexOf(property) >= 0) {
|
|
867
|
+
data[property] = JSON.parse(properties[property])
|
|
868
|
+
} else {
|
|
869
|
+
data[property] = properties[property];
|
|
870
|
+
}
|
|
846
871
|
}
|
|
847
872
|
}
|
|
848
873
|
}
|
|
@@ -1117,6 +1142,7 @@ export class MapManager
|
|
|
1117
1142
|
let latestMap = null;
|
|
1118
1143
|
let lastCreatedTime = '';
|
|
1119
1144
|
for (const map of this._mapList) {
|
|
1145
|
+
// We can break/return if we have a UUID match...
|
|
1120
1146
|
if (('uuid' in map && mapDescribes === map.uuid
|
|
1121
1147
|
|| mapDescribes === map.id
|
|
1122
1148
|
|| 'taxon' in map && mapDescribes === map.taxon
|
package/src/info.js
CHANGED
package/src/interactions.js
CHANGED
|
@@ -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
|
|
|
@@ -171,6 +171,8 @@ export class UserInteractions
|
|
|
171
171
|
|
|
172
172
|
if (flatmap.options.layerControl) {
|
|
173
173
|
this._map.addControl(new LayerControl(flatmap, this._layerManager));
|
|
174
|
+
// ************************************
|
|
175
|
+
//this._map.addControl(new SCKANControl(flatmap));
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
// Flag features that have annotations
|
|
@@ -178,6 +180,9 @@ export class UserInteractions
|
|
|
178
180
|
|
|
179
181
|
for (const [id, ann] of flatmap.annotations) {
|
|
180
182
|
const feature = this.mapFeature_(id);
|
|
183
|
+
if (id == 118) {
|
|
184
|
+
console.log(feature, ann);
|
|
185
|
+
}
|
|
181
186
|
if (feature !== undefined) {
|
|
182
187
|
this._map.setFeatureState(feature, { 'annotated': true });
|
|
183
188
|
}
|
|
@@ -556,6 +561,7 @@ export class UserInteractions
|
|
|
556
561
|
}
|
|
557
562
|
bbox = expandBounds(bbox, annotation.bounds);
|
|
558
563
|
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
564
|
+
// FC may have lines that are not pathways features, esp. when authoring...
|
|
559
565
|
for (const pathFeatureId of this._pathways.lineFeatureIds([featureId])) {
|
|
560
566
|
if (select) {
|
|
561
567
|
this.selectFeature_(pathFeatureId);
|
|
@@ -777,6 +783,10 @@ export class UserInteractions
|
|
|
777
783
|
}
|
|
778
784
|
}
|
|
779
785
|
} else {
|
|
786
|
+
// Allow tooltip for body but no highlighting/activation??
|
|
787
|
+
// More generally, only highlight a feature if all "within" the viewport??
|
|
788
|
+
// -- then as we zoom outermost features wouldn't highlight (but still show tooltip)
|
|
789
|
+
// -- or highlight in a more subtle way (eg. opacity change and not colour change)??
|
|
780
790
|
let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
|
|
781
791
|
|| 'label' in feature.properties
|
|
782
792
|
|| 'node' in feature.properties)
|
|
@@ -1022,6 +1032,11 @@ export class UserInteractions
|
|
|
1022
1032
|
return this._pathways.nodePathModels(nodeId);
|
|
1023
1033
|
}
|
|
1024
1034
|
|
|
1035
|
+
showSckanPaths(validity, enable=true)
|
|
1036
|
+
//===================================
|
|
1037
|
+
{
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1025
1040
|
//==============================================================================
|
|
1026
1041
|
|
|
1027
1042
|
// Find where to place a label or popup on a feature
|
package/src/layers.js
CHANGED
|
@@ -20,6 +20,9 @@ limitations under the License.
|
|
|
20
20
|
|
|
21
21
|
'use strict';
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
// See https://stevage.github.io/map-gl-utils/ for layering ideas...
|
|
25
|
+
|
|
23
26
|
//==============================================================================
|
|
24
27
|
|
|
25
28
|
import {PATHWAYS_LAYER} from './pathways.js';
|
package/src/main.js
CHANGED
|
@@ -92,6 +92,8 @@ export async function standaloneViewer(map_endpoint=null, options={})
|
|
|
92
92
|
mapManager.loadMap(id, 'map-canvas', (eventType, ...args) => {
|
|
93
93
|
if (args[0].type === 'control' && args[0].control === 'background') {
|
|
94
94
|
mapOptions.background = args[0].value;
|
|
95
|
+
} else if (eventType === 'click') {
|
|
96
|
+
console.log(args);
|
|
95
97
|
}
|
|
96
98
|
}, mapOptions)
|
|
97
99
|
.then(map => {
|
package/src/minimap.js
CHANGED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 David Brooks
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
******************************************************************************/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
//==============================================================================
|
|
24
|
+
|
|
25
|
+
// Needed for Webpack
|
|
26
|
+
import zoomInButton from '../static/images/zoom-in-button.png'
|
|
27
|
+
|
|
28
|
+
//==============================================================================
|
|
29
|
+
|
|
30
|
+
// Make sure colour string is in `#rrggbb` form.
|
|
31
|
+
// Based on https://stackoverflow.com/a/47355187
|
|
32
|
+
|
|
33
|
+
function standardise_color(str){
|
|
34
|
+
const canvas = document.createElement("canvas");
|
|
35
|
+
const ctx = canvas.getContext("2d");
|
|
36
|
+
ctx.fillStyle = str;
|
|
37
|
+
const colour = ctx.fillStyle;
|
|
38
|
+
canvas.remove()
|
|
39
|
+
return colour;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//==============================================================================
|
|
43
|
+
|
|
44
|
+
export class NavigationControl
|
|
45
|
+
{
|
|
46
|
+
constructor(flatmap)
|
|
47
|
+
{
|
|
48
|
+
this._flatmap = flatmap;
|
|
49
|
+
this._map = undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getDefaultPosition()
|
|
53
|
+
//==================
|
|
54
|
+
{
|
|
55
|
+
return 'top-right';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onAdd(map)
|
|
59
|
+
//========
|
|
60
|
+
{
|
|
61
|
+
this._map = map;
|
|
62
|
+
this._container = document.createElement('div');
|
|
63
|
+
this._container.className = 'maplibregl-ctrl navigation-group';
|
|
64
|
+
this._container.innerHTML = `<button id="flatmap-zoom-in" class="navigation-zoom-in" type="button" title="Zoom in" aria-label="Zoom in"></button>
|
|
65
|
+
<button id="flatmap-zoom-out" class="navigation-zoom-out" type="button" title="Zoom out" aria-label="Zoom out"></button>
|
|
66
|
+
<button id="flatmap-reset" class="navigation-reset" type="button" title="Reset" aria-label="Reset"></button>`;
|
|
67
|
+
this._container.onclick = this.onClick_.bind(this);
|
|
68
|
+
return this._container;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onRemove()
|
|
72
|
+
//========
|
|
73
|
+
{
|
|
74
|
+
this._container.parentNode.removeChild(this._container);
|
|
75
|
+
this._map = undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onClick_(e)
|
|
79
|
+
//=========
|
|
80
|
+
{
|
|
81
|
+
if (e.target.id === 'flatmap-zoom-in') {
|
|
82
|
+
this._flatmap.zoomIn();
|
|
83
|
+
} else if (e.target.id === 'flatmap-zoom-out') {
|
|
84
|
+
this._flatmap.zoomOut();
|
|
85
|
+
} else if (e.target.id === 'flatmap-reset') {
|
|
86
|
+
this._flatmap.resetMap();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//==============================================================================
|
|
92
|
+
|
|
93
|
+
export class PathControl
|
|
94
|
+
{
|
|
95
|
+
constructor(flatmap, pathTypes)
|
|
96
|
+
{
|
|
97
|
+
this._flatmap = flatmap;
|
|
98
|
+
this._map = undefined;
|
|
99
|
+
this.__pathTypes = pathTypes;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getDefaultPosition()
|
|
103
|
+
//==================
|
|
104
|
+
{
|
|
105
|
+
return 'top-right';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onAdd(map)
|
|
109
|
+
//========
|
|
110
|
+
{
|
|
111
|
+
this._map = map;
|
|
112
|
+
this._container = document.createElement('div');
|
|
113
|
+
this._container.className = 'maplibregl-ctrl';
|
|
114
|
+
this._container.id = 'flatmap-nerve-key';
|
|
115
|
+
|
|
116
|
+
this._legend = document.createElement('div');
|
|
117
|
+
this._legend.id = 'nerve-key-text';
|
|
118
|
+
this._legend.className = 'flatmap-nerve-grid';
|
|
119
|
+
|
|
120
|
+
const innerHTML = [];
|
|
121
|
+
innerHTML.push(`<label for="path-all-paths">ALL PATHS:</label><div class="nerve-line"></div><input id="path-all-paths" type="checkbox" checked/>`);
|
|
122
|
+
for (const path of this.__pathTypes) {
|
|
123
|
+
// need to set style background instead of class...
|
|
124
|
+
// background: #2A62F6;
|
|
125
|
+
// background: repeating-linear-gradient(to right,#EA3423 0,#EA3423 6px,transparent 6px,transparent 9px)
|
|
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/>`);
|
|
127
|
+
}
|
|
128
|
+
this._legend.innerHTML = innerHTML.join('\n');
|
|
129
|
+
this.__checkedCount = this.__pathTypes.length;
|
|
130
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
131
|
+
|
|
132
|
+
this._button = document.createElement('button');
|
|
133
|
+
this._button.id = 'nerve-key-button';
|
|
134
|
+
this._button.className = 'control-button text-button';
|
|
135
|
+
this._button.setAttribute('type', 'button');
|
|
136
|
+
this._button.setAttribute('aria-label', 'Nerve paths legend');
|
|
137
|
+
this._button.setAttribute('control-visible', 'false');
|
|
138
|
+
this._button.textContent = 'PATHS';
|
|
139
|
+
this._button.title = 'Show/hide neuron paths';
|
|
140
|
+
this._container.appendChild(this._button);
|
|
141
|
+
|
|
142
|
+
this._container.addEventListener('click', this.onClick_.bind(this));
|
|
143
|
+
return this._container;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onRemove()
|
|
147
|
+
//========
|
|
148
|
+
{
|
|
149
|
+
this._container.parentNode.removeChild(this._container);
|
|
150
|
+
this._map = undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onClick_(event)
|
|
154
|
+
//=============
|
|
155
|
+
{
|
|
156
|
+
if (event.target.id === 'nerve-key-button') {
|
|
157
|
+
if (this._button.getAttribute('control-visible') === 'false') {
|
|
158
|
+
this._container.appendChild(this._legend);
|
|
159
|
+
this._button.setAttribute('control-visible', 'true');
|
|
160
|
+
this._legend.focus();
|
|
161
|
+
} else {
|
|
162
|
+
this._legend = this._container.removeChild(this._legend);
|
|
163
|
+
this._button.setAttribute('control-visible', 'false');
|
|
164
|
+
}
|
|
165
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
166
|
+
if (event.target.id === 'path-all-paths') {
|
|
167
|
+
if (event.target.indeterminate) {
|
|
168
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
169
|
+
event.target.indeterminate = false;
|
|
170
|
+
}
|
|
171
|
+
if (event.target.checked) {
|
|
172
|
+
this.__checkedCount = this.__pathTypes.length;
|
|
173
|
+
} else {
|
|
174
|
+
this.__checkedCount = 0;
|
|
175
|
+
}
|
|
176
|
+
for (const path of this.__pathTypes) {
|
|
177
|
+
const pathCheckbox = document.getElementById(`path-${path.type}`);
|
|
178
|
+
if (pathCheckbox) {
|
|
179
|
+
pathCheckbox.checked = event.target.checked;
|
|
180
|
+
this._flatmap.enablePath(path.type, event.target.checked);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else if (event.target.id.startsWith('path-')) {
|
|
184
|
+
const pathType = event.target.id.substring(5);
|
|
185
|
+
this._flatmap.enablePath(pathType, event.target.checked);
|
|
186
|
+
if (event.target.checked) {
|
|
187
|
+
this.__checkedCount += 1;
|
|
188
|
+
} else {
|
|
189
|
+
this.__checkedCount -= 1;
|
|
190
|
+
}
|
|
191
|
+
const allPathsCheckbox = document.getElementById('path-all-paths');
|
|
192
|
+
if (this.__checkedCount === 0) {
|
|
193
|
+
allPathsCheckbox.checked = false;
|
|
194
|
+
allPathsCheckbox.indeterminate = false;
|
|
195
|
+
} else if (this.__checkedCount === this.__pathTypes.length) {
|
|
196
|
+
allPathsCheckbox.checked = true;
|
|
197
|
+
allPathsCheckbox.indeterminate = false;
|
|
198
|
+
} else {
|
|
199
|
+
allPathsCheckbox.indeterminate = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
event.stopPropagation();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//==============================================================================
|
|
208
|
+
|
|
209
|
+
export class LayerControl
|
|
210
|
+
{
|
|
211
|
+
constructor(flatmap, layerManager)
|
|
212
|
+
{
|
|
213
|
+
this.__flatmap = flatmap;
|
|
214
|
+
this.__manager = layerManager;
|
|
215
|
+
this.__map = undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getDefaultPosition()
|
|
219
|
+
//==================
|
|
220
|
+
{
|
|
221
|
+
return 'top-right';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
onAdd(map)
|
|
225
|
+
//========
|
|
226
|
+
{
|
|
227
|
+
this.__map = map;
|
|
228
|
+
this.__container = document.createElement('div');
|
|
229
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
230
|
+
this.__container.id = 'flatmap-layer-control';
|
|
231
|
+
|
|
232
|
+
this.__layers = document.createElement('div');
|
|
233
|
+
this.__layers.id = 'layer-control-text';
|
|
234
|
+
this.__layers.className = 'flatmap-layer-grid';
|
|
235
|
+
|
|
236
|
+
const innerHTML = [];
|
|
237
|
+
innerHTML.push(`<label for="layer-all-layers">ALL LAYERS:</label><input id="layer-all-layers" type="checkbox" checked/>`);
|
|
238
|
+
for (const layer of this.__manager.layers) {
|
|
239
|
+
innerHTML.push(`<label for="layer-${layer.id}">${layer.description}</label><input id="layer-${layer.id}" type="checkbox" checked/>`);
|
|
240
|
+
}
|
|
241
|
+
this.__layers.innerHTML = innerHTML.join('\n');
|
|
242
|
+
|
|
243
|
+
this.__layersCount = this.__manager.layers.length;
|
|
244
|
+
this.__checkedCount = this.__layersCount;
|
|
245
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
246
|
+
|
|
247
|
+
this.__button = document.createElement('button');
|
|
248
|
+
this.__button.id = 'map-layers-button';
|
|
249
|
+
this.__button.className = 'control-button text-button';
|
|
250
|
+
this.__button.setAttribute('type', 'button');
|
|
251
|
+
this.__button.setAttribute('aria-label', 'Show/hide map layers');
|
|
252
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
253
|
+
this.__button.textContent = 'LAYERS';
|
|
254
|
+
this.__button.title = 'Show/hide map layers';
|
|
255
|
+
this.__container.appendChild(this.__button);
|
|
256
|
+
|
|
257
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
258
|
+
return this.__container;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
onRemove()
|
|
262
|
+
//========
|
|
263
|
+
{
|
|
264
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
265
|
+
this.__map = undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
onClick_(event)
|
|
269
|
+
//=============
|
|
270
|
+
{
|
|
271
|
+
if (event.target.id === 'map-layers-button') {
|
|
272
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
273
|
+
this.__container.appendChild(this.__layers);
|
|
274
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
275
|
+
this.__layers.focus();
|
|
276
|
+
} else {
|
|
277
|
+
this.__layers = this.__container.removeChild(this.__layers);
|
|
278
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
279
|
+
}
|
|
280
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
281
|
+
if (event.target.id === 'layer-all-layers') {
|
|
282
|
+
if (event.target.indeterminate) {
|
|
283
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
284
|
+
event.target.indeterminate = false;
|
|
285
|
+
}
|
|
286
|
+
if (event.target.checked) {
|
|
287
|
+
this.__checkedCount = this.__layersCount;
|
|
288
|
+
} else {
|
|
289
|
+
this.__checkedCount = 0;
|
|
290
|
+
}
|
|
291
|
+
for (const layer of this.__manager.layers) {
|
|
292
|
+
const layerCheckbox = document.getElementById(`layer-${layer.id}`);
|
|
293
|
+
if (layerCheckbox) {
|
|
294
|
+
layerCheckbox.checked = event.target.checked;
|
|
295
|
+
this.__manager.activate(layer.id, event.target.checked);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (event.target.id.startsWith('layer-')) {
|
|
299
|
+
const layerId = event.target.id.substring(6);
|
|
300
|
+
this.__manager.activate(layerId, event.target.checked);
|
|
301
|
+
if (event.target.checked) {
|
|
302
|
+
this.__checkedCount += 1;
|
|
303
|
+
} else {
|
|
304
|
+
this.__checkedCount -= 1;
|
|
305
|
+
}
|
|
306
|
+
const allLayersCheckbox = document.getElementById('layer-all-layers');
|
|
307
|
+
if (this.__checkedCount === 0) {
|
|
308
|
+
allLayersCheckbox.checked = false;
|
|
309
|
+
allLayersCheckbox.indeterminate = false;
|
|
310
|
+
} else if (this.__checkedCount === this.__layersCount) {
|
|
311
|
+
allLayersCheckbox.checked = true;
|
|
312
|
+
allLayersCheckbox.indeterminate = false;
|
|
313
|
+
} else {
|
|
314
|
+
allLayersCheckbox.indeterminate = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
event.stopPropagation();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
//==============================================================================
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
const SCKAN_STATES = [
|
|
326
|
+
{
|
|
327
|
+
'id': 'VALID',
|
|
328
|
+
'description': 'Path consistent with SCKAN'
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
'id': 'INVALID',
|
|
332
|
+
'description': 'Path inconsistent with SCKAN'
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
export class SCKANControl
|
|
338
|
+
{
|
|
339
|
+
constructor(flatmap)
|
|
340
|
+
{
|
|
341
|
+
this.__flatmap = flatmap;
|
|
342
|
+
this.__map = undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getDefaultPosition()
|
|
346
|
+
//==================
|
|
347
|
+
{
|
|
348
|
+
return 'top-right';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
onAdd(map)
|
|
352
|
+
//========
|
|
353
|
+
{
|
|
354
|
+
this.__map = map;
|
|
355
|
+
this.__container = document.createElement('div');
|
|
356
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
357
|
+
this.__container.id = 'flatmap-layer-control';
|
|
358
|
+
|
|
359
|
+
this.__sckan = document.createElement('div');
|
|
360
|
+
this.__sckan.id = 'sckan-control-text';
|
|
361
|
+
this.__sckan.className = 'flatmap-layer-grid';
|
|
362
|
+
|
|
363
|
+
const innerHTML = [];
|
|
364
|
+
innerHTML.push(`<label for="sckan-all-paths">ALL PATHS:</label><input id="sckan-all-paths" type="checkbox" checked/>`);
|
|
365
|
+
for (const state of SCKAN_STATES) {
|
|
366
|
+
innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" checked/>`);
|
|
367
|
+
}
|
|
368
|
+
this.__sckan.innerHTML = innerHTML.join('\n');
|
|
369
|
+
|
|
370
|
+
this.__sckanCount = SCKAN_STATES.length;
|
|
371
|
+
this.__checkedCount = this.__sckanCount;
|
|
372
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
373
|
+
|
|
374
|
+
this.__button = document.createElement('button');
|
|
375
|
+
this.__button.id = 'map-sckan-button';
|
|
376
|
+
this.__button.className = 'control-button text-button';
|
|
377
|
+
this.__button.setAttribute('type', 'button');
|
|
378
|
+
this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
|
|
379
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
380
|
+
this.__button.textContent = 'SCKAN';
|
|
381
|
+
this.__button.title = 'Show/hide valid SCKAN paths';
|
|
382
|
+
this.__container.appendChild(this.__button);
|
|
383
|
+
|
|
384
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
385
|
+
return this.__container;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
onRemove()
|
|
389
|
+
//========
|
|
390
|
+
{
|
|
391
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
392
|
+
this.__map = undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
onClick_(event)
|
|
396
|
+
//=============
|
|
397
|
+
{
|
|
398
|
+
if (event.target.id === 'map-sckan-button') {
|
|
399
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
400
|
+
this.__container.appendChild(this.__sckan);
|
|
401
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
402
|
+
this.__sckan.focus();
|
|
403
|
+
} else {
|
|
404
|
+
this.__sckan = this.__container.removeChild(this.__sckan);
|
|
405
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
406
|
+
}
|
|
407
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
408
|
+
if (event.target.id === 'sckan-all-paths') {
|
|
409
|
+
if (event.target.indeterminate) {
|
|
410
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
411
|
+
event.target.indeterminate = false;
|
|
412
|
+
}
|
|
413
|
+
if (event.target.checked) {
|
|
414
|
+
this.__checkedCount = this.__sckanCount;
|
|
415
|
+
} else {
|
|
416
|
+
this.__checkedCount = 0;
|
|
417
|
+
}
|
|
418
|
+
for (const state of SCKAN_STATES) {
|
|
419
|
+
const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
|
|
420
|
+
if (sckanCheckbox) {
|
|
421
|
+
sckanCheckbox.checked = event.target.checked;
|
|
422
|
+
this.__flatmap.showSckanPaths(state.id, event.target.checked);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} else if (event.target.id.startsWith('sckan-')) {
|
|
426
|
+
const sckanId = event.target.id.substring(6);
|
|
427
|
+
this.__flatmap.showSckanPaths(sckanId, event.target.checked);
|
|
428
|
+
if (event.target.checked) {
|
|
429
|
+
this.__checkedCount += 1;
|
|
430
|
+
} else {
|
|
431
|
+
this.__checkedCount -= 1;
|
|
432
|
+
}
|
|
433
|
+
const allLayersCheckbox = document.getElementById('sckan-all-paths');
|
|
434
|
+
if (this.__checkedCount === 0) {
|
|
435
|
+
allLayersCheckbox.checked = false;
|
|
436
|
+
allLayersCheckbox.indeterminate = false;
|
|
437
|
+
} else if (this.__checkedCount === this.__sckanCount) {
|
|
438
|
+
allLayersCheckbox.checked = true;
|
|
439
|
+
allLayersCheckbox.indeterminate = false;
|
|
440
|
+
} else {
|
|
441
|
+
allLayersCheckbox.indeterminate = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
event.stopPropagation();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class StateControl
|
|
453
|
+
{
|
|
454
|
+
constructor(id, flatmap, states, controlFunction)
|
|
455
|
+
{
|
|
456
|
+
this.__id = id;
|
|
457
|
+
this.__flatmap = flatmap;
|
|
458
|
+
this.__states = states;
|
|
459
|
+
this.__controlFunction = controlFunction;
|
|
460
|
+
this.__map = undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
getDefaultPosition()
|
|
464
|
+
//==================
|
|
465
|
+
{
|
|
466
|
+
return 'top-right';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
onAdd(map)
|
|
470
|
+
//========
|
|
471
|
+
{
|
|
472
|
+
this.__map = map;
|
|
473
|
+
this.__container = document.createElement('div');
|
|
474
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
475
|
+
this.__container.id = 'flatmap-state-control';
|
|
476
|
+
|
|
477
|
+
this.__control = document.createElement('div');
|
|
478
|
+
this.__control.id = `${this.__id}-control-text`;
|
|
479
|
+
this.__control.className = 'flatmap-state-grid';
|
|
480
|
+
|
|
481
|
+
const innerHTML = [];
|
|
482
|
+
innerHTML.push(`<label for="sckan-all-paths">ALL PATHS:</label><input id="sckan-all-paths" type="checkbox" checked/>`);
|
|
483
|
+
for (const state of SCKAN_STATES) {
|
|
484
|
+
innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" checked/>`);
|
|
485
|
+
}
|
|
486
|
+
this.__sckan.innerHTML = innerHTML.join('\n');
|
|
487
|
+
|
|
488
|
+
this.__sckanCount = SCKAN_STATES.length;
|
|
489
|
+
this.__checkedCount = this.__sckanCount;
|
|
490
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
491
|
+
|
|
492
|
+
this.__button = document.createElement('button');
|
|
493
|
+
this.__button.id = 'map-sckan-button';
|
|
494
|
+
this.__button.className = 'control-button text-button';
|
|
495
|
+
this.__button.setAttribute('type', 'button');
|
|
496
|
+
this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
|
|
497
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
498
|
+
this.__button.textContent = 'SCKAN';
|
|
499
|
+
this.__button.title = 'Show/hide valid SCKAN paths';
|
|
500
|
+
this.__container.appendChild(this.__button);
|
|
501
|
+
|
|
502
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
503
|
+
return this.__container;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
onRemove()
|
|
507
|
+
//========
|
|
508
|
+
{
|
|
509
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
510
|
+
this.__map = undefined;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
onClick_(event)
|
|
514
|
+
//=============
|
|
515
|
+
{
|
|
516
|
+
if (event.target.id === 'map-sckan-button') {
|
|
517
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
518
|
+
this.__container.appendChild(this.__sckan);
|
|
519
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
520
|
+
this.__sckan.focus();
|
|
521
|
+
} else {
|
|
522
|
+
this.__sckan = this.__container.removeChild(this.__sckan);
|
|
523
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
524
|
+
}
|
|
525
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
526
|
+
if (event.target.id === 'sckan-all-paths') {
|
|
527
|
+
if (event.target.indeterminate) {
|
|
528
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
529
|
+
event.target.indeterminate = false;
|
|
530
|
+
}
|
|
531
|
+
if (event.target.checked) {
|
|
532
|
+
this.__checkedCount = this.__sckanCount;
|
|
533
|
+
} else {
|
|
534
|
+
this.__checkedCount = 0;
|
|
535
|
+
}
|
|
536
|
+
for (const state of SCKAN_STATES) {
|
|
537
|
+
const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
|
|
538
|
+
if (sckanCheckbox) {
|
|
539
|
+
sckanCheckbox.checked = event.target.checked;
|
|
540
|
+
this.__flatmap.showSckanPaths(state.id, event.target.checked);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} else if (event.target.id.startsWith('sckan-')) {
|
|
544
|
+
const sckanId = event.target.id.substring(6);
|
|
545
|
+
this.__flatmap.showSckanPaths(sckanId, event.target.checked);
|
|
546
|
+
if (event.target.checked) {
|
|
547
|
+
this.__checkedCount += 1;
|
|
548
|
+
} else {
|
|
549
|
+
this.__checkedCount -= 1;
|
|
550
|
+
}
|
|
551
|
+
const allLayersCheckbox = document.getElementById('sckan-all-paths');
|
|
552
|
+
if (this.__checkedCount === 0) {
|
|
553
|
+
allLayersCheckbox.checked = false;
|
|
554
|
+
allLayersCheckbox.indeterminate = false;
|
|
555
|
+
} else if (this.__checkedCount === this.__sckanCount) {
|
|
556
|
+
allLayersCheckbox.checked = true;
|
|
557
|
+
allLayersCheckbox.indeterminate = false;
|
|
558
|
+
} else {
|
|
559
|
+
allLayersCheckbox.indeterminate = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
event.stopPropagation();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
//==============================================================================
|
|
568
|
+
|
|
569
|
+
export class BackgroundControl
|
|
570
|
+
{
|
|
571
|
+
constructor(flatmap)
|
|
572
|
+
{
|
|
573
|
+
this.__flatmap = flatmap;
|
|
574
|
+
this.__map = undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
getDefaultPosition()
|
|
578
|
+
//==================
|
|
579
|
+
{
|
|
580
|
+
return 'bottom-right';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
onAdd(map)
|
|
584
|
+
//========
|
|
585
|
+
{
|
|
586
|
+
this.__map = map;
|
|
587
|
+
this.__container = document.createElement('div');
|
|
588
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
589
|
+
this.__colourDiv = document.createElement('div');
|
|
590
|
+
this.__colourDiv.setAttribute('aria-label', 'Change background colour');
|
|
591
|
+
this.__colourDiv.title = 'Change background colour';
|
|
592
|
+
const background = standardise_color(this.__flatmap.getBackgroundColour());
|
|
593
|
+
this.__colourDiv.innerHTML = `<input type="color" id="colourPicker" value="${background}">`;
|
|
594
|
+
this.__container.appendChild(this.__colourDiv);
|
|
595
|
+
this.__colourDiv.addEventListener('input', this.__updateColour.bind(this), false);
|
|
596
|
+
this.__colourDiv.addEventListener('change', this.__updateColour.bind(this), false);
|
|
597
|
+
return this.__container;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
onRemove()
|
|
601
|
+
//========
|
|
602
|
+
{
|
|
603
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
604
|
+
this.__map = undefined;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
__updateColour(event)
|
|
608
|
+
//===================
|
|
609
|
+
{
|
|
610
|
+
const colour = event.target.value;
|
|
611
|
+
this.__flatmap.setBackgroundColour(colour);
|
|
612
|
+
this.__flatmap.controlEvent('change', 'background', colour)
|
|
613
|
+
event.stopPropagation();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
//==============================================================================
|
package/src/pathways.js
CHANGED
|
@@ -28,6 +28,7 @@ export const PATHWAYS_LAYER = 'pathways';
|
|
|
28
28
|
|
|
29
29
|
export const PATH_TYPES = [
|
|
30
30
|
{ type: "cns", label: "CNS", colour: "#9B1FC1"},
|
|
31
|
+
{ type: "intracardiac", label: "Local circuit neuron", colour: "#F19E38"},
|
|
31
32
|
{ type: "lcn", label: "Local circuit neuron", colour: "#F19E38"},
|
|
32
33
|
{ type: "para-pre", label: "Parasympathetic pre-ganglionic", colour: "#3F8F4A"},
|
|
33
34
|
{ type: "para-post", label: "Parasympathetic post-ganglionic", colour: "#3F8F4A"},
|
|
@@ -35,7 +36,9 @@ export const PATH_TYPES = [
|
|
|
35
36
|
{ type: "somatic", label: "Somatic lower motor", colour: "#98561D"},
|
|
36
37
|
{ type: "symp-pre", label: "Sympathetic pre-ganglionic", colour: "#EA3423"},
|
|
37
38
|
{ type: "symp-post", label: "Sympathetic post-ganglionic", colour: "#EA3423"},
|
|
38
|
-
{ type: "other", label: "Other neuron type", colour: "#888"}
|
|
39
|
+
{ type: "other", label: "Other neuron type", colour: "#888"},
|
|
40
|
+
{ type: "arterial", label: "Arterial blood vessel", colour: "#F00"},
|
|
41
|
+
{ type: "venous", label: "Venous blood vessel", colour: "#2F6EBA"}
|
|
39
42
|
];
|
|
40
43
|
|
|
41
44
|
export const PATH_STYLE_RULES =
|
package/src/search.js
CHANGED
package/src/styling.js
CHANGED
|
@@ -134,7 +134,8 @@ export class FeatureFillLayer extends VectorStyleLayer
|
|
|
134
134
|
'fill-opacity': [
|
|
135
135
|
'case',
|
|
136
136
|
['boolean', ['feature-state', 'selected'], false], 0.7,
|
|
137
|
-
['has', '
|
|
137
|
+
['has', 'opacity'], ['get', 'opacity'],
|
|
138
|
+
['has', 'colour'], 1.0,
|
|
138
139
|
['boolean', ['feature-state', 'active'], false], 0.7,
|
|
139
140
|
['has', 'node'], 0.3,
|
|
140
141
|
['any',
|
|
@@ -192,6 +193,7 @@ export class FeatureBorderLayer extends VectorStyleLayer
|
|
|
192
193
|
lineColour.push('#000');
|
|
193
194
|
lineColour.push(['has', 'node']);
|
|
194
195
|
lineColour.push('#AFA202');
|
|
196
|
+
// this colour should be complement of background colour...
|
|
195
197
|
lineColour.push('#444');
|
|
196
198
|
|
|
197
199
|
const lineOpacity = [
|
|
@@ -261,7 +263,6 @@ export class FeatureLineLayer extends VectorStyleLayer
|
|
|
261
263
|
:
|
|
262
264
|
[
|
|
263
265
|
'any',
|
|
264
|
-
['has', 'centreline'],
|
|
265
266
|
['==', 'type', 'bezier'],
|
|
266
267
|
['==', 'type', `line`]
|
|
267
268
|
];
|
|
@@ -278,7 +279,6 @@ export class FeatureLineLayer extends VectorStyleLayer
|
|
|
278
279
|
['has', 'colour'], ['get', 'colour'],
|
|
279
280
|
['boolean', ['feature-state', 'active'], false], coloured ? '#888' : '#CCC',
|
|
280
281
|
['==', ['get', 'type'], 'network'], '#AFA202',
|
|
281
|
-
['has', 'centreline'], '#888',
|
|
282
282
|
options.authoring ? '#C44' : '#444'
|
|
283
283
|
],
|
|
284
284
|
'line-opacity': [
|
|
@@ -292,7 +292,6 @@ export class FeatureLineLayer extends VectorStyleLayer
|
|
|
292
292
|
'let',
|
|
293
293
|
'width', [
|
|
294
294
|
'case',
|
|
295
|
-
['has', 'centreline'], 1.2,
|
|
296
295
|
['==', ['get', 'type'], 'network'], 1.2,
|
|
297
296
|
['boolean', ['feature-state', 'selected'], false], 1.2,
|
|
298
297
|
['boolean', ['feature-state', 'active'], false], 1.2,
|
|
@@ -358,6 +357,7 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
358
357
|
[
|
|
359
358
|
'any',
|
|
360
359
|
['==', 'type', 'bezier'],
|
|
360
|
+
['==', 'type', 'centreline'],
|
|
361
361
|
['==', 'type', `line`]
|
|
362
362
|
];
|
|
363
363
|
this.__dashed = dashed;
|
|
@@ -372,7 +372,8 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
372
372
|
['boolean', ['feature-state', 'selected'], false], '#0F0',
|
|
373
373
|
['boolean', ['feature-state', 'hidden'], false], '#CCC',
|
|
374
374
|
['==', ['get', 'type'], 'bezier'], 'red',
|
|
375
|
-
['==', ['get', '
|
|
375
|
+
['==', ['get', 'type'], 'centreline'], '#00F',
|
|
376
|
+
['has', 'error'], '#FFFE0E',
|
|
376
377
|
['==', ['get', 'kind'], 'unknown'], '#888',
|
|
377
378
|
...PATH_STYLE_RULES,
|
|
378
379
|
'#888'
|
|
@@ -384,21 +385,23 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
384
385
|
['boolean', ['get', 'invisible'], false], 0.001,
|
|
385
386
|
['boolean', ['feature-state', 'selected'], false], 1.0,
|
|
386
387
|
['boolean', ['feature-state', 'active'], false], 1.0,
|
|
388
|
+
// Only dim lines when other lines are selected, not if just features selected??
|
|
387
389
|
dimmed ? 0.1 : 0.8
|
|
388
390
|
],
|
|
389
391
|
'line-width': [
|
|
390
392
|
'let',
|
|
391
|
-
'width', [
|
|
393
|
+
'width', ["*", [
|
|
392
394
|
'case',
|
|
393
395
|
['==', ['get', 'type'], 'bezier'], 0.1,
|
|
394
|
-
['
|
|
396
|
+
['has', 'error'], 1,
|
|
395
397
|
['==', ['get', 'kind'], 'unknown'], 1,
|
|
396
398
|
['boolean', ['get', 'invisible'], false], 0.1,
|
|
397
|
-
['boolean', ['feature-state', 'selected'], false],
|
|
398
|
-
['boolean', ['feature-state', 'active'], false],
|
|
399
|
-
0.
|
|
400
|
-
],
|
|
401
|
-
'
|
|
399
|
+
['boolean', ['feature-state', 'selected'], false], 0.6,
|
|
400
|
+
['boolean', ['feature-state', 'active'], false], 0.9,
|
|
401
|
+
0.6
|
|
402
|
+
],
|
|
403
|
+
['case', ['has', 'stroke-width'], ['get', 'stroke-width'], 1.0]],
|
|
404
|
+
['interpolate',
|
|
402
405
|
['exponential', 2],
|
|
403
406
|
['zoom'],
|
|
404
407
|
2, ["*", ['var', 'width'], ["^", 2, -0.5]],
|
|
@@ -408,7 +411,7 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
408
411
|
]
|
|
409
412
|
};
|
|
410
413
|
if (this.__dashed) {
|
|
411
|
-
paintStyle['line-dasharray'] = [
|
|
414
|
+
paintStyle['line-dasharray'] = [1, 1];
|
|
412
415
|
}
|
|
413
416
|
return super.changedPaintStyle(paintStyle, changes);
|
|
414
417
|
}
|