@abi-software/flatmap-viewer 2.3.0-b.1 → 2.3.0-b.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.rst +1 -1
- package/package.json +3 -5
- package/src/annotation.js +0 -6
- package/src/{controls.js → controls/controls.js} +1 -8
- package/src/{info.js → controls/info.js} +2 -6
- package/src/{minimap.js → controls/minimap.js} +1 -5
- package/src/controls/search.js +113 -0
- package/src/controls/systems.js +66 -0
- package/src/flatmap-viewer.js +4 -2
- package/src/interactions.js +59 -59
- package/src/main.js +2 -1
- package/src/search.js +0 -93
- package/src/styling.js +46 -27
- package/src/systems.js +57 -40
- /package/src/{newcontrols.js → controls/newcontrols.js} +0 -0
package/README.rst
CHANGED
|
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
|
|
|
38
38
|
Package Installation
|
|
39
39
|
====================
|
|
40
40
|
|
|
41
|
-
* ``npm install @abi-software/flatmap-viewer@2.3.0-b.
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.3.0-b.2``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abi-software/flatmap-viewer",
|
|
3
|
-
"version": "2.3.0-b.
|
|
3
|
+
"version": "2.3.0-b.2",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
|
|
6
6
|
"main": "src/main.js",
|
|
@@ -38,14 +38,12 @@
|
|
|
38
38
|
"browser-sync": "^2.26.7",
|
|
39
39
|
"bs-fullscreen-message": "^1.1.0",
|
|
40
40
|
"clean-webpack-plugin": "^3.0.0",
|
|
41
|
-
"css-loader": "^6.
|
|
41
|
+
"css-loader": "^6.7.3",
|
|
42
42
|
"eslint": "^8.7.0",
|
|
43
43
|
"express": "^4.17.1",
|
|
44
|
-
"file-loader": "^6.2.0",
|
|
45
44
|
"html-webpack-plugin": "^4.5.2",
|
|
46
45
|
"strip-ansi": "^7.0.1",
|
|
47
|
-
"style-loader": "^
|
|
48
|
-
"url-loader": "^4.1.0",
|
|
46
|
+
"style-loader": "^3.3.2",
|
|
49
47
|
"webpack": "^5.16.0",
|
|
50
48
|
"webpack-cli": "^4.4.0",
|
|
51
49
|
"webpack-dev-middleware": "^4.1.0",
|
package/src/annotation.js
CHANGED
|
@@ -116,12 +116,6 @@ export class Annotator
|
|
|
116
116
|
async __authorise(panel)
|
|
117
117
|
//======================
|
|
118
118
|
{
|
|
119
|
-
/*
|
|
120
|
-
const testUser = {name: 'Testing...'};
|
|
121
|
-
this.__setUser(testUser);
|
|
122
|
-
this.__authorised = true;
|
|
123
|
-
return Promise.resolve(testUser);
|
|
124
|
-
*/
|
|
125
119
|
const abortController = new AbortController();
|
|
126
120
|
setTimeout((panel) => {
|
|
127
121
|
if (this.user === 'undefined') {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Flatmap viewer and annotation tool
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2019
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -18,13 +18,6 @@ limitations under the License.
|
|
|
18
18
|
|
|
19
19
|
******************************************************************************/
|
|
20
20
|
|
|
21
|
-
'use strict';
|
|
22
|
-
|
|
23
|
-
//==============================================================================
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//==============================================================================
|
|
27
|
-
|
|
28
21
|
// Make sure colour string is in `#rrggbb` form.
|
|
29
22
|
// Based on https://stackoverflow.com/a/47355187
|
|
30
23
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Flatmap viewer and annotation tool
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2019
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -18,11 +18,7 @@ limitations under the License.
|
|
|
18
18
|
|
|
19
19
|
******************************************************************************/
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
//==============================================================================
|
|
24
|
-
|
|
25
|
-
import { indexedProperties } from './search.js';
|
|
21
|
+
import { indexedProperties } from '../search.js';
|
|
26
22
|
|
|
27
23
|
//==============================================================================
|
|
28
24
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Flatmap viewer and annotation tool
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2019
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -45,10 +45,6 @@ limitations under the License.
|
|
|
45
45
|
|
|
46
46
|
//==============================================================================
|
|
47
47
|
|
|
48
|
-
'use strict';
|
|
49
|
-
|
|
50
|
-
//==============================================================================
|
|
51
|
-
|
|
52
48
|
import maplibre from 'maplibre-gl';
|
|
53
49
|
|
|
54
50
|
//==============================================================================
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 - 2023 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
|
+
|
|
22
|
+
export class SearchControl
|
|
23
|
+
{
|
|
24
|
+
constructor(flatmap)
|
|
25
|
+
{
|
|
26
|
+
this.__flatmap = flatmap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onAdd(map)
|
|
30
|
+
//========
|
|
31
|
+
{
|
|
32
|
+
this._map = map;
|
|
33
|
+
this._container = document.createElement('div');
|
|
34
|
+
this._container.className = 'maplibregl-ctrl search-control';
|
|
35
|
+
|
|
36
|
+
this._input = document.createElement('input');
|
|
37
|
+
this._input.id = 'search-control-input';
|
|
38
|
+
this._input.setAttribute('type', 'search');
|
|
39
|
+
this._input.setAttribute('visible', 'false');
|
|
40
|
+
this._input.setAttribute('placeholder', 'Search...');
|
|
41
|
+
|
|
42
|
+
this._button = document.createElement('button');
|
|
43
|
+
this._button.id = 'search-control-button';
|
|
44
|
+
this._button.className = 'control-button';
|
|
45
|
+
this._button.title = 'Search flatmap';
|
|
46
|
+
this._button.setAttribute('type', 'button');
|
|
47
|
+
this._button.setAttribute('aria-label', 'Search flatmap');
|
|
48
|
+
// https://iconmonstr.com/magnifier-6-svg/
|
|
49
|
+
this._button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" id="search-control-icon" viewBox="0 0 24 24">
|
|
50
|
+
<path d="M21.172 24l-7.387-7.387c-1.388.874-3.024 1.387-4.785 1.387-4.971 0-9-4.029-9-9s4.029-9 9-9 9 4.029 9 9c0 1.761-.514 3.398-1.387 4.785l7.387 7.387-2.828 2.828zm-12.172-8c3.859 0 7-3.14 7-7s-3.141-7-7-7-7 3.14-7 7 3.141 7 7 7z"/>
|
|
51
|
+
</svg>`;
|
|
52
|
+
this._container.appendChild(this._button);
|
|
53
|
+
|
|
54
|
+
this._container.onclick = this.onClick_.bind(this);
|
|
55
|
+
return this._container;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getDefaultPosition()
|
|
59
|
+
//==================
|
|
60
|
+
{
|
|
61
|
+
return 'top-right';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onRemove()
|
|
65
|
+
//========
|
|
66
|
+
{
|
|
67
|
+
this._container.parentNode.removeChild(this._container);
|
|
68
|
+
this._map = undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
searchMap_(search=true)
|
|
72
|
+
//=====================
|
|
73
|
+
{
|
|
74
|
+
this._input = this._container.removeChild(this._input);
|
|
75
|
+
this._input.setAttribute('visible', 'false');
|
|
76
|
+
const text = this._input.value;
|
|
77
|
+
if (search && text !== '') {
|
|
78
|
+
const results = this.__flatmap.search(text);
|
|
79
|
+
this.__flatmap.showSearchResults(results);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onKeyDown_(e)
|
|
84
|
+
//===========
|
|
85
|
+
{
|
|
86
|
+
if (e.key === 'Enter') {
|
|
87
|
+
this.searchMap_();
|
|
88
|
+
} else if (e.key === 'Escape') {
|
|
89
|
+
this.searchMap_(false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onClick_(e)
|
|
94
|
+
//=========
|
|
95
|
+
{
|
|
96
|
+
const targetId = ('rangeTarget' in e) ? e.rangeTarget.id : e.target.id; // FF has rangeTarget
|
|
97
|
+
if (['search-control-button', 'search-control-icon'].includes(targetId)) {
|
|
98
|
+
if (this._input.getAttribute('visible') === 'false') {
|
|
99
|
+
this._container.appendChild(this._input);
|
|
100
|
+
this._container.appendChild(this._button);
|
|
101
|
+
this._input.setAttribute('visible', 'true');
|
|
102
|
+
this._input.onkeydown = this.onKeyDown_.bind(this);
|
|
103
|
+
this._input.value = '';
|
|
104
|
+
this.__flatmap.clearSearchResults();
|
|
105
|
+
this._input.focus();
|
|
106
|
+
} else {
|
|
107
|
+
this.searchMap_();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//==============================================================================
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 - 2023 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
|
+
|
|
22
|
+
import { Control } from './controls';
|
|
23
|
+
|
|
24
|
+
//==============================================================================
|
|
25
|
+
|
|
26
|
+
export class SystemsControl extends Control
|
|
27
|
+
{
|
|
28
|
+
constructor(flatmap, systems)
|
|
29
|
+
{
|
|
30
|
+
super(flatmap, 'system', 'systems');
|
|
31
|
+
this.__systems = systems;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
__innerLinesHTML()
|
|
35
|
+
//================
|
|
36
|
+
{
|
|
37
|
+
const html = [];
|
|
38
|
+
for (const system of this.__systems) {
|
|
39
|
+
html.push(`<label for="${this.__prefix}${system.id}" style="background: ${system.colour};">${system.name}</label><input id="${this.__prefix}${system.id}" type="checkbox" checked/>`);
|
|
40
|
+
}
|
|
41
|
+
return html;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
__enableAll(enable)
|
|
45
|
+
//=================
|
|
46
|
+
{
|
|
47
|
+
for (const system of this.__systems) {
|
|
48
|
+
const checkbox = document.getElementById(`${this.__prefix}${system.id}`);
|
|
49
|
+
if (checkbox) {
|
|
50
|
+
checkbox.checked = enable;
|
|
51
|
+
this.__flatmap.enableSystem(system.name, enable);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
__enableControl(id, enable)
|
|
57
|
+
//=========================
|
|
58
|
+
{
|
|
59
|
+
for (const system of this.__systems) {
|
|
60
|
+
if (id === system.id) {
|
|
61
|
+
this.__flatmap.enableSystem(system.name, enable);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -34,11 +34,12 @@ import '../static/css/flatmap-viewer.css';
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
36
|
import {MapServer} from './mapserver.js';
|
|
37
|
-
import {MinimapControl} from './minimap.js';
|
|
38
|
-
import {NavigationControl} from './controls.js';
|
|
39
37
|
import {SearchIndex} from './search.js';
|
|
40
38
|
import {UserInteractions} from './interactions.js';
|
|
41
39
|
|
|
40
|
+
import {MinimapControl} from './controls/minimap.js';
|
|
41
|
+
import {NavigationControl} from './controls/controls.js';
|
|
42
|
+
|
|
42
43
|
import * as images from './images.js';
|
|
43
44
|
import * as utils from './utils.js';
|
|
44
45
|
|
|
@@ -1301,6 +1302,7 @@ export class MapManager
|
|
|
1301
1302
|
* @arg options.showPosition {boolean} Show ``position`` of tooltip.
|
|
1302
1303
|
* @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
|
|
1303
1304
|
* another application so show a number of controls. Defaults to ``false``.
|
|
1305
|
+
* @arg options.annotator {boolean} Allow interactive annotation of features and paths.
|
|
1304
1306
|
* @example
|
|
1305
1307
|
* const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
|
|
1306
1308
|
*
|
package/src/interactions.js
CHANGED
|
@@ -34,14 +34,16 @@ import polylabel from 'polylabel';
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
36
|
import {Annotator} from './annotation';
|
|
37
|
-
import {displayedProperties, InfoControl} from './info';
|
|
38
37
|
import {LayerManager} from './layers';
|
|
39
38
|
import {PATHWAYS_LAYER, Pathways} from './pathways';
|
|
39
|
+
import {COLOUR_ERROR, VECTOR_TILES_SOURCE} from './styling';
|
|
40
|
+
import {SystemsManager} from './systems';
|
|
41
|
+
|
|
42
|
+
import {displayedProperties, InfoControl} from './controls/info';
|
|
40
43
|
import {BackgroundControl, LayerControl, NerveControl,
|
|
41
|
-
PathControl, SCKANControl} from './controls';
|
|
42
|
-
import {SearchControl} from './search';
|
|
43
|
-
import {
|
|
44
|
-
import {SystemsControl, SystemsManager} from './systems';
|
|
44
|
+
PathControl, SCKANControl} from './controls/controls';
|
|
45
|
+
import {SearchControl} from './controls/search';
|
|
46
|
+
import {SystemsControl} from './controls/systems';
|
|
45
47
|
|
|
46
48
|
import * as pathways from './pathways';
|
|
47
49
|
import * as utils from './utils';
|
|
@@ -144,25 +146,16 @@ export class UserInteractions
|
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
// Add annotation capability
|
|
150
|
+
if (flatmap.options.annotator) {
|
|
151
|
+
this.__setupAnnotation();
|
|
152
|
+
} else {
|
|
153
|
+
this.__annotator = null;
|
|
154
|
+
}
|
|
149
155
|
|
|
150
156
|
// Note features that are FC systems
|
|
151
157
|
|
|
152
|
-
this.
|
|
153
|
-
for (const [id, ann] of this._flatmap.annotations) {
|
|
154
|
-
if (ann['fc-class'] === 'fc-class:System') {
|
|
155
|
-
if (this.__systems.has(ann.name)) {
|
|
156
|
-
this.__systems.get(ann.name).featureIds.push(ann.featureId)
|
|
157
|
-
} else {
|
|
158
|
-
this.__systems.set(ann.name, {
|
|
159
|
-
id: ann.name.replaceAll(' ', '_'),
|
|
160
|
-
colour: ann.colour,
|
|
161
|
-
featureIds: [ ann.featureId ]
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
158
|
+
this.__systemsManager = new SystemsManager(this._flatmap, this);
|
|
166
159
|
|
|
167
160
|
// Add various controls when running standalone
|
|
168
161
|
|
|
@@ -191,7 +184,7 @@ export class UserInteractions
|
|
|
191
184
|
|
|
192
185
|
// SCKAN path and SYSTEMS controls for FC maps
|
|
193
186
|
if (flatmap.options.style === 'functional') {
|
|
194
|
-
this._map.addControl(new SystemsControl(flatmap, this.
|
|
187
|
+
this._map.addControl(new SystemsControl(flatmap, this.__systemsManager.systems));
|
|
195
188
|
this._map.addControl(new SCKANControl(flatmap, flatmap.options.layerOptions));
|
|
196
189
|
}
|
|
197
190
|
}
|
|
@@ -258,10 +251,10 @@ export class UserInteractions
|
|
|
258
251
|
const annotated_features = await this.__annotator.annotated_features();
|
|
259
252
|
|
|
260
253
|
// Flag features that have annotations
|
|
261
|
-
|
|
254
|
+
this.__featureIdToMapId = new Map();
|
|
262
255
|
for (const [mapId, ann] of this._flatmap.annotations) {
|
|
263
256
|
this.__featureIdToMapId.set(ann.id, mapId);
|
|
264
|
-
const feature = this.
|
|
257
|
+
const feature = this.mapFeature(mapId);
|
|
265
258
|
if (feature !== undefined) {
|
|
266
259
|
this._map.setFeatureState(feature, { 'map-annotation': true });
|
|
267
260
|
if (annotated_features.indexOf(ann.id) >= 0) {
|
|
@@ -274,11 +267,13 @@ export class UserInteractions
|
|
|
274
267
|
setFeatureAnnotated(featureId)
|
|
275
268
|
//============================
|
|
276
269
|
{
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
270
|
+
if (this.__annotator) {
|
|
271
|
+
// featureId v's geoJSON id
|
|
272
|
+
const mapId = this.__featureIdToMapId.get(featureId);
|
|
273
|
+
const feature = this.mapFeature(mapId);
|
|
274
|
+
if (feature !== undefined) {
|
|
275
|
+
this._map.setFeatureState(feature, { 'annotated': true });
|
|
276
|
+
}
|
|
282
277
|
}
|
|
283
278
|
}
|
|
284
279
|
|
|
@@ -304,34 +299,35 @@ export class UserInteractions
|
|
|
304
299
|
getSystems()
|
|
305
300
|
//==========
|
|
306
301
|
{
|
|
307
|
-
|
|
308
|
-
for (const system of this.__systems.values()) {
|
|
309
|
-
systems.push({
|
|
310
|
-
name: system.name,
|
|
311
|
-
colour: system.colour,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
return systems;
|
|
302
|
+
return this.__systemsManager.systems;
|
|
315
303
|
}
|
|
316
304
|
|
|
317
305
|
enableSystem(systemName, enable=true)
|
|
318
306
|
//===================================
|
|
319
307
|
{
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
308
|
+
this.__systemsManager.enable(systemName, enable);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
enableFeatureWithChildren(featureId, enable=true)
|
|
312
|
+
//===============================================
|
|
313
|
+
{
|
|
314
|
+
const feature = this.mapFeature(featureId);
|
|
315
|
+
if (feature !== undefined) {
|
|
316
|
+
this.enableFeature(feature, enable);
|
|
317
|
+
for (const childFeatureId of feature.children) {
|
|
318
|
+
this.enableFeatureWithChildren(childFeatureId, enable);
|
|
323
319
|
}
|
|
324
320
|
}
|
|
325
321
|
}
|
|
326
322
|
|
|
327
|
-
|
|
328
|
-
|
|
323
|
+
__enableFeatureWithParents(featureId, enable=true)
|
|
324
|
+
//================================================
|
|
329
325
|
{
|
|
330
|
-
const feature = this.
|
|
326
|
+
const feature = this.mapFeature(featureId);
|
|
331
327
|
if (feature !== undefined) {
|
|
332
|
-
this.
|
|
333
|
-
for (const childFeatureId of feature.
|
|
334
|
-
this.
|
|
328
|
+
this.enableFeature(feature, enable);
|
|
329
|
+
for (const childFeatureId of feature.parents) {
|
|
330
|
+
this.__enableFeatureWithParents(childFeatureId, enable);
|
|
335
331
|
}
|
|
336
332
|
}
|
|
337
333
|
}
|
|
@@ -348,8 +344,8 @@ export class UserInteractions
|
|
|
348
344
|
}
|
|
349
345
|
}
|
|
350
346
|
|
|
351
|
-
|
|
352
|
-
|
|
347
|
+
enableFeature(feature, enable=true)
|
|
348
|
+
//=================================
|
|
353
349
|
{
|
|
354
350
|
if (feature !== undefined) {
|
|
355
351
|
if (enable) {
|
|
@@ -361,8 +357,8 @@ export class UserInteractions
|
|
|
361
357
|
}
|
|
362
358
|
}
|
|
363
359
|
|
|
364
|
-
|
|
365
|
-
|
|
360
|
+
mapFeature(featureId)
|
|
361
|
+
//===================
|
|
366
362
|
{
|
|
367
363
|
const ann = this._flatmap.annotation(featureId);
|
|
368
364
|
if (ann !== undefined) {
|
|
@@ -394,7 +390,7 @@ export class UserInteractions
|
|
|
394
390
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
395
391
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
396
392
|
} else {
|
|
397
|
-
const feature = this.
|
|
393
|
+
const feature = this.mapFeature(featureId);
|
|
398
394
|
if (feature !== undefined) {
|
|
399
395
|
this._map.setFeatureState(feature, { 'selected': true });
|
|
400
396
|
this._selectedFeatureIds.set(featureId, 1);
|
|
@@ -411,7 +407,7 @@ export class UserInteractions
|
|
|
411
407
|
if (references > 1) {
|
|
412
408
|
this._selectedFeatureIds.set(featureId, references - 1);
|
|
413
409
|
} else {
|
|
414
|
-
const feature = this.
|
|
410
|
+
const feature = this.mapFeature(featureId);
|
|
415
411
|
if (feature !== undefined) {
|
|
416
412
|
this._map.removeFeatureState(feature, 'selected');
|
|
417
413
|
this._selectedFeatureIds.delete(+featureId);
|
|
@@ -427,7 +423,7 @@ export class UserInteractions
|
|
|
427
423
|
//==================
|
|
428
424
|
{
|
|
429
425
|
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
430
|
-
const feature = this.
|
|
426
|
+
const feature = this.mapFeature(featureId);
|
|
431
427
|
if (feature !== undefined) {
|
|
432
428
|
this._map.removeFeatureState(feature, 'selected');
|
|
433
429
|
}
|
|
@@ -457,7 +453,7 @@ export class UserInteractions
|
|
|
457
453
|
//==========================
|
|
458
454
|
{
|
|
459
455
|
featureId = +featureId; // Ensure numeric
|
|
460
|
-
this.__activateFeature(this.
|
|
456
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
461
457
|
}
|
|
462
458
|
|
|
463
459
|
unhighlightFeatures_()
|
|
@@ -833,7 +829,7 @@ export class UserInteractions
|
|
|
833
829
|
const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
|
|
834
830
|
for (const featureId of this._pathways.lineFeatureIds(lineIds)) {
|
|
835
831
|
if (+featureId !== lineFeatureId) {
|
|
836
|
-
this.__activateFeature(this.
|
|
832
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
837
833
|
}
|
|
838
834
|
}
|
|
839
835
|
}
|
|
@@ -957,6 +953,10 @@ export class UserInteractions
|
|
|
957
953
|
__annotationEvent(feature)
|
|
958
954
|
//========================
|
|
959
955
|
{
|
|
956
|
+
if (!this.__annotator) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
960
|
event.preventDefault();
|
|
961
961
|
|
|
962
962
|
// Remove any tooltip
|
|
@@ -1016,12 +1016,12 @@ export class UserInteractions
|
|
|
1016
1016
|
{
|
|
1017
1017
|
if ('nerveId' in feature.properties) {
|
|
1018
1018
|
for (const featureId of this._pathways.nerveFeatureIds(feature.properties.nerveId)) {
|
|
1019
|
-
this.__activateFeature(this.
|
|
1019
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
1022
|
if ('nodeId' in feature.properties) {
|
|
1023
1023
|
for (const featureId of this._pathways.nodeFeatureIds(feature.properties.nodeId)) {
|
|
1024
|
-
this.__activateFeature(this.
|
|
1024
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
1027
|
}
|
|
@@ -1046,7 +1046,7 @@ export class UserInteractions
|
|
|
1046
1046
|
//=====================================
|
|
1047
1047
|
{
|
|
1048
1048
|
for (const featureId of featureIds) {
|
|
1049
|
-
const feature = this.
|
|
1049
|
+
const feature = this.mapFeature(featureId);
|
|
1050
1050
|
if (feature !== undefined) {
|
|
1051
1051
|
if (enable) {
|
|
1052
1052
|
this._map.removeFeatureState(feature, 'hidden');
|
|
@@ -1257,7 +1257,7 @@ export class UserInteractions
|
|
|
1257
1257
|
const markerId = this.__markerIdByMarker.get(marker);
|
|
1258
1258
|
const annotation = this.__annotationByMarkerId.get(markerId);
|
|
1259
1259
|
// The marker's feature
|
|
1260
|
-
const feature = this.
|
|
1260
|
+
const feature = this.mapFeature(annotation.featureId);
|
|
1261
1261
|
if (feature !== undefined) {
|
|
1262
1262
|
if (event.type === 'mouseenter') {
|
|
1263
1263
|
// Highlight on mouse enter
|
package/src/main.js
CHANGED
package/src/search.js
CHANGED
|
@@ -36,99 +36,6 @@ export const indexedProperties = [
|
|
|
36
36
|
|
|
37
37
|
//==============================================================================
|
|
38
38
|
|
|
39
|
-
export class SearchControl
|
|
40
|
-
{
|
|
41
|
-
constructor(flatmap)
|
|
42
|
-
{
|
|
43
|
-
this.__flatmap = flatmap;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
onAdd(map)
|
|
47
|
-
//========
|
|
48
|
-
{
|
|
49
|
-
this._map = map;
|
|
50
|
-
this._container = document.createElement('div');
|
|
51
|
-
this._container.className = 'maplibregl-ctrl search-control';
|
|
52
|
-
|
|
53
|
-
this._input = document.createElement('input');
|
|
54
|
-
this._input.id = 'search-control-input';
|
|
55
|
-
this._input.setAttribute('type', 'search');
|
|
56
|
-
this._input.setAttribute('visible', 'false');
|
|
57
|
-
this._input.setAttribute('placeholder', 'Search...');
|
|
58
|
-
|
|
59
|
-
this._button = document.createElement('button');
|
|
60
|
-
this._button.id = 'search-control-button';
|
|
61
|
-
this._button.className = 'control-button';
|
|
62
|
-
this._button.title = 'Search flatmap';
|
|
63
|
-
this._button.setAttribute('type', 'button');
|
|
64
|
-
this._button.setAttribute('aria-label', 'Search flatmap');
|
|
65
|
-
// https://iconmonstr.com/magnifier-6-svg/
|
|
66
|
-
this._button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" id="search-control-icon" viewBox="0 0 24 24">
|
|
67
|
-
<path d="M21.172 24l-7.387-7.387c-1.388.874-3.024 1.387-4.785 1.387-4.971 0-9-4.029-9-9s4.029-9 9-9 9 4.029 9 9c0 1.761-.514 3.398-1.387 4.785l7.387 7.387-2.828 2.828zm-12.172-8c3.859 0 7-3.14 7-7s-3.141-7-7-7-7 3.14-7 7 3.141 7 7 7z"/>
|
|
68
|
-
</svg>`;
|
|
69
|
-
this._container.appendChild(this._button);
|
|
70
|
-
|
|
71
|
-
this._container.onclick = this.onClick_.bind(this);
|
|
72
|
-
return this._container;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
getDefaultPosition()
|
|
76
|
-
//==================
|
|
77
|
-
{
|
|
78
|
-
return 'top-right';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
onRemove()
|
|
82
|
-
//========
|
|
83
|
-
{
|
|
84
|
-
this._container.parentNode.removeChild(this._container);
|
|
85
|
-
this._map = undefined;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
searchMap_(search=true)
|
|
89
|
-
//=====================
|
|
90
|
-
{
|
|
91
|
-
this._input = this._container.removeChild(this._input);
|
|
92
|
-
this._input.setAttribute('visible', 'false');
|
|
93
|
-
const text = this._input.value;
|
|
94
|
-
if (search && text !== '') {
|
|
95
|
-
const results = this.__flatmap.search(text);
|
|
96
|
-
this.__flatmap.showSearchResults(results);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
onKeyDown_(e)
|
|
101
|
-
//===========
|
|
102
|
-
{
|
|
103
|
-
if (e.key === 'Enter') {
|
|
104
|
-
this.searchMap_();
|
|
105
|
-
} else if (e.key === 'Escape') {
|
|
106
|
-
this.searchMap_(false);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
onClick_(e)
|
|
111
|
-
//=========
|
|
112
|
-
{
|
|
113
|
-
const targetId = ('rangeTarget' in e) ? e.rangeTarget.id : e.target.id; // FF has rangeTarget
|
|
114
|
-
if (['search-control-button', 'search-control-icon'].includes(targetId)) {
|
|
115
|
-
if (this._input.getAttribute('visible') === 'false') {
|
|
116
|
-
this._container.appendChild(this._input);
|
|
117
|
-
this._container.appendChild(this._button);
|
|
118
|
-
this._input.setAttribute('visible', 'true');
|
|
119
|
-
this._input.onkeydown = this.onKeyDown_.bind(this);
|
|
120
|
-
this._input.value = '';
|
|
121
|
-
this.__flatmap.clearSearchResults();
|
|
122
|
-
this._input.focus();
|
|
123
|
-
} else {
|
|
124
|
-
this.searchMap_();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
//==============================================================================
|
|
131
|
-
|
|
132
39
|
export class SearchIndex
|
|
133
40
|
{
|
|
134
41
|
constructor()
|
package/src/styling.js
CHANGED
|
@@ -135,19 +135,21 @@ export class FeatureFillLayer extends VectorStyleLayer
|
|
|
135
135
|
|
|
136
136
|
paintStyle(options, changes=false)
|
|
137
137
|
{
|
|
138
|
+
const ghostColour = '#D8D8D8'; // Function of BG colour?
|
|
138
139
|
const coloured = !('colour' in options) || options.colour;
|
|
139
140
|
const dimmed = 'dimmed' in options && options.dimmed;
|
|
140
141
|
const paintStyle = {
|
|
141
142
|
'fill-color': [
|
|
142
143
|
'case',
|
|
143
144
|
['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
|
|
145
|
+
['boolean', ['feature-state', 'hidden'], false], ghostColour,
|
|
144
146
|
['has', 'colour'], ['get', 'colour'],
|
|
145
147
|
['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
|
|
146
148
|
'white' // background colour? body colour ??
|
|
147
149
|
],
|
|
148
150
|
'fill-opacity': [
|
|
149
151
|
'case',
|
|
150
|
-
['boolean', ['feature-state', 'hidden'], false], 0.
|
|
152
|
+
['boolean', ['feature-state', 'hidden'], false], 0.1,
|
|
151
153
|
['boolean', ['feature-state', 'selected'], false], 0.7,
|
|
152
154
|
['has', 'opacity'], ['get', 'opacity'],
|
|
153
155
|
['has', 'colour'], 1.0,
|
|
@@ -358,6 +360,38 @@ export class FeatureDashLineLayer extends FeatureLineLayer
|
|
|
358
360
|
|
|
359
361
|
//==============================================================================
|
|
360
362
|
|
|
363
|
+
function sckanFilter(options)
|
|
364
|
+
{
|
|
365
|
+
const sckanState = !'sckan' in options ? 'all'
|
|
366
|
+
: options.sckan.toLowerCase();
|
|
367
|
+
const sckanFilter =
|
|
368
|
+
sckanState == 'none' ? [
|
|
369
|
+
['!has', 'sckan']
|
|
370
|
+
] :
|
|
371
|
+
sckanState == 'valid' ? [[
|
|
372
|
+
'any',
|
|
373
|
+
['!has', 'sckan'],
|
|
374
|
+
[
|
|
375
|
+
'all',
|
|
376
|
+
['has', 'sckan'],
|
|
377
|
+
['==', 'sckan', true]
|
|
378
|
+
]
|
|
379
|
+
]] :
|
|
380
|
+
sckanState == 'invalid' ? [[
|
|
381
|
+
'any',
|
|
382
|
+
['!has', 'sckan'],
|
|
383
|
+
[
|
|
384
|
+
'all',
|
|
385
|
+
['has', 'sckan'],
|
|
386
|
+
['!=', 'sckan', true]
|
|
387
|
+
]
|
|
388
|
+
]] :
|
|
389
|
+
[ ];
|
|
390
|
+
return sckanFilter;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//==============================================================================
|
|
394
|
+
|
|
361
395
|
export class AnnotatedPathLayer extends VectorStyleLayer
|
|
362
396
|
{
|
|
363
397
|
constructor(id, sourceLayer)
|
|
@@ -365,6 +399,15 @@ export class AnnotatedPathLayer extends VectorStyleLayer
|
|
|
365
399
|
super(id, 'annotated-path', sourceLayer);
|
|
366
400
|
}
|
|
367
401
|
|
|
402
|
+
makeFilter(options={})
|
|
403
|
+
{
|
|
404
|
+
return [
|
|
405
|
+
'all',
|
|
406
|
+
['==', '$type', 'LineString'],
|
|
407
|
+
...sckanFilter(options)
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
|
|
368
411
|
paintStyle(options={}, changes=false)
|
|
369
412
|
{
|
|
370
413
|
const dimmed = 'dimmed' in options && options.dimmed;
|
|
@@ -404,7 +447,7 @@ export class AnnotatedPathLayer extends VectorStyleLayer
|
|
|
404
447
|
return {
|
|
405
448
|
...super.style(),
|
|
406
449
|
'type': 'line',
|
|
407
|
-
'filter':
|
|
450
|
+
'filter': this.makeFilter(options),
|
|
408
451
|
'paint': this.paintStyle(options),
|
|
409
452
|
'layout': {
|
|
410
453
|
'line-cap': 'square'
|
|
@@ -427,31 +470,7 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
427
470
|
|
|
428
471
|
makeFilter(options={})
|
|
429
472
|
{
|
|
430
|
-
const
|
|
431
|
-
: options.sckan.toLowerCase();
|
|
432
|
-
const sckan_filter =
|
|
433
|
-
sckanState == 'none' ? [
|
|
434
|
-
['!has', 'sckan']
|
|
435
|
-
] :
|
|
436
|
-
sckanState == 'valid' ? [[
|
|
437
|
-
'any',
|
|
438
|
-
['!has', 'sckan'],
|
|
439
|
-
[
|
|
440
|
-
'all',
|
|
441
|
-
['has', 'sckan'],
|
|
442
|
-
['==', 'sckan', true]
|
|
443
|
-
]
|
|
444
|
-
]] :
|
|
445
|
-
sckanState == 'invalid' ? [[
|
|
446
|
-
'any',
|
|
447
|
-
['!has', 'sckan'],
|
|
448
|
-
[
|
|
449
|
-
'all',
|
|
450
|
-
['has', 'sckan'],
|
|
451
|
-
['!=', 'sckan', true]
|
|
452
|
-
]
|
|
453
|
-
]] :
|
|
454
|
-
[ ];
|
|
473
|
+
const sckan_filter = sckanFilter(options);
|
|
455
474
|
|
|
456
475
|
return this.__dashed ? [
|
|
457
476
|
'all',
|
package/src/systems.js
CHANGED
|
@@ -16,61 +16,78 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
16
16
|
See the License for the specific language governing permissions and
|
|
17
17
|
limitations under the License.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import { Control } from './controls';
|
|
23
|
-
|
|
19
|
+
**/
|
|
24
20
|
//==============================================================================
|
|
25
21
|
|
|
26
22
|
export class SystemsManager
|
|
27
23
|
{
|
|
28
|
-
constructor()
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
//==============================================================================
|
|
35
|
-
|
|
36
|
-
export class SystemsControl extends Control
|
|
37
|
-
{
|
|
38
|
-
constructor(flatmap, systems)
|
|
24
|
+
constructor(flatmap, ui, enabled=true)
|
|
39
25
|
{
|
|
40
|
-
|
|
41
|
-
this.__systems =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
this.__ui = ui;
|
|
27
|
+
this.__systems = new Map();
|
|
28
|
+
this.__enabledChildren = new Map();
|
|
29
|
+
for (const [id, ann] of flatmap.annotations) {
|
|
30
|
+
if (ann['fc-class'] === 'fc-class:System') {
|
|
31
|
+
if (this.__systems.has(ann.name)) {
|
|
32
|
+
this.__systems.get(ann.name).featureIds.push(ann.featureId)
|
|
33
|
+
} else {
|
|
34
|
+
this.__systems.set(ann.name, {
|
|
35
|
+
id: ann.name.replaceAll(' ', '_'),
|
|
36
|
+
colour: ann.colour,
|
|
37
|
+
featureIds: [ ann.featureId ],
|
|
38
|
+
enabled: enabled
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
for (const childId of ann['children']) {
|
|
42
|
+
if (enabled) {
|
|
43
|
+
const enabledCount = (this.__enabledChildren.has(childId))
|
|
44
|
+
? this.__enabledChildren.get(childId)
|
|
45
|
+
: 0;
|
|
46
|
+
this.__enabledChildren.set(childId, enabledCount + 1);
|
|
47
|
+
} else {
|
|
48
|
+
this.__enabledChildren.set(childId, 0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
50
52
|
}
|
|
51
|
-
return html;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
get systems()
|
|
56
|
+
//===========
|
|
56
57
|
{
|
|
58
|
+
const systems = [];
|
|
57
59
|
for (const [name, system] of this.__systems.entries()) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
systems.push({
|
|
61
|
+
name: name,
|
|
62
|
+
id: system.id,
|
|
63
|
+
colour: system.colour,
|
|
64
|
+
enabled: system.enabled
|
|
65
|
+
});
|
|
63
66
|
}
|
|
67
|
+
return systems;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
enable(systemName, enable=true)
|
|
71
|
+
//=============================
|
|
68
72
|
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
const system = this.__systems.get(systemName);
|
|
74
|
+
if (system !== undefined && enable !== system.enabled) {
|
|
75
|
+
for (const featureId of system.featureIds) {
|
|
76
|
+
const feature = this.__ui.mapFeature(featureId);
|
|
77
|
+
if (feature !== undefined) {
|
|
78
|
+
this.__ui.enableFeature(feature, enable);
|
|
79
|
+
for (const childFeatureId of feature.children) {
|
|
80
|
+
const enabledCount = this.__enabledChildren.get(childFeatureId);
|
|
81
|
+
if (enable && enabledCount === 0 || !enable && enabledCount == 1) {
|
|
82
|
+
this.__ui.enableFeatureWithChildren(childFeatureId, enable);
|
|
83
|
+
}
|
|
84
|
+
this.__enabledChildren.set(childFeatureId, enabledCount + (enable ? 1 : -1));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
72
87
|
}
|
|
88
|
+
system.enabled = enable;
|
|
73
89
|
}
|
|
74
90
|
}
|
|
75
|
-
|
|
76
91
|
}
|
|
92
|
+
|
|
93
|
+
//==============================================================================
|
|
File without changes
|