@abi-software/flatmap-viewer 2.2.10-devel.2 → 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 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-devel.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",
package/src/controls.js CHANGED
@@ -22,7 +22,8 @@ limitations under the License.
22
22
 
23
23
  //==============================================================================
24
24
 
25
- import * as pathways from './pathways.js';
25
+ // Needed for Webpack
26
+ import zoomInButton from '../static/images/zoom-in-button.png'
26
27
 
27
28
  //==============================================================================
28
29
 
@@ -91,10 +92,11 @@ export class NavigationControl
91
92
 
92
93
  export class PathControl
93
94
  {
94
- constructor(flatmap)
95
+ constructor(flatmap, pathTypes)
95
96
  {
96
97
  this._flatmap = flatmap;
97
98
  this._map = undefined;
99
+ this.__pathTypes = pathTypes;
98
100
  }
99
101
 
100
102
  getDefaultPosition()
@@ -117,11 +119,14 @@ export class PathControl
117
119
 
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
- for (const path of pathways.PATH_TYPES) {
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');
124
- this.__checkedCount = pathways.PATH_TYPES.length;
129
+ this.__checkedCount = this.__pathTypes.length;
125
130
  this.__halfCount = Math.trunc(this.__checkedCount/2);
126
131
 
127
132
  this._button = document.createElement('button');
@@ -164,11 +169,11 @@ export class PathControl
164
169
  event.target.indeterminate = false;
165
170
  }
166
171
  if (event.target.checked) {
167
- this.__checkedCount = pathways.PATH_TYPES.length;
172
+ this.__checkedCount = this.__pathTypes.length;
168
173
  } else {
169
174
  this.__checkedCount = 0;
170
175
  }
171
- for (const path of pathways.PATH_TYPES) {
176
+ for (const path of this.__pathTypes) {
172
177
  const pathCheckbox = document.getElementById(`path-${path.type}`);
173
178
  if (pathCheckbox) {
174
179
  pathCheckbox.checked = event.target.checked;
@@ -187,7 +192,7 @@ export class PathControl
187
192
  if (this.__checkedCount === 0) {
188
193
  allPathsCheckbox.checked = false;
189
194
  allPathsCheckbox.indeterminate = false;
190
- } else if (this.__checkedCount === pathways.PATH_TYPES.length) {
195
+ } else if (this.__checkedCount === this.__pathTypes.length) {
191
196
  allPathsCheckbox.checked = true;
192
197
  allPathsCheckbox.indeterminate = false;
193
198
  } else {
@@ -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
+ }
@@ -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
- data[property] = properties[property];
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
@@ -29,6 +29,10 @@ import { indexedProperties } from './search.js';
29
29
  export const displayedProperties = [
30
30
  'id',
31
31
  'class',
32
+ 'featureId',
33
+ 'tile-layer',
34
+ 'layer',
35
+ 'kind',
32
36
  ...indexedProperties
33
37
  ];
34
38
 
@@ -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
 
@@ -163,13 +163,16 @@ export class UserInteractions
163
163
  // Add a control to manage our pathways
164
164
 
165
165
  if (flatmap.options.pathControls) {
166
- this._map.addControl(new PathControl(flatmap));
166
+ // Restrict to path types that are on the map...
167
+ this._map.addControl(new PathControl(flatmap, this._pathways.pathTypes));
167
168
  }
168
169
 
169
170
  // Add a control to manage our layers
170
171
 
171
172
  if (flatmap.options.layerControl) {
172
173
  this._map.addControl(new LayerControl(flatmap, this._layerManager));
174
+ // ************************************
175
+ //this._map.addControl(new SCKANControl(flatmap));
173
176
  }
174
177
 
175
178
  // Flag features that have annotations
@@ -177,6 +180,9 @@ export class UserInteractions
177
180
 
178
181
  for (const [id, ann] of flatmap.annotations) {
179
182
  const feature = this.mapFeature_(id);
183
+ if (id == 118) {
184
+ console.log(feature, ann);
185
+ }
180
186
  if (feature !== undefined) {
181
187
  this._map.setFeatureState(feature, { 'annotated': true });
182
188
  }
@@ -555,6 +561,7 @@ export class UserInteractions
555
561
  }
556
562
  bbox = expandBounds(bbox, annotation.bounds);
557
563
  if ('type' in annotation && annotation.type.startsWith('line')) {
564
+ // FC may have lines that are not pathways features, esp. when authoring...
558
565
  for (const pathFeatureId of this._pathways.lineFeatureIds([featureId])) {
559
566
  if (select) {
560
567
  this.selectFeature_(pathFeatureId);
@@ -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>`;
@@ -776,6 +783,10 @@ export class UserInteractions
776
783
  }
777
784
  }
778
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)??
779
790
  let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
780
791
  || 'label' in feature.properties
781
792
  || 'node' in feature.properties)
@@ -1021,6 +1032,11 @@ export class UserInteractions
1021
1032
  return this._pathways.nodePathModels(nodeId);
1022
1033
  }
1023
1034
 
1035
+ showSckanPaths(validity, enable=true)
1036
+ //===================================
1037
+ {
1038
+ }
1039
+
1024
1040
  //==============================================================================
1025
1041
 
1026
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
@@ -300,7 +300,7 @@ export class MinimapControl
300
300
  const newBounds = this.moveTrackingRect_(offset);
301
301
 
302
302
  this._map.fitBounds(newBounds, {
303
- duration: 80,
303
+ duration: 80, // Why these options??
304
304
  noMoveStart: true
305
305
  });
306
306
  }