@abi-software/flatmapvuer 0.6.3-vue.3.9 → 1.0.0

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.
Files changed (38) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +120 -120
  3. package/cypress.config.js +23 -23
  4. package/dist/flatmapvuer.js +15894 -16259
  5. package/dist/flatmapvuer.umd.cjs +132 -145
  6. package/dist/index.html +17 -17
  7. package/dist/style.css +1 -1
  8. package/package.json +95 -95
  9. package/public/index.html +17 -17
  10. package/reporter-config.json +9 -9
  11. package/src/App.vue +310 -310
  12. package/src/assets/_variables.scss +43 -43
  13. package/src/assets/styles.scss +5 -5
  14. package/src/components/AnnotationTool.vue +450 -446
  15. package/src/components/EventBus.js +3 -3
  16. package/src/components/ExternalResourceCard.vue +107 -107
  17. package/src/components/FlatmapVuer.vue +2600 -2531
  18. package/src/components/MultiFlatmapVuer.vue +731 -731
  19. package/src/components/ProvenancePopup.vue +503 -495
  20. package/src/components/SelectionsGroup.vue +255 -255
  21. package/src/components/Tooltip.vue +50 -50
  22. package/src/components/TreeControls.vue +231 -231
  23. package/src/components/index.js +7 -7
  24. package/src/components/legends/DynamicLegends.vue +106 -106
  25. package/src/components/legends/SvgLegends.vue +112 -112
  26. package/src/icons/flatmap-marker.js +1 -1
  27. package/src/icons/fonts/mapicon-species.svg +14 -14
  28. package/src/icons/fonts/mapicon-species.ttf +0 -0
  29. package/src/icons/fonts/mapicon-species.woff +0 -0
  30. package/src/icons/mapicon-species-style.css +42 -42
  31. package/src/icons/yellowstar.js +5 -5
  32. package/src/legends/legend.svg +25 -25
  33. package/src/main.js +19 -19
  34. package/src/services/flatmapQueries.js +453 -453
  35. package/src/store/index.js +23 -23
  36. package/vite.config.js +73 -73
  37. package/vite.static-build.js +12 -12
  38. package/vuese-generator.js +64 -64
@@ -1,732 +1,732 @@
1
- <template>
2
- <div class="multi-container" ref="multiContainer">
3
- <div style="position: absolute; z-index: 10" v-if="!disableUI">
4
- <div class="species-display-text">Species</div>
5
- <el-popover
6
- content="Select a species"
7
- placement="right"
8
- trigger="manual"
9
- popper-class="flatmap-popper right-popper"
10
- :visible="helpMode"
11
- :teleported="false"
12
- ref="selectPopover"
13
- >
14
- <template #reference>
15
- <el-select
16
- id="flatmap-select"
17
- :teleported="false"
18
- v-model="activeSpecies"
19
- placeholder="Select"
20
- class="select-box"
21
- popper-class="flatmap-dropdown"
22
- @change="setSpecies"
23
- >
24
- <el-option
25
- v-for="(item, key) in speciesList"
26
- :key="key"
27
- :label="key"
28
- :value="key"
29
- >
30
- <span class="select-box-icon">
31
- <i :class="item.iconClass"></i>
32
- </span>
33
- {{ key }}
34
- </el-option>
35
- </el-select>
36
- </template>
37
-
38
- </el-popover>
39
- </div>
40
- <FlatmapVuer
41
- v-for="(item, key) in speciesList"
42
- :key="key"
43
- v-show="activeSpecies == key"
44
- :entry="item.taxo"
45
- :uuid="item.uuid"
46
- :biologicalSex="item.biologicalSex"
47
- :displayWarning="item.displayWarning"
48
- :displayLatestChanges="item.displayLatestChanges"
49
- :isLegacy="item.isLegacy"
50
- :ref="key"
51
- :enableOpenMapUI="enableOpenMapUI"
52
- :openMapOptions="openMapOptions"
53
- :disableUI="disableUI"
54
- @view-latest-map="viewLatestMap"
55
- @resource-selected="resourceSelected"
56
- @ready="FlatmapReady"
57
- @pan-zoom-callback="panZoomCallback"
58
- @open-map="
59
- /**
60
- * This event is emitted when the user chooses a different map option
61
- * from ``openMapOptions`` props.
62
- * @arg $event
63
- */
64
- $emit('open-map', $event)"
65
- :minZoom="minZoom"
66
- :helpMode="helpMode"
67
- :renderAtMounted="renderAtMounted"
68
- :displayMinimap="displayMinimap"
69
- :showStarInLegend="showStarInLegend"
70
- style="height: 100%"
71
- :flatmapAPI="flatmapAPI"
72
- :sparcAPI="sparcAPI"
73
- />
74
- </div>
75
- </template>
76
-
77
- <script>
78
- /* eslint-disable no-alert, no-console */
79
- import { reactive } from 'vue'
80
- import EventBus from './EventBus'
81
- import FlatmapVuer from './FlatmapVuer.vue'
82
- import * as flatmap from '@abi-software/flatmap-viewer'
83
- import {
84
- ElCol as Col,
85
- ElOption as Option,
86
- ElSelect as Select,
87
- ElRow as Row,
88
- ElPopover as Popover,
89
- } from 'element-plus'
90
-
91
- const TAXON_UUID = {
92
- 'NCBITaxon:10114': '01fedbf9-d783-509c-a10c-827941ab13da',
93
- 'NCBITaxon:9823': 'a336ac04-24db-561f-a25f-1c994fe17410',
94
- 'NCBITaxon:9606': '42ed6323-f645-5fbe-bada-9581819cf689',
95
- 'NCBITaxon:10090': '25285fab-48a0-5620-a6a0-f9a0374837d5',
96
- 'NCBITaxon:9685': '73060497-46a6-52bf-b975-cac511c127cb',
97
- }
98
-
99
- /**
100
- * A vue component to show a flatmap from the list of multiple flatmap data.
101
- */
102
- export default {
103
- name: 'MultiFlatmapVuer',
104
- components: {
105
- Col,
106
- Row,
107
- Option,
108
- Select,
109
- Popover,
110
- FlatmapVuer,
111
- },
112
- beforeMount() {
113
- //List for resolving the promise in initialise
114
- //if initialise is called multiple times
115
- this._resolveList = []
116
- this._initialised = false
117
- },
118
- mounted: function () {
119
- this.initialise()
120
- EventBus.on('onActionClick', (action) => {
121
- this.resourceSelected(action)
122
- })
123
- },
124
- methods: {
125
- /**
126
- * @vuese
127
- * Function to initialise the component when mounted.
128
- * It returns a promise.
129
- */
130
- initialise: function () {
131
- return new Promise((resolve) => {
132
- if (this.requireInitialisation) {
133
- //It has not been initialised yet
134
- this.requireInitialisation = false
135
- fetch(this.flatmapAPI)
136
- .then((response) => response.json())
137
- .then((data) => {
138
- //Check each key in the provided availableSpecies against the one
139
- Object.keys(this.availableSpecies).forEach((key) => {
140
- // FIrst look through the uuid
141
- const uuid = this.availableSpecies[key].uuid
142
- if (uuid && data.map((e) => e.uuid).indexOf(uuid) > 0) {
143
- this.speciesList[key] = reactive(this.availableSpecies[key])
144
- } else {
145
- for (let i = 0; i < data.length; i++) {
146
- if (this.availableSpecies[key].taxo === data[i].taxon) {
147
- if (this.availableSpecies[key].biologicalSex) {
148
- if (
149
- data[i].biologicalSex &&
150
- data[i].biologicalSex ===
151
- this.availableSpecies[key].biologicalSex
152
- ) {
153
- this.speciesList[key] = reactive(this.availableSpecies[key])
154
- break
155
- }
156
- } else {
157
- this.speciesList[key] = reactive(this.availableSpecies[key])
158
- break
159
- }
160
- }
161
- }
162
- }
163
- })
164
- //Use the state species if it does not have any other species information
165
- let species = this.initial
166
- if (this.state) {
167
- const mapState = this.state.state
168
- if (
169
- (!mapState || (!mapState.uuid && !mapState.entry)) &&
170
- this.state.species
171
- )
172
- species = this.state.species
173
- else species = undefined
174
- }
175
- if (species) {
176
- //No state resuming, set the current flatmap to {this.initial}
177
- if (species && this.speciesList[species] !== undefined) {
178
- this.activeSpecies = species
179
- } else {
180
- this.activeSpecies = Object.keys(this.speciesList)[0]
181
- }
182
- this.setSpecies(
183
- this.activeSpecies,
184
- this.state ? this.state.state : undefined,
185
- 5
186
- )
187
- }
188
- this._initialised = true
189
- resolve()
190
- //Resolve all other promises resolve in the list
191
- this._resolveList.forEach((other) => {
192
- other()
193
- })
194
- })
195
- } else if (this._initialised) {
196
- //resolve as it has been initialised
197
- resolve()
198
- } else {
199
- //resolve when the async initialisation is finished
200
- this._resolveList.push(resolve)
201
- }
202
- })
203
- },
204
- /**
205
- * @vuese
206
- * Function to emit ``resource-selected`` event with provided ``resource``.
207
- * @arg action
208
- */
209
- resourceSelected: function (action) {
210
- /**
211
- * This event is emitted by ``resourceSelected`` method.
212
- */
213
- this.$emit('resource-selected', action)
214
- },
215
- /**
216
- * @vuese
217
- * Function to emit ``ready`` event after the flatmap is loaded.
218
- * @arg component
219
- */
220
- FlatmapReady: function (component) {
221
- /**
222
- * This event is emitted by ``FlatmapReady`` method after the flatmap is loaded.
223
- * @arg component
224
- */
225
- this.$emit('ready', component)
226
- },
227
- /**
228
- * @vuese
229
- * Function to get the current active map.
230
- */
231
- getCurrentFlatmap: function () {
232
- return this.$refs[this.activeSpecies][0]
233
- },
234
- /**
235
- * @vuese
236
- * Function to emit ``pan-zoom-callback`` event
237
- * from the event emitted in ``callback`` function from ``MapManager.loadMap()``.
238
- * @arg payload
239
- */
240
- panZoomCallback: function (payload) {
241
- /**
242
- * The event emitted by ``panZoomCallback`` method.
243
- * @arg payload
244
- */
245
- this.$emit('pan-zoom-callback', payload)
246
- },
247
- /**
248
- * @vuese
249
- * Function to show popup on map.
250
- * @arg featureId,
251
- * @arg node,
252
- * @arg options
253
- */
254
- showPopup: function (featureId, node, options) {
255
- let map = this.getCurrentFlatmap()
256
- map.showPopup(featureId, node, options)
257
- },
258
- /**
259
- * @vuese
260
- * Function to show marker popup.
261
- * @arg featureId,
262
- * @arg node,
263
- * @arg options
264
- */
265
- showMarkerPopup: function (featureId, node, options) {
266
- let map = this.getCurrentFlatmap()
267
- map.showMarkerPopup(featureId, node, options)
268
- },
269
- /**
270
- * @vuese
271
- * Function to set species.
272
- * This function is called on the first load and
273
- * when user changes the species.
274
- * @arg species,
275
- * @arg state,
276
- * @arg numberOfRetry
277
- */
278
- setSpecies: function (species, state, numberOfRetry) {
279
- if (this.$refs && species in this.$refs) {
280
- this.activeSpecies = species
281
- this.$refs[this.activeSpecies][0].createFlatmap(state)
282
- /**
283
- * This event is emitted by ``setSpecies`` method.
284
- * Emitted on first load and when user changes species.
285
- * @arg activeSpecies
286
- */
287
- this.$emit('flatmapChanged', this.activeSpecies)
288
- } else if (numberOfRetry) {
289
- const retry = numberOfRetry - 1
290
- if (retry >= 0) {
291
- this.$nextTick(() => {
292
- this.setSpecies(species, state, retry)
293
- })
294
- }
295
- }
296
- },
297
- /**
298
- * @vuese
299
- * Function to switch to the latest existing map from
300
- * a legacy map of the same species.
301
- * @arg state
302
- *
303
- * @private
304
- */
305
- viewLatestMap: function (state) {
306
- const keys = Object.keys(this.speciesList)
307
- for (let i = 0; i < keys.length; i++) {
308
- const species = this.speciesList[keys[i]]
309
- if (
310
- !species.isLegacy &&
311
- species.taxo === state.entry &&
312
- species.biologicalSex === state.biologicalSex
313
- ) {
314
- this.setSpecies(keys[i], state, 0)
315
- return
316
- }
317
- }
318
- },
319
- /**
320
- * @vuese
321
- * Create a legacy entry with the provided information
322
- * @arg state,
323
- * @arg taxo,
324
- * @arg uuid
325
- *
326
- * @private
327
- */
328
- createLegacyEntry: function (state, taxo, uuid) {
329
- if (uuid && taxo) {
330
- let name = 'Legacy'
331
- if (state.species) {
332
- if (state.species.slice(0, 6) === 'Legacy') name = state.species
333
- else name = name + ` ${state.species}`
334
- }
335
- this.speciesList[name] = reactive({
336
- taxo: taxo,
337
- isLegacy: true,
338
- displayWarning: true,
339
- })
340
- return {
341
- species: name,
342
- state: {
343
- entry: taxo,
344
- uuid: uuid,
345
- viewport: state.state.viewport,
346
- searchTerm: state.state.searchTerm,
347
- },
348
- }
349
- }
350
- },
351
- /**
352
- * @vuese
353
- * Function used to translate the legacy map state to one that can be used in current
354
- * flatmap if required. If it is a legacy, an Select entry will be added
355
- * @arg state
356
- *
357
- * @private
358
- */
359
- updateState: function (state) {
360
- return new Promise((resolve) => {
361
- if (state && state.state) {
362
- const mapState = state.state
363
- //uuid is not in the state, this is a legacy map
364
- if (!mapState.uuid) {
365
- if (mapState.entry) {
366
- const uuid =
367
- mapState.entry in TAXON_UUID
368
- ? TAXON_UUID[mapState.entry]
369
- : undefined
370
- const newState = this.createLegacyEntry(
371
- state,
372
- mapState.entry,
373
- uuid
374
- )
375
- resolve(newState ? newState : state)
376
- }
377
- } else if (mapState.entry) {
378
- //uuid is in the state but should be checked if it is the latest map
379
- //for that taxon
380
- return new Promise(() => {
381
- const mapManager = new flatmap.MapManager(this.flatmapAPI)
382
- //mapManager.findMap_ is an async function so we need to wrap this with a promise
383
- const identifier = { taxon: mapState.entry }
384
- if (mapState.biologicalSex)
385
- identifier['biologicalSex'] = mapState.biologicalSex
386
- mapManager
387
- .findMap_(identifier)
388
- .then((map) => {
389
- if (map.uuid !== mapState.uuid) {
390
- return this.createLegacyEntry(
391
- state,
392
- mapState.entry,
393
- mapState.uuid
394
- )
395
- }
396
- })
397
- .then((newState) => {
398
- resolve(newState ? newState : state)
399
- })
400
- .catch(() => {
401
- resolve(state)
402
- })
403
- })
404
- }
405
- //Create a new state and add the legacy map to the select
406
- }
407
- resolve(state)
408
- })
409
- },
410
- /**
411
- * @vuese
412
- * Function used for getting the current states of the scene. This exported states
413
- * can be imported using the importStates method.
414
- *
415
- * @public
416
- */
417
- getState: function () {
418
- let state = {
419
- species: this.activeSpecies,
420
- state: undefined,
421
- }
422
- let map = this.getCurrentFlatmap()
423
- state.state = map.getState()
424
- return state
425
- },
426
- /**
427
- * @vuese
428
- * Function used for importing the states of the scene. This exported states
429
- * can be imported using the read states method.
430
- * @arg state
431
- *
432
- * @public
433
- */
434
- setState: function (state) {
435
- if (state) {
436
- //Update state if required
437
- this.updateState(state).then((currentState) => {
438
- this.initialise().then(() => {
439
- if (
440
- currentState.species &&
441
- currentState.species !== this.activeSpecies
442
- ) {
443
- this.setSpecies(currentState.species, currentState.state, 5)
444
- } else if (currentState.state) {
445
- let map = this.getCurrentFlatmap()
446
- map.setState(currentState.state)
447
- }
448
- })
449
- })
450
- }
451
- },
452
- },
453
- props: {
454
- /**
455
- * Initial species for the flatmap.
456
- * This value will be ignored if a valid state object is provided.
457
- */
458
- initial: {
459
- type: String,
460
- default: '',
461
- },
462
- /**
463
- * The minimum zoom level of the map.
464
- */
465
- minZoom: {
466
- type: Number,
467
- default: 4,
468
- },
469
- /**
470
- * The option to create map on component mounted.
471
- */
472
- renderAtMounted: {
473
- type: Boolean,
474
- default: false,
475
- },
476
- /**
477
- * The option to show tooltips for help mode.
478
- */
479
- helpMode: {
480
- type: Boolean,
481
- default: false,
482
- },
483
- /**
484
- * The option to display minimap at the top-right corner of the map.
485
- */
486
- displayMinimap: {
487
- type: Boolean,
488
- default: false,
489
- },
490
- /**
491
- * The option to show star in legend area.
492
- */
493
- showStarInLegend: {
494
- type: Boolean,
495
- default: false,
496
- },
497
- /**
498
- * Flag to determine rather open map UI should be
499
- * presented or not.
500
- */
501
- enableOpenMapUI: {
502
- type: Boolean,
503
- default: false,
504
- },
505
- /**
506
- * The data to show different map options.
507
- * Available at the bottom-left corner ("Open new map" tooltip).
508
- */
509
- openMapOptions: {
510
- type: Array,
511
- },
512
- /**
513
- * The available species data for different maps.
514
- * This data is used for multi flatmaps.
515
- */
516
- availableSpecies: {
517
- type: Object,
518
- /**
519
- * ```{
520
- 'Human Female': {
521
- taxo: 'NCBITaxon:9606',
522
- biologicalSex: 'PATO:0000383',
523
- iconClass: 'mapicon-icon_human',
524
- displayWarning: true,
525
- },
526
- 'Human Male': {
527
- taxo: 'NCBITaxon:9606',
528
- biologicalSex: 'PATO:0000384',
529
- iconClass: 'mapicon-icon_human',
530
- displayWarning: true,
531
- },
532
- Rat: {
533
- taxo: 'NCBITaxon:10114',
534
- iconClass: 'mapicon-icon_rat',
535
- displayLatestChanges: true,
536
- },
537
- Mouse: {
538
- taxo: 'NCBITaxon:10090',
539
- iconClass: 'mapicon-icon_mouse',
540
- displayWarning: true,
541
- },
542
- Pig: {
543
- taxo: 'NCBITaxon:9823',
544
- iconClass: 'mapicon-icon_pig',
545
- displayWarning: true,
546
- },
547
- Cat: {
548
- taxo: 'NCBITaxon:9685',
549
- iconClass: 'mapicon-icon_cat',
550
- displayWarning: true,
551
- },
552
- }```
553
- */
554
- default: function () {
555
- return {
556
- 'Human Female': {
557
- taxo: 'NCBITaxon:9606',
558
- biologicalSex: 'PATO:0000383',
559
- iconClass: 'mapicon-icon_human',
560
- displayWarning: true,
561
- },
562
- 'Human Male': {
563
- taxo: 'NCBITaxon:9606',
564
- biologicalSex: 'PATO:0000384',
565
- iconClass: 'mapicon-icon_human',
566
- displayWarning: true,
567
- },
568
- Rat: {
569
- taxo: 'NCBITaxon:10114',
570
- iconClass: 'mapicon-icon_rat',
571
- displayLatestChanges: true,
572
- },
573
- Mouse: {
574
- taxo: 'NCBITaxon:10090',
575
- iconClass: 'mapicon-icon_mouse',
576
- displayWarning: true,
577
- },
578
- Pig: {
579
- taxo: 'NCBITaxon:9823',
580
- iconClass: 'mapicon-icon_pig',
581
- displayWarning: true,
582
- },
583
- Cat: {
584
- taxo: 'NCBITaxon:9685',
585
- iconClass: 'mapicon-icon_cat',
586
- displayWarning: true,
587
- },
588
- }
589
- },
590
- },
591
- /**
592
- * State containing state of the flatmap.
593
- */
594
- state: {
595
- type: Object,
596
- default: undefined,
597
- },
598
- /**
599
- * Specify the endpoint of the flatmap server.
600
- */
601
- flatmapAPI: {
602
- type: String,
603
- default: 'https://mapcore-demo.org/current/flatmap/v3/',
604
- },
605
- /**
606
- * Specify the endpoint of the SPARC API.
607
- */
608
- sparcAPI: {
609
- type: String,
610
- default: 'https://api.sparc.science/',
611
- },
612
- /**
613
- * Flag to disable UIs on Map
614
- */
615
- disableUI: {
616
- type: Boolean,
617
- default: false,
618
- }
619
- },
620
- data: function () {
621
- return {
622
- activeSpecies: undefined,
623
- speciesList: {},
624
- requireInitialisation: true,
625
- }
626
- },
627
- watch: {
628
- state: {
629
- handler: function (state) {
630
- this.setState(state)
631
- },
632
- immediate: true,
633
- deep: true,
634
- },
635
- },
636
- }
637
- </script>
638
-
639
- <style lang="scss" scoped>
640
- .multi-container {
641
- height: 100%;
642
- width: 100%;
643
- }
644
-
645
- .species-display-text {
646
- width: 47px;
647
- height: 20px;
648
- color: rgb(48, 49, 51);
649
- font-size: 14px;
650
- font-weight: normal;
651
- line-height: 20px;
652
- left: 24px;
653
- top: 16px;
654
- position: absolute;
655
- }
656
-
657
- .select-box {
658
- width: 120px;
659
- border-radius: 4px;
660
- border: 1px solid rgb(144, 147, 153);
661
- background-color: var(--white);
662
- font-weight: 500;
663
- color: rgb(48, 49, 51);
664
- left: 16px;
665
- top: 44px;
666
- position: absolute;
667
- :deep(.el-input__inner) {
668
- color: rgb(48, 49, 51);
669
- padding-top: 0.25em;
670
- }
671
- :deep() {
672
- .el-input {
673
- .el-input__wrapper{
674
- &is-focus,
675
- &:focus {
676
- border: 1px solid $app-primary-color;
677
- }
678
- }
679
- }
680
- }
681
-
682
- .select-box-icon {
683
- display: inline-block;
684
- width: 24px;
685
- margin-right: 5px;
686
- text-align: center;
687
- }
688
- }
689
-
690
- .flatmap-dropdown {
691
- min-width: 160px !important;
692
- .el-select-dropdown__item {
693
- white-space: nowrap;
694
- text-align: left;
695
- &.selected {
696
- color: $app-primary-color;
697
- font-weight: normal;
698
- }
699
- }
700
- }
701
-
702
- .flatmap-popper {
703
- padding: 6px 4px;
704
- font-size: 12px;
705
- color: rgb(48, 49, 51);
706
- background-color: #f3ecf6;
707
- border: 1px solid $app-primary-color;
708
- white-space: nowrap;
709
- min-width: unset;
710
- &.right-popper {
711
- .popper__arrow {
712
- border-right-color: $app-primary-color !important;
713
- &:after {
714
- border-right-color: #f3ecf6 !important;
715
- }
716
- }
717
- }
718
- }
719
-
720
- :deep(.flatmap-marker-popup) {
721
- background-color: #f0f0f000 !important;
722
- box-shadow: none !important;
723
- }
724
- </style>
725
-
726
- <style lang="scss">
727
-
728
- .multi-container {
729
- --el-color-primary: #8300BF;
730
- }
731
-
1
+ <template>
2
+ <div class="multi-container" ref="multiContainer">
3
+ <div style="position: absolute; z-index: 10" v-if="!disableUI">
4
+ <div class="species-display-text">Species</div>
5
+ <el-popover
6
+ content="Select a species"
7
+ placement="right"
8
+ trigger="manual"
9
+ popper-class="flatmap-popper right-popper"
10
+ :visible="helpMode"
11
+ :teleported="false"
12
+ ref="selectPopover"
13
+ >
14
+ <template #reference>
15
+ <el-select
16
+ id="flatmap-select"
17
+ :teleported="false"
18
+ v-model="activeSpecies"
19
+ placeholder="Select"
20
+ class="select-box"
21
+ popper-class="flatmap-dropdown"
22
+ @change="setSpecies"
23
+ >
24
+ <el-option
25
+ v-for="(item, key) in speciesList"
26
+ :key="key"
27
+ :label="key"
28
+ :value="key"
29
+ >
30
+ <span class="select-box-icon">
31
+ <i :class="item.iconClass"></i>
32
+ </span>
33
+ {{ key }}
34
+ </el-option>
35
+ </el-select>
36
+ </template>
37
+
38
+ </el-popover>
39
+ </div>
40
+ <FlatmapVuer
41
+ v-for="(item, key) in speciesList"
42
+ :key="key"
43
+ v-show="activeSpecies == key"
44
+ :entry="item.taxo"
45
+ :uuid="item.uuid"
46
+ :biologicalSex="item.biologicalSex"
47
+ :displayWarning="item.displayWarning"
48
+ :displayLatestChanges="item.displayLatestChanges"
49
+ :isLegacy="item.isLegacy"
50
+ :ref="key"
51
+ :enableOpenMapUI="enableOpenMapUI"
52
+ :openMapOptions="openMapOptions"
53
+ :disableUI="disableUI"
54
+ @view-latest-map="viewLatestMap"
55
+ @resource-selected="resourceSelected"
56
+ @ready="FlatmapReady"
57
+ @pan-zoom-callback="panZoomCallback"
58
+ @open-map="
59
+ /**
60
+ * This event is emitted when the user chooses a different map option
61
+ * from ``openMapOptions`` props.
62
+ * @arg $event
63
+ */
64
+ $emit('open-map', $event)"
65
+ :minZoom="minZoom"
66
+ :helpMode="helpMode"
67
+ :renderAtMounted="renderAtMounted"
68
+ :displayMinimap="displayMinimap"
69
+ :showStarInLegend="showStarInLegend"
70
+ style="height: 100%"
71
+ :flatmapAPI="flatmapAPI"
72
+ :sparcAPI="sparcAPI"
73
+ />
74
+ </div>
75
+ </template>
76
+
77
+ <script>
78
+ /* eslint-disable no-alert, no-console */
79
+ import { reactive } from 'vue'
80
+ import EventBus from './EventBus'
81
+ import FlatmapVuer from './FlatmapVuer.vue'
82
+ import * as flatmap from '@abi-software/flatmap-viewer'
83
+ import {
84
+ ElCol as Col,
85
+ ElOption as Option,
86
+ ElSelect as Select,
87
+ ElRow as Row,
88
+ ElPopover as Popover,
89
+ } from 'element-plus'
90
+
91
+ const TAXON_UUID = {
92
+ 'NCBITaxon:10114': '01fedbf9-d783-509c-a10c-827941ab13da',
93
+ 'NCBITaxon:9823': 'a336ac04-24db-561f-a25f-1c994fe17410',
94
+ 'NCBITaxon:9606': '42ed6323-f645-5fbe-bada-9581819cf689',
95
+ 'NCBITaxon:10090': '25285fab-48a0-5620-a6a0-f9a0374837d5',
96
+ 'NCBITaxon:9685': '73060497-46a6-52bf-b975-cac511c127cb',
97
+ }
98
+
99
+ /**
100
+ * A vue component to show a flatmap from the list of multiple flatmap data.
101
+ */
102
+ export default {
103
+ name: 'MultiFlatmapVuer',
104
+ components: {
105
+ Col,
106
+ Row,
107
+ Option,
108
+ Select,
109
+ Popover,
110
+ FlatmapVuer,
111
+ },
112
+ beforeMount() {
113
+ //List for resolving the promise in initialise
114
+ //if initialise is called multiple times
115
+ this._resolveList = []
116
+ this._initialised = false
117
+ },
118
+ mounted: function () {
119
+ this.initialise()
120
+ EventBus.on('onActionClick', (action) => {
121
+ this.resourceSelected(action)
122
+ })
123
+ },
124
+ methods: {
125
+ /**
126
+ * @vuese
127
+ * Function to initialise the component when mounted.
128
+ * It returns a promise.
129
+ */
130
+ initialise: function () {
131
+ return new Promise((resolve) => {
132
+ if (this.requireInitialisation) {
133
+ //It has not been initialised yet
134
+ this.requireInitialisation = false
135
+ fetch(this.flatmapAPI)
136
+ .then((response) => response.json())
137
+ .then((data) => {
138
+ //Check each key in the provided availableSpecies against the one
139
+ Object.keys(this.availableSpecies).forEach((key) => {
140
+ // FIrst look through the uuid
141
+ const uuid = this.availableSpecies[key].uuid
142
+ if (uuid && data.map((e) => e.uuid).indexOf(uuid) > 0) {
143
+ this.speciesList[key] = reactive(this.availableSpecies[key])
144
+ } else {
145
+ for (let i = 0; i < data.length; i++) {
146
+ if (this.availableSpecies[key].taxo === data[i].taxon) {
147
+ if (this.availableSpecies[key].biologicalSex) {
148
+ if (
149
+ data[i].biologicalSex &&
150
+ data[i].biologicalSex ===
151
+ this.availableSpecies[key].biologicalSex
152
+ ) {
153
+ this.speciesList[key] = reactive(this.availableSpecies[key])
154
+ break
155
+ }
156
+ } else {
157
+ this.speciesList[key] = reactive(this.availableSpecies[key])
158
+ break
159
+ }
160
+ }
161
+ }
162
+ }
163
+ })
164
+ //Use the state species if it does not have any other species information
165
+ let species = this.initial
166
+ if (this.state) {
167
+ const mapState = this.state.state
168
+ if (
169
+ (!mapState || (!mapState.uuid && !mapState.entry)) &&
170
+ this.state.species
171
+ )
172
+ species = this.state.species
173
+ else species = undefined
174
+ }
175
+ if (species) {
176
+ //No state resuming, set the current flatmap to {this.initial}
177
+ if (species && this.speciesList[species] !== undefined) {
178
+ this.activeSpecies = species
179
+ } else {
180
+ this.activeSpecies = Object.keys(this.speciesList)[0]
181
+ }
182
+ this.setSpecies(
183
+ this.activeSpecies,
184
+ this.state ? this.state.state : undefined,
185
+ 5
186
+ )
187
+ }
188
+ this._initialised = true
189
+ resolve()
190
+ //Resolve all other promises resolve in the list
191
+ this._resolveList.forEach((other) => {
192
+ other()
193
+ })
194
+ })
195
+ } else if (this._initialised) {
196
+ //resolve as it has been initialised
197
+ resolve()
198
+ } else {
199
+ //resolve when the async initialisation is finished
200
+ this._resolveList.push(resolve)
201
+ }
202
+ })
203
+ },
204
+ /**
205
+ * @vuese
206
+ * Function to emit ``resource-selected`` event with provided ``resource``.
207
+ * @arg action
208
+ */
209
+ resourceSelected: function (action) {
210
+ /**
211
+ * This event is emitted by ``resourceSelected`` method.
212
+ */
213
+ this.$emit('resource-selected', action)
214
+ },
215
+ /**
216
+ * @vuese
217
+ * Function to emit ``ready`` event after the flatmap is loaded.
218
+ * @arg component
219
+ */
220
+ FlatmapReady: function (component) {
221
+ /**
222
+ * This event is emitted by ``FlatmapReady`` method after the flatmap is loaded.
223
+ * @arg component
224
+ */
225
+ this.$emit('ready', component)
226
+ },
227
+ /**
228
+ * @vuese
229
+ * Function to get the current active map.
230
+ */
231
+ getCurrentFlatmap: function () {
232
+ return this.$refs[this.activeSpecies][0]
233
+ },
234
+ /**
235
+ * @vuese
236
+ * Function to emit ``pan-zoom-callback`` event
237
+ * from the event emitted in ``callback`` function from ``MapManager.loadMap()``.
238
+ * @arg payload
239
+ */
240
+ panZoomCallback: function (payload) {
241
+ /**
242
+ * The event emitted by ``panZoomCallback`` method.
243
+ * @arg payload
244
+ */
245
+ this.$emit('pan-zoom-callback', payload)
246
+ },
247
+ /**
248
+ * @vuese
249
+ * Function to show popup on map.
250
+ * @arg featureId,
251
+ * @arg node,
252
+ * @arg options
253
+ */
254
+ showPopup: function (featureId, node, options) {
255
+ let map = this.getCurrentFlatmap()
256
+ map.showPopup(featureId, node, options)
257
+ },
258
+ /**
259
+ * @vuese
260
+ * Function to show marker popup.
261
+ * @arg featureId,
262
+ * @arg node,
263
+ * @arg options
264
+ */
265
+ showMarkerPopup: function (featureId, node, options) {
266
+ let map = this.getCurrentFlatmap()
267
+ map.showMarkerPopup(featureId, node, options)
268
+ },
269
+ /**
270
+ * @vuese
271
+ * Function to set species.
272
+ * This function is called on the first load and
273
+ * when user changes the species.
274
+ * @arg species,
275
+ * @arg state,
276
+ * @arg numberOfRetry
277
+ */
278
+ setSpecies: function (species, state, numberOfRetry) {
279
+ if (this.$refs && species in this.$refs) {
280
+ this.activeSpecies = species
281
+ this.$refs[this.activeSpecies][0].createFlatmap(state)
282
+ /**
283
+ * This event is emitted by ``setSpecies`` method.
284
+ * Emitted on first load and when user changes species.
285
+ * @arg activeSpecies
286
+ */
287
+ this.$emit('flatmapChanged', this.activeSpecies)
288
+ } else if (numberOfRetry) {
289
+ const retry = numberOfRetry - 1
290
+ if (retry >= 0) {
291
+ this.$nextTick(() => {
292
+ this.setSpecies(species, state, retry)
293
+ })
294
+ }
295
+ }
296
+ },
297
+ /**
298
+ * @vuese
299
+ * Function to switch to the latest existing map from
300
+ * a legacy map of the same species.
301
+ * @arg state
302
+ *
303
+ * @private
304
+ */
305
+ viewLatestMap: function (state) {
306
+ const keys = Object.keys(this.speciesList)
307
+ for (let i = 0; i < keys.length; i++) {
308
+ const species = this.speciesList[keys[i]]
309
+ if (
310
+ !species.isLegacy &&
311
+ species.taxo === state.entry &&
312
+ species.biologicalSex === state.biologicalSex
313
+ ) {
314
+ this.setSpecies(keys[i], state, 0)
315
+ return
316
+ }
317
+ }
318
+ },
319
+ /**
320
+ * @vuese
321
+ * Create a legacy entry with the provided information
322
+ * @arg state,
323
+ * @arg taxo,
324
+ * @arg uuid
325
+ *
326
+ * @private
327
+ */
328
+ createLegacyEntry: function (state, taxo, uuid) {
329
+ if (uuid && taxo) {
330
+ let name = 'Legacy'
331
+ if (state.species) {
332
+ if (state.species.slice(0, 6) === 'Legacy') name = state.species
333
+ else name = name + ` ${state.species}`
334
+ }
335
+ this.speciesList[name] = reactive({
336
+ taxo: taxo,
337
+ isLegacy: true,
338
+ displayWarning: true,
339
+ })
340
+ return {
341
+ species: name,
342
+ state: {
343
+ entry: taxo,
344
+ uuid: uuid,
345
+ viewport: state.state.viewport,
346
+ searchTerm: state.state.searchTerm,
347
+ },
348
+ }
349
+ }
350
+ },
351
+ /**
352
+ * @vuese
353
+ * Function used to translate the legacy map state to one that can be used in current
354
+ * flatmap if required. If it is a legacy, an Select entry will be added
355
+ * @arg state
356
+ *
357
+ * @private
358
+ */
359
+ updateState: function (state) {
360
+ return new Promise((resolve) => {
361
+ if (state && state.state) {
362
+ const mapState = state.state
363
+ //uuid is not in the state, this is a legacy map
364
+ if (!mapState.uuid) {
365
+ if (mapState.entry) {
366
+ const uuid =
367
+ mapState.entry in TAXON_UUID
368
+ ? TAXON_UUID[mapState.entry]
369
+ : undefined
370
+ const newState = this.createLegacyEntry(
371
+ state,
372
+ mapState.entry,
373
+ uuid
374
+ )
375
+ resolve(newState ? newState : state)
376
+ }
377
+ } else if (mapState.entry) {
378
+ //uuid is in the state but should be checked if it is the latest map
379
+ //for that taxon
380
+ return new Promise(() => {
381
+ const mapManager = new flatmap.MapManager(this.flatmapAPI)
382
+ //mapManager.findMap_ is an async function so we need to wrap this with a promise
383
+ const identifier = { taxon: mapState.entry }
384
+ if (mapState.biologicalSex)
385
+ identifier['biologicalSex'] = mapState.biologicalSex
386
+ mapManager
387
+ .findMap_(identifier)
388
+ .then((map) => {
389
+ if (map.uuid !== mapState.uuid) {
390
+ return this.createLegacyEntry(
391
+ state,
392
+ mapState.entry,
393
+ mapState.uuid
394
+ )
395
+ }
396
+ })
397
+ .then((newState) => {
398
+ resolve(newState ? newState : state)
399
+ })
400
+ .catch(() => {
401
+ resolve(state)
402
+ })
403
+ })
404
+ }
405
+ //Create a new state and add the legacy map to the select
406
+ }
407
+ resolve(state)
408
+ })
409
+ },
410
+ /**
411
+ * @vuese
412
+ * Function used for getting the current states of the scene. This exported states
413
+ * can be imported using the importStates method.
414
+ *
415
+ * @public
416
+ */
417
+ getState: function () {
418
+ let state = {
419
+ species: this.activeSpecies,
420
+ state: undefined,
421
+ }
422
+ let map = this.getCurrentFlatmap()
423
+ state.state = map.getState()
424
+ return state
425
+ },
426
+ /**
427
+ * @vuese
428
+ * Function used for importing the states of the scene. This exported states
429
+ * can be imported using the read states method.
430
+ * @arg state
431
+ *
432
+ * @public
433
+ */
434
+ setState: function (state) {
435
+ if (state) {
436
+ //Update state if required
437
+ this.updateState(state).then((currentState) => {
438
+ this.initialise().then(() => {
439
+ if (
440
+ currentState.species &&
441
+ currentState.species !== this.activeSpecies
442
+ ) {
443
+ this.setSpecies(currentState.species, currentState.state, 5)
444
+ } else if (currentState.state) {
445
+ let map = this.getCurrentFlatmap()
446
+ map.setState(currentState.state)
447
+ }
448
+ })
449
+ })
450
+ }
451
+ },
452
+ },
453
+ props: {
454
+ /**
455
+ * Initial species for the flatmap.
456
+ * This value will be ignored if a valid state object is provided.
457
+ */
458
+ initial: {
459
+ type: String,
460
+ default: '',
461
+ },
462
+ /**
463
+ * The minimum zoom level of the map.
464
+ */
465
+ minZoom: {
466
+ type: Number,
467
+ default: 4,
468
+ },
469
+ /**
470
+ * The option to create map on component mounted.
471
+ */
472
+ renderAtMounted: {
473
+ type: Boolean,
474
+ default: false,
475
+ },
476
+ /**
477
+ * The option to show tooltips for help mode.
478
+ */
479
+ helpMode: {
480
+ type: Boolean,
481
+ default: false,
482
+ },
483
+ /**
484
+ * The option to display minimap at the top-right corner of the map.
485
+ */
486
+ displayMinimap: {
487
+ type: Boolean,
488
+ default: false,
489
+ },
490
+ /**
491
+ * The option to show star in legend area.
492
+ */
493
+ showStarInLegend: {
494
+ type: Boolean,
495
+ default: false,
496
+ },
497
+ /**
498
+ * Flag to determine rather open map UI should be
499
+ * presented or not.
500
+ */
501
+ enableOpenMapUI: {
502
+ type: Boolean,
503
+ default: false,
504
+ },
505
+ /**
506
+ * The data to show different map options.
507
+ * Available at the bottom-left corner ("Open new map" tooltip).
508
+ */
509
+ openMapOptions: {
510
+ type: Array,
511
+ },
512
+ /**
513
+ * The available species data for different maps.
514
+ * This data is used for multi flatmaps.
515
+ */
516
+ availableSpecies: {
517
+ type: Object,
518
+ /**
519
+ * ```{
520
+ 'Human Female': {
521
+ taxo: 'NCBITaxon:9606',
522
+ biologicalSex: 'PATO:0000383',
523
+ iconClass: 'mapicon-icon_human',
524
+ displayWarning: true,
525
+ },
526
+ 'Human Male': {
527
+ taxo: 'NCBITaxon:9606',
528
+ biologicalSex: 'PATO:0000384',
529
+ iconClass: 'mapicon-icon_human',
530
+ displayWarning: true,
531
+ },
532
+ Rat: {
533
+ taxo: 'NCBITaxon:10114',
534
+ iconClass: 'mapicon-icon_rat',
535
+ displayLatestChanges: true,
536
+ },
537
+ Mouse: {
538
+ taxo: 'NCBITaxon:10090',
539
+ iconClass: 'mapicon-icon_mouse',
540
+ displayWarning: true,
541
+ },
542
+ Pig: {
543
+ taxo: 'NCBITaxon:9823',
544
+ iconClass: 'mapicon-icon_pig',
545
+ displayWarning: true,
546
+ },
547
+ Cat: {
548
+ taxo: 'NCBITaxon:9685',
549
+ iconClass: 'mapicon-icon_cat',
550
+ displayWarning: true,
551
+ },
552
+ }```
553
+ */
554
+ default: function () {
555
+ return {
556
+ 'Human Female': {
557
+ taxo: 'NCBITaxon:9606',
558
+ biologicalSex: 'PATO:0000383',
559
+ iconClass: 'mapicon-icon_human',
560
+ displayWarning: true,
561
+ },
562
+ 'Human Male': {
563
+ taxo: 'NCBITaxon:9606',
564
+ biologicalSex: 'PATO:0000384',
565
+ iconClass: 'mapicon-icon_human',
566
+ displayWarning: true,
567
+ },
568
+ Rat: {
569
+ taxo: 'NCBITaxon:10114',
570
+ iconClass: 'mapicon-icon_rat',
571
+ displayLatestChanges: true,
572
+ },
573
+ Mouse: {
574
+ taxo: 'NCBITaxon:10090',
575
+ iconClass: 'mapicon-icon_mouse',
576
+ displayWarning: true,
577
+ },
578
+ Pig: {
579
+ taxo: 'NCBITaxon:9823',
580
+ iconClass: 'mapicon-icon_pig',
581
+ displayWarning: true,
582
+ },
583
+ Cat: {
584
+ taxo: 'NCBITaxon:9685',
585
+ iconClass: 'mapicon-icon_cat',
586
+ displayWarning: true,
587
+ },
588
+ }
589
+ },
590
+ },
591
+ /**
592
+ * State containing state of the flatmap.
593
+ */
594
+ state: {
595
+ type: Object,
596
+ default: undefined,
597
+ },
598
+ /**
599
+ * Specify the endpoint of the flatmap server.
600
+ */
601
+ flatmapAPI: {
602
+ type: String,
603
+ default: 'https://mapcore-demo.org/current/flatmap/v3/',
604
+ },
605
+ /**
606
+ * Specify the endpoint of the SPARC API.
607
+ */
608
+ sparcAPI: {
609
+ type: String,
610
+ default: 'https://api.sparc.science/',
611
+ },
612
+ /**
613
+ * Flag to disable UIs on Map
614
+ */
615
+ disableUI: {
616
+ type: Boolean,
617
+ default: false,
618
+ }
619
+ },
620
+ data: function () {
621
+ return {
622
+ activeSpecies: undefined,
623
+ speciesList: {},
624
+ requireInitialisation: true,
625
+ }
626
+ },
627
+ watch: {
628
+ state: {
629
+ handler: function (state) {
630
+ this.setState(state)
631
+ },
632
+ immediate: true,
633
+ deep: true,
634
+ },
635
+ },
636
+ }
637
+ </script>
638
+
639
+ <style lang="scss" scoped>
640
+ .multi-container {
641
+ height: 100%;
642
+ width: 100%;
643
+ }
644
+
645
+ .species-display-text {
646
+ width: 47px;
647
+ height: 20px;
648
+ color: rgb(48, 49, 51);
649
+ font-size: 14px;
650
+ font-weight: normal;
651
+ line-height: 20px;
652
+ left: 24px;
653
+ top: 16px;
654
+ position: absolute;
655
+ }
656
+
657
+ .select-box {
658
+ width: 120px;
659
+ border-radius: 4px;
660
+ border: 1px solid rgb(144, 147, 153);
661
+ background-color: var(--white);
662
+ font-weight: 500;
663
+ color: rgb(48, 49, 51);
664
+ left: 16px;
665
+ top: 44px;
666
+ position: absolute;
667
+ :deep(.el-input__inner) {
668
+ color: rgb(48, 49, 51);
669
+ padding-top: 0.25em;
670
+ }
671
+ :deep() {
672
+ .el-input {
673
+ .el-input__wrapper{
674
+ &is-focus,
675
+ &:focus {
676
+ border: 1px solid $app-primary-color;
677
+ }
678
+ }
679
+ }
680
+ }
681
+
682
+ .select-box-icon {
683
+ display: inline-block;
684
+ width: 24px;
685
+ margin-right: 5px;
686
+ text-align: center;
687
+ }
688
+ }
689
+
690
+ .flatmap-dropdown {
691
+ min-width: 160px !important;
692
+ .el-select-dropdown__item {
693
+ white-space: nowrap;
694
+ text-align: left;
695
+ &.selected {
696
+ color: $app-primary-color;
697
+ font-weight: normal;
698
+ }
699
+ }
700
+ }
701
+
702
+ .flatmap-popper {
703
+ padding: 6px 4px;
704
+ font-size: 12px;
705
+ color: rgb(48, 49, 51);
706
+ background-color: #f3ecf6;
707
+ border: 1px solid $app-primary-color;
708
+ white-space: nowrap;
709
+ min-width: unset;
710
+ &.right-popper {
711
+ .popper__arrow {
712
+ border-right-color: $app-primary-color !important;
713
+ &:after {
714
+ border-right-color: #f3ecf6 !important;
715
+ }
716
+ }
717
+ }
718
+ }
719
+
720
+ :deep(.flatmap-marker-popup) {
721
+ background-color: #f0f0f000 !important;
722
+ box-shadow: none !important;
723
+ }
724
+ </style>
725
+
726
+ <style lang="scss">
727
+
728
+ .multi-container {
729
+ --el-color-primary: #8300BF;
730
+ }
731
+
732
732
  </style>