@abi-software/flatmap-viewer 2.3.0-a.1 → 2.3.0-a.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/src/controls.js CHANGED
@@ -23,6 +23,20 @@ limitations under the License.
23
23
  //==============================================================================
24
24
 
25
25
 
26
+ //==============================================================================
27
+
28
+ // Make sure colour string is in `#rrggbb` form.
29
+ // Based on https://stackoverflow.com/a/47355187
30
+
31
+ function standardise_color(str){
32
+ const canvas = document.createElement("canvas");
33
+ const ctx = canvas.getContext("2d");
34
+ ctx.fillStyle = str;
35
+ const colour = ctx.fillStyle;
36
+ canvas.remove()
37
+ return colour;
38
+ }
39
+
26
40
  //==============================================================================
27
41
 
28
42
  export class NavigationControl
@@ -76,11 +90,11 @@ export class NavigationControl
76
90
 
77
91
  export class PathControl
78
92
  {
79
- constructor(flatmap, pathways)
93
+ constructor(flatmap, pathTypes)
80
94
  {
81
95
  this._flatmap = flatmap;
82
96
  this._map = undefined;
83
- this.__pathTypes = pathways.pathTypes;
97
+ this.__pathTypes = pathTypes;
84
98
  }
85
99
 
86
100
  getDefaultPosition()
@@ -102,20 +116,28 @@ export class PathControl
102
116
  this._legend.className = 'flatmap-nerve-grid';
103
117
 
104
118
  const innerHTML = [];
105
- innerHTML.push(`<label for="path-all-paths">ALL PATHS:</label><input id="path-all-paths" type="checkbox" checked/><div class="nerve-line"></div>`);
119
+ innerHTML.push(`<label for="path-all-paths">ALL PATHS:</label><div class="nerve-line"></div><input id="path-all-paths" type="checkbox" checked/>`);
120
+ this.__checkedCount = 0;
106
121
  for (const path of this.__pathTypes) {
107
- innerHTML.push(`<label for="path-${path.type}">${path.label}</label><input id="path-${path.type}" type="checkbox" checked/><div class="nerve-line nerve-${path.type}"></div>`);
122
+ const checked = !('enabled' in path) || path.enabled ? 'checked' : '';
123
+ if (checked != '') {
124
+ this.__checkedCount += 1;
125
+ }
126
+ const colour = path.colour || '#440';
127
+ const style = path.dashed ? `background: repeating-linear-gradient(to right,${colour} 0,${colour} 6px,transparent 6px,transparent 9px);`
128
+ : `background: ${colour};`;
129
+
130
+ innerHTML.push(`<label for="path-${path.type}">${path.label}</label><div class="nerve-line" style="${style}"></div><input id="path-${path.type}" type="checkbox" ${checked}/>`);
108
131
  }
109
132
  this._legend.innerHTML = innerHTML.join('\n');
110
- this.__checkedCount = this.__pathTypes.length;
111
- this.__halfCount = Math.trunc(this.__checkedCount/2);
133
+ this.__halfCount = Math.trunc(this.__pathTypes.length/2);
112
134
 
113
135
  this._button = document.createElement('button');
114
136
  this._button.id = 'nerve-key-button';
115
- this._button.className = 'control-button';
137
+ this._button.className = 'control-button text-button';
116
138
  this._button.setAttribute('type', 'button');
117
139
  this._button.setAttribute('aria-label', 'Nerve paths legend');
118
- this._button.setAttribute('legend-visible', 'false');
140
+ this._button.setAttribute('control-visible', 'false');
119
141
  this._button.textContent = 'PATHS';
120
142
  this._button.title = 'Show/hide neuron paths';
121
143
  this._container.appendChild(this._button);
@@ -135,13 +157,16 @@ export class PathControl
135
157
  //=============
136
158
  {
137
159
  if (event.target.id === 'nerve-key-button') {
138
- if (this._button.getAttribute('legend-visible') === 'false') {
160
+ if (this._button.getAttribute('control-visible') === 'false') {
139
161
  this._container.appendChild(this._legend);
140
- this._button.setAttribute('legend-visible', 'true');
162
+ this._button.setAttribute('control-visible', 'true');
163
+ const allPathsCheckbox = document.getElementById('path-all-paths');
164
+ allPathsCheckbox.indeterminate = this.__checkedCount < this.__pathTypes.length
165
+ && this.__checkedCount > 0;
141
166
  this._legend.focus();
142
167
  } else {
143
168
  this._legend = this._container.removeChild(this._legend);
144
- this._button.setAttribute('legend-visible', 'false');
169
+ this._button.setAttribute('control-visible', 'false');
145
170
  }
146
171
  } else if (event.target.tagName === 'INPUT') {
147
172
  if (event.target.id === 'path-all-paths') {
@@ -186,3 +211,470 @@ export class PathControl
186
211
  }
187
212
 
188
213
  //==============================================================================
214
+
215
+ export class LayerControl
216
+ {
217
+ constructor(flatmap, layerManager)
218
+ {
219
+ this.__flatmap = flatmap;
220
+ this.__layers = layerManager.layers;
221
+ this.__map = undefined;
222
+ }
223
+
224
+ getDefaultPosition()
225
+ //==================
226
+ {
227
+ return 'top-right';
228
+ }
229
+
230
+ onAdd(map)
231
+ //========
232
+ {
233
+ this.__map = map;
234
+ this.__container = document.createElement('div');
235
+ this.__container.className = 'maplibregl-ctrl flatmap-control';
236
+ this.__layersControl = document.createElement('div');
237
+ this.__layersControl.className = 'flatmap-control-grid';
238
+
239
+ const innerHTML = [];
240
+ innerHTML.push(`<label for="layer-all-layers">ALL LAYERS:</label><input id="layer-all-layers" type="checkbox" checked/>`);
241
+ for (const layer of this.__layers) {
242
+ innerHTML.push(`<label for="layer-${layer.id}">${layer.description}</label><input id="layer-${layer.id}" type="checkbox" checked/>`);
243
+ }
244
+ this.__layersControl.innerHTML = innerHTML.join('\n');
245
+
246
+ this.__layersCount = this.__layers;
247
+ this.__checkedCount = this.__layersCount;
248
+ this.__halfCount = Math.trunc(this.__checkedCount/2);
249
+
250
+ this.__button = document.createElement('button');
251
+ this.__button.id = 'map-layers-button';
252
+ this.__button.className = 'control-button text-button';
253
+ this.__button.setAttribute('type', 'button');
254
+ this.__button.setAttribute('aria-label', 'Show/hide map layers');
255
+ this.__button.setAttribute('control-visible', 'false');
256
+ this.__button.textContent = 'LAYERS';
257
+ this.__button.title = 'Show/hide map layers';
258
+ this.__container.appendChild(this.__button);
259
+
260
+ this.__container.addEventListener('click', this.onClick_.bind(this));
261
+ return this.__container;
262
+ }
263
+
264
+ onRemove()
265
+ //========
266
+ {
267
+ this.__container.parentNode.removeChild(this.__container);
268
+ this.__map = undefined;
269
+ }
270
+
271
+ onClick_(event)
272
+ //=============
273
+ {
274
+ if (event.target.id === 'map-layers-button') {
275
+ if (this.__button.getAttribute('control-visible') === 'false') {
276
+ this.__container.appendChild(this.__layersControl);
277
+ this.__button.setAttribute('control-visible', 'true');
278
+ this.__layersControl.focus();
279
+ } else {
280
+ this.__layersControl = this.__container.removeChild(this.__layersControl);
281
+ this.__button.setAttribute('control-visible', 'false');
282
+ }
283
+ } else if (event.target.tagName === 'INPUT') {
284
+ if (event.target.id === 'layer-all-layers') {
285
+ if (event.target.indeterminate) {
286
+ event.target.checked = (this.__checkedCount >= this.__halfCount);
287
+ event.target.indeterminate = false;
288
+ }
289
+ if (event.target.checked) {
290
+ this.__checkedCount = this.__layersCount;
291
+ } else {
292
+ this.__checkedCount = 0;
293
+ }
294
+ for (const layer of this.__layers) {
295
+ const layerCheckbox = document.getElementById(`layer-${layer.id}`);
296
+ if (layerCheckbox) {
297
+ layerCheckbox.checked = event.target.checked;
298
+ this.__flatmap.enableLayer(layer.id, event.target.checked);
299
+ }
300
+ }
301
+ } else if (event.target.id.startsWith('layer-')) {
302
+ const layerId = event.target.id.substring(6);
303
+ this.__flatmap.enableLayer(layerId, event.target.checked);
304
+ if (event.target.checked) {
305
+ this.__checkedCount += 1;
306
+ } else {
307
+ this.__checkedCount -= 1;
308
+ }
309
+ const allLayersCheckbox = document.getElementById('layer-all-layers');
310
+ if (this.__checkedCount === 0) {
311
+ allLayersCheckbox.checked = false;
312
+ allLayersCheckbox.indeterminate = false;
313
+ } else if (this.__checkedCount === this.__layersCount) {
314
+ allLayersCheckbox.checked = true;
315
+ allLayersCheckbox.indeterminate = false;
316
+ } else {
317
+ allLayersCheckbox.indeterminate = true;
318
+ }
319
+ }
320
+ }
321
+ event.stopPropagation();
322
+ }
323
+ }
324
+
325
+ //==============================================================================
326
+
327
+ export class Control
328
+ {
329
+ constructor(flatmap, id, name)
330
+ {
331
+ this.__flatmap = flatmap;
332
+ this.__id = id;
333
+ this.__name = name;
334
+ this.__map = undefined;
335
+ this.__prefix = `${this.__id}-`
336
+ }
337
+
338
+ getDefaultPosition()
339
+ //==================
340
+ {
341
+ return 'top-right';
342
+ }
343
+
344
+ __innerLinesHTML()
345
+ //================
346
+ {
347
+ return [];
348
+ }
349
+
350
+ __enableAll(enable)
351
+ //=================
352
+ {
353
+
354
+ }
355
+
356
+ onAdd(map)
357
+ //========
358
+ {
359
+ this.__map = map;
360
+ this.__container = document.createElement('div');
361
+ this.__container.className = 'maplibregl-ctrl flatmap-control';
362
+ this.__control = document.createElement('div');
363
+ this.__control.className = 'flatmap-control-grid';
364
+
365
+ const innerHTML = this.__innerLinesHTML();
366
+ this.__totalCount = innerHTML.length;
367
+ innerHTML.splice(0, 0, `<label for="control-all-${this.__id}">ALL ${this.__name.toUpperCase()}:</label><input id="control-all-${this.__id}" type="checkbox" checked/>`);
368
+ this.__control.innerHTML = innerHTML.join('\n');
369
+
370
+ this.__checkedCount = this.__totalCount;
371
+ this.__halfCount = Math.trunc(this.__checkedCount/2);
372
+
373
+ this.__button = document.createElement('button');
374
+ this.__button.id = `flatmap-${this.__id}-button`;
375
+ this.__button.className = 'control-button text-button';
376
+ this.__button.setAttribute('type', 'button');
377
+ this.__button.setAttribute('aria-label', `Show/hide map's ${this.__name}`);
378
+ this.__button.setAttribute('control-visible', 'false');
379
+ this.__button.textContent = this.__name.toUpperCase().substring(0, 6);
380
+ this.__button.title = `Show/hide map's ${this.__name}`;
381
+ this.__container.appendChild(this.__button);
382
+
383
+ this.__container.addEventListener('click', this.onClick_.bind(this));
384
+ return this.__container;
385
+ }
386
+
387
+ onRemove()
388
+ //========
389
+ {
390
+ this.__container.parentNode.removeChild(this.__container);
391
+ this.__map = undefined;
392
+ }
393
+
394
+ onClick_(event)
395
+ //=============
396
+ {
397
+ if (event.target.id === `flatmap-${this.__id}-button`) {
398
+ if (this.__button.getAttribute('control-visible') === 'false') {
399
+ this.__container.appendChild(this.__control);
400
+ this.__button.setAttribute('control-visible', 'true');
401
+ this.__control.focus();
402
+ } else {
403
+ this.__control = this.__container.removeChild(this.__control);
404
+ this.__button.setAttribute('control-visible', 'false');
405
+ }
406
+ } else if (event.target.tagName === 'INPUT') {
407
+ if (event.target.id === `control-all-${this.__id}`) {
408
+ if (event.target.indeterminate) {
409
+ event.target.checked = (this.__checkedCount >= this.__halfCount);
410
+ event.target.indeterminate = false;
411
+ }
412
+ if (event.target.checked) {
413
+ this.__checkedCount = this.__totalCount;
414
+ } else {
415
+ this.__checkedCount = 0;
416
+ }
417
+ this.__enableAll(event.target.checked);
418
+ } else if (event.target.id.startsWith(`${this.__id}-`)) {
419
+ this.__enableControl(event.target.id.substring(this.__prefix.length),
420
+ event.target.checked);
421
+ if (event.target.checked) {
422
+ this.__checkedCount += 1;
423
+ } else {
424
+ this.__checkedCount -= 1;
425
+ }
426
+ const allCheckbox = document.getElementById(`control-all-${this.__id}`);
427
+ if (this.__checkedCount === 0) {
428
+ allCheckbox.checked = false;
429
+ allCheckbox.indeterminate = false;
430
+ } else if (this.__checkedCount === this.__totalCount) {
431
+ allCheckbox.checked = true;
432
+ allCheckbox.indeterminate = false;
433
+ } else {
434
+ allCheckbox.indeterminate = true;
435
+ }
436
+ }
437
+ }
438
+ event.stopPropagation();
439
+ }
440
+ }
441
+
442
+ //==============================================================================
443
+
444
+ const SCKAN_STATES = [
445
+ {
446
+ 'id': 'VALID',
447
+ 'description': 'Path consistent with SCKAN'
448
+ },
449
+ {
450
+ 'id': 'INVALID',
451
+ 'description': 'Path inconsistent with SCKAN'
452
+ }
453
+ ];
454
+
455
+
456
+ export class SCKANControl
457
+ {
458
+ constructor(flatmap, options={sckan: 'valid'})
459
+ {
460
+ this.__flatmap = flatmap;
461
+ this.__map = undefined;
462
+ this.__initialState = options.sckan || 'valid';
463
+ }
464
+
465
+ getDefaultPosition()
466
+ //==================
467
+ {
468
+ return 'top-right';
469
+ }
470
+
471
+ onAdd(map)
472
+ //========
473
+ {
474
+ this.__map = map;
475
+ this.__container = document.createElement('div');
476
+ this.__container.className = 'maplibregl-ctrl flatmap-control';
477
+ this.__sckan = document.createElement('div');
478
+ this.__sckan.className = 'flatmap-control-grid';
479
+
480
+ const innerHTML = [];
481
+ let checked = (this.__initialState === 'all') ? 'checked' : '';
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
+ checked = (this.__initialState.toUpperCase() === state.id) ? 'checked' : '';
485
+ innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" ${checked}/>`);
486
+ }
487
+ this.__sckan.innerHTML = innerHTML.join('\n');
488
+
489
+ this.__sckanCount = SCKAN_STATES.length;
490
+ this.__checkedCount = (this.__initialState === 'all') ? this.__sckanCount
491
+ : (this.__initialState === 'none') ? 0
492
+ : 1;
493
+ this.__halfCount = Math.trunc(this.__sckanCount/2);
494
+
495
+ this.__button = document.createElement('button');
496
+ this.__button.id = 'map-sckan-button';
497
+ this.__button.className = 'control-button text-button';
498
+ this.__button.setAttribute('type', 'button');
499
+ this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
500
+ this.__button.setAttribute('control-visible', 'false');
501
+ this.__button.textContent = 'SCKAN';
502
+ this.__button.title = 'Show/hide valid SCKAN paths';
503
+ this.__container.appendChild(this.__button);
504
+
505
+ this.__container.addEventListener('click', this.onClick_.bind(this));
506
+ return this.__container;
507
+ }
508
+
509
+ onRemove()
510
+ //========
511
+ {
512
+ this.__container.parentNode.removeChild(this.__container);
513
+ this.__map = undefined;
514
+ }
515
+
516
+ onClick_(event)
517
+ //=============
518
+ {
519
+ if (event.target.id === 'map-sckan-button') {
520
+ if (this.__button.getAttribute('control-visible') === 'false') {
521
+ this.__container.appendChild(this.__sckan);
522
+ this.__button.setAttribute('control-visible', 'true');
523
+ const allLayersCheckbox = document.getElementById('sckan-all-paths');
524
+ allLayersCheckbox.indeterminate = (this.__checkedCount > 0)
525
+ && (this.__checkedCount < this.__sckanCount);
526
+ this.__sckan.focus();
527
+ } else {
528
+ this.__sckan = this.__container.removeChild(this.__sckan);
529
+ this.__button.setAttribute('control-visible', 'false');
530
+ }
531
+ } else if (event.target.tagName === 'INPUT') {
532
+ if (event.target.id === 'sckan-all-paths') {
533
+ if (event.target.indeterminate) {
534
+ event.target.checked = (this.__checkedCount >= this.__halfCount);
535
+ event.target.indeterminate = false;
536
+ }
537
+ if (event.target.checked) {
538
+ this.__state = 'all';
539
+ this.__checkedCount = this.__sckanCount;
540
+ } else {
541
+ this.__state = 'none';
542
+ this.__checkedCount = 0;
543
+ }
544
+ for (const state of SCKAN_STATES) {
545
+ const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
546
+ if (sckanCheckbox) {
547
+ sckanCheckbox.checked = event.target.checked;
548
+ this.__flatmap.enableSckanPath(state.id, event.target.checked);
549
+ }
550
+ }
551
+ } else if (event.target.id.startsWith('sckan-')) {
552
+ const sckanId = event.target.id.substring(6);
553
+ this.__flatmap.enableSckanPath(sckanId, event.target.checked);
554
+ if (event.target.checked) {
555
+ this.__checkedCount += 1;
556
+ } else {
557
+ this.__checkedCount -= 1;
558
+ }
559
+ const allLayersCheckbox = document.getElementById('sckan-all-paths');
560
+ if (this.__checkedCount === 0) {
561
+ allLayersCheckbox.checked = false;
562
+ allLayersCheckbox.indeterminate = false;
563
+ } else if (this.__checkedCount === this.__sckanCount) {
564
+ allLayersCheckbox.checked = true;
565
+ allLayersCheckbox.indeterminate = false;
566
+ } else {
567
+ allLayersCheckbox.indeterminate = true;
568
+ }
569
+ }
570
+ }
571
+ event.stopPropagation();
572
+ }
573
+ }
574
+
575
+ //==============================================================================
576
+
577
+ export class NerveControl
578
+ {
579
+ constructor(flatmap, options={showCentrelines: false})
580
+ {
581
+ this.__flatmap = flatmap;
582
+ this.__map = undefined;
583
+ this.__visible = options.showCentrelines || false;
584
+ }
585
+
586
+ getDefaultPosition()
587
+ //==================
588
+ {
589
+ return 'top-right';
590
+ }
591
+
592
+ onAdd(map)
593
+ //========
594
+ {
595
+ this.__map = map;
596
+ this.__container = document.createElement('div');
597
+ this.__container.className = 'maplibregl-ctrl';
598
+
599
+ this.__button = document.createElement('button');
600
+ this.__button.id = 'map-nerve-button';
601
+ this.__button.className = 'control-button text-button';
602
+ this.__button.setAttribute('type', 'button');
603
+ this.__button.setAttribute('aria-label', 'Show/hide nerve centrelines');
604
+ this.__button.textContent = 'NERVES';
605
+ this.__button.title = 'Show/hide nerve centrelines';
606
+ this.__container.appendChild(this.__button);
607
+
608
+ this.__container.addEventListener('click', this.onClick_.bind(this));
609
+ return this.__container;
610
+ }
611
+
612
+ onRemove()
613
+ //========
614
+ {
615
+ this.__container.parentNode.removeChild(this.__container);
616
+ this.__map = undefined;
617
+ }
618
+
619
+ onClick_(event)
620
+ //=============
621
+ {
622
+ if (event.target.id === 'map-nerve-button') {
623
+ this.__visible = !this.__visible;
624
+ this.__flatmap.enableCentrelines(this.__visible);
625
+ }
626
+ event.stopPropagation();
627
+ }
628
+ }
629
+
630
+ //==============================================================================
631
+
632
+ export class BackgroundControl
633
+ {
634
+ constructor(flatmap)
635
+ {
636
+ this.__flatmap = flatmap;
637
+ this.__map = undefined;
638
+ }
639
+
640
+ getDefaultPosition()
641
+ //==================
642
+ {
643
+ return 'bottom-right';
644
+ }
645
+
646
+ onAdd(map)
647
+ //========
648
+ {
649
+ this.__map = map;
650
+ this.__container = document.createElement('div');
651
+ this.__container.className = 'maplibregl-ctrl';
652
+ this.__colourDiv = document.createElement('div');
653
+ this.__colourDiv.setAttribute('aria-label', 'Change background colour');
654
+ this.__colourDiv.title = 'Change background colour';
655
+ const background = standardise_color(this.__flatmap.getBackgroundColour());
656
+ this.__colourDiv.innerHTML = `<input type="color" id="colourPicker" value="${background}">`;
657
+ this.__container.appendChild(this.__colourDiv);
658
+ this.__colourDiv.addEventListener('input', this.__updateColour.bind(this), false);
659
+ this.__colourDiv.addEventListener('change', this.__updateColour.bind(this), false);
660
+ return this.__container;
661
+ }
662
+
663
+ onRemove()
664
+ //========
665
+ {
666
+ this.__container.parentNode.removeChild(this.__container);
667
+ this.__map = undefined;
668
+ }
669
+
670
+ __updateColour(event)
671
+ //===================
672
+ {
673
+ const colour = event.target.value;
674
+ this.__flatmap.setBackgroundColour(colour);
675
+ this.__flatmap.controlEvent('change', 'background', colour)
676
+ event.stopPropagation();
677
+ }
678
+ }
679
+
680
+ //==============================================================================