@abi-software/flatmapvuer 0.3.9 → 0.3.10-beta

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.
@@ -1,591 +1,594 @@
1
- <template>
2
- <div class="tooltip-container">
3
- <el-main v-if="content" class="main" v-loading="loading">
4
- <div class="block" v-if="content.title">
5
- <span class="title">{{capitalise(content.title)}}</span>
6
- </div>
7
- <div class="block" v-else>
8
- <span class="title">{{content.featureId}}</span>
9
- </div>
10
- <div class="content-container scrollbar">
11
- <!-- Currently we don't show the pubmed viewer, will remove once we are certain it won't be used -->
12
- <pubmed-viewer v-if="content.featureIds" v-show="false" class="block" :entry="content" @pubmedSearchUrl="pubmedSearchUrlUpdate"/>
13
- {{content.paths}}
14
- <div v-if="this.origins" class="block">
15
- <div>
16
- <span class="attribute-title">Origin</span>
17
- <el-popover
18
- width="250"
19
- trigger="hover"
20
- :append-to-body=false
21
- popper-class="popover-origin-help"
22
- >
23
- <i slot="reference" class="el-icon-warning-outline info"/>
24
- <span style="word-break: keep-all;">
25
- <i>Origin</i> {{originDescription}}
26
- </span>
27
- </el-popover>
28
- </div>
29
- <div v-for="(origin, i) in origins" class="attribute-content" :key="origin">
30
- {{ capitalise(origin) }}
31
- <div v-if="i != origins.length - 1" class="seperator"></div>
32
- </div>
33
- <el-button v-show="originsWithDatasets.length > 0" class="button" @click="openDendrites">
34
- Explore origin data
35
- </el-button>
36
- </div>
37
- <div v-if="this.components" class="block">
38
- <div class="attribute-title">Components</div>
39
- <div v-for="(component, i) in components" class="attribute-content" :key="component">
40
- {{ capitalise(component) }}
41
- <div v-if="i != components.length - 1" class="seperator"></div>
42
- </div>
43
- </div>
44
- <div v-if="this.destinations" class="block">
45
- <div>
46
- <span class="attribute-title">Destination</span>
47
- <el-popover
48
- width="250"
49
- trigger="hover"
50
- :append-to-body=false
51
- popper-class="popover-origin-help"
52
- >
53
- <i slot="reference" class="el-icon-warning-outline info"/>
54
- <span style="word-break: keep-all;">
55
- <i>Destination</i> is where the axons terminate
56
- </span>
57
- </el-popover>
58
- </div>
59
- <div v-for="(destination, i) in destinations" class="attribute-content" :key="destination">
60
- {{ capitalise(destination) }}
61
- <div v-if="i != destinations.length - 1" class="seperator"></div>
62
- </div>
63
- <el-button v-show="destinationsWithDatasets.length > 0" class="button" @click="openAxons">
64
- Explore destination data
65
- </el-button>
66
- </div>
67
-
68
- <!-- We will serach on components until we can search on neurons -->
69
- <el-button v-show="components.length > 0" class="button" @click="openAll">
70
- Search for data on components
71
- </el-button>
72
-
73
- <!-- Disable neuron search until it is ready -->
74
- <!-- <el-button v-for="action in content.actions" round :key="action.title"
75
- class="button" @click="resourceSelected(action)">
76
- <i v-if="action.title === 'Search for datasets' || action.title === 'View Dataset' " class="el-icon-coin"></i>
77
- {{action.title}}
78
- </el-button> -->
79
- <el-button v-if="pubmedSearchUrl != ''" class="button" icon="el-icon-notebook-2" @click="openUrl(pubmedSearchUrl)">
80
- Open publications in pubmed
81
- </el-button>
82
- </div>
83
- </el-main>
84
- </div>
85
- </template>
86
-
87
-
88
- <script>
89
- /* eslint-disable no-alert, no-console */
90
- import Vue from "vue";
91
- import {
92
- Button,
93
- Container,
94
- Header,
95
- Icon,
96
- Main
97
- } from "element-ui";
98
- import lang from "element-ui/lib/locale/lang/en";
99
- import locale from "element-ui/lib/locale";
100
- locale.use(lang);
101
- Vue.use(Button);
102
- Vue.use(Container);
103
- Vue.use(Header);
104
- Vue.use(Icon);
105
- Vue.use(Main);
106
-
107
- // pubmedviewer is currently not in use, but still under review so not ready to delete yet
108
- import PubmedViewer from './PubmedViewer.vue'
109
- import EventBus from './EventBus'
110
-
111
- const titleCase = (str) => {
112
- return str.replace(/\w\S*/g, (t) => { return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase() });
113
- }
114
-
115
- const inArray = function(ar1, ar2){
116
- let as1 = JSON.stringify(ar1)
117
- let as2 = JSON.stringify(ar2)
118
- return as1.indexOf(as2) !== -1
119
- }
120
-
121
- // remove duplicates by stringifying the objects
122
- const removeDuplicates = function(arrayOfAnything){
123
- return [...new Set(arrayOfAnything.map(e => JSON.stringify(e)))].map(e => JSON.parse(e))
124
- }
125
-
126
- const capitalise = function(str){
127
- if (str)
128
- return str.charAt(0).toUpperCase() + str.slice(1)
129
- return ""
130
- }
131
-
132
- export default {
133
- components: { PubmedViewer },
134
- name: "Tooltip",
135
- props: {
136
- visible: {
137
- type: Boolean,
138
- default: false
139
- },
140
- content: {
141
- type: Object,
142
- default: undefined
143
- },
144
- },
145
- data: function() {
146
- return {
147
- activeSpecies: undefined,
148
- appendToBody: false,
149
- pubmedSearchUrl: '',
150
- loading: false,
151
- destinations: [],
152
- origins: [],
153
- components: [],
154
- destinationsWithDatasets: [],
155
- originsWithDatasets: [],
156
- originDescriptions: {
157
- 'motor': 'is the location of the initial cell body of the circuit',
158
- 'sensory': 'is the location of the initial cell body in the PNS circuit'
159
- },
160
- componentsWithDatasets: [],
161
- uberons: [{id: undefined, name: undefined}]
162
- };
163
- },
164
- inject: ['sparcAPI', 'flatmapAPI'],
165
- watch: {
166
- 'content.featureIds': {
167
- handler: function(){
168
- this.pathwayQuery(this.content.featureIds)
169
- }
170
- }
171
- },
172
- mounted: function(){
173
- this.getOrganCuries()
174
- },
175
- computed: {
176
- originDescription: function(){
177
- if(this.content && this.content.title && this.content.title.toLowerCase().includes('motor')){
178
- return this.originDescriptions.motor
179
- } else {
180
- return this.originDescriptions.sensory
181
- }
182
- }
183
- },
184
- methods: {
185
- resourceSelected: function(action) {
186
- this.$emit("resource-selected", action);
187
- },
188
- titleCase: function(title){
189
- return titleCase(title)
190
- },
191
- capitalise: function(text){
192
- return capitalise(text)
193
- },
194
- onClose: function() {
195
- this.$emit("onClose")
196
- },
197
- openUrl: function(url){
198
- window.open(url, '_blank')
199
- },
200
- openAll: function(){
201
- EventBus.$emit('onActionClick', {type:'Facets', labels: this.components})
202
- },
203
- openAxons: function(){
204
- EventBus.$emit('onActionClick', {type:'Facets', labels: this.destinationsWithDatasets.map(a=>a.name)})
205
- },
206
- openDendrites: function(){
207
- EventBus.$emit('onActionClick', {type:'Facets', labels: this.originsWithDatasets.map(a=>a.name)})
208
- },
209
- pubmedSearchUrlUpdate: function (val){
210
- this.pubmedSearchUrl = val
211
- },
212
- findAllIdsFromConnectivity(connectivity){
213
- let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
214
- let nodes = [...new Set(dnodes)] // remove duplicates
215
- let found = []
216
- nodes.forEach(n=>{
217
- if (Array.isArray(n)){
218
- found.push(n.flat())
219
- } else {
220
- found.push(n)
221
- }
222
- })
223
- return [... new Set(found.flat())]
224
- },
225
- flattenConntectivity(connectivity){
226
- let dnodes = connectivity.flat() // get nodes from edgelist
227
- let nodes = [...new Set(dnodes)] // remove duplicates
228
- let found = []
229
- nodes.forEach(n=>{
230
- if (Array.isArray(n)){
231
- found.push(n.flat())
232
- } else {
233
- found.push(n)
234
- }
235
- })
236
- return found.flat()
237
- },
238
- findComponents: function(connectivity){
239
- let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
240
- let nodes = removeDuplicates(dnodes)
241
-
242
- let found = []
243
- let terminal = false
244
- nodes.forEach(node=>{
245
- terminal = false
246
- // Check if the node is an destination or origin (note that they are labelled dendrite and axon as opposed to origin and destination)
247
- if(inArray(connectivity.axons,node)){
248
- terminal = true
249
- }
250
- if(inArray(connectivity.dendrites, node)){
251
- terminal = true
252
- }
253
- if (!terminal){
254
- found.push(node)
255
- }
256
- })
257
-
258
- return found
259
- },
260
- getOrganCuries: function(){
261
- fetch(`${this.sparcAPI}get-organ-curies/`)
262
- .then(response=>response.json())
263
- .then(data=>{
264
- this.uberons = data.uberon.array
265
- })
266
- },
267
- buildConnectivitySqlStatement: function(keastIds) {
268
- let sql = 'select knowledge from knowledge where entity in ('
269
- if (keastIds.length === 1) {
270
- sql += `'${keastIds[0]}')`
271
- } else if (keastIds.length > 1) {
272
- for (let i in keastIds) {
273
- sql += `'${keastIds[i]}'${i >= keastIds.length - 1 ? ')' : ','} `
274
- }
275
- }
276
- return sql
277
- },
278
- buildLabelSqlStatement: function(uberons) {
279
- let sql = 'select entity, label from labels where entity in ('
280
- if (uberons.length === 1) {
281
- sql += `'${uberons[0]}')`
282
- } else if (uberons.length > 1) {
283
- for (let i in uberons) {
284
- sql += `'${uberons[i]}'${i >= uberons.length - 1 ? ')' : ','} `
285
- }
286
- }
287
- return sql
288
- },
289
- createLabelLookup: function(uberons) {
290
- return new Promise(resolve=> {
291
- let uberonMap = {}
292
- const data = { sql: this.buildLabelSqlStatement(uberons)}
293
- fetch(`${this.flatmapAPI}knowledge/query/`, {
294
- method: 'POST',
295
- headers: {
296
- 'Content-Type': 'application/json',
297
- },
298
- body: JSON.stringify(data),
299
- })
300
- .then(response => response.json())
301
- .then(payload => {
302
- const entity = payload.keys.indexOf("entity");
303
- const label = payload.keys.indexOf("label");
304
- if (entity > -1 && label > -1) {
305
- payload.values.forEach(pair => {
306
- uberonMap[pair[entity]] = pair[label];
307
- });
308
- }
309
- resolve(uberonMap)
310
- })
311
- })
312
- },
313
- createComponentsLabelList: function(components, lookUp){
314
- let labelList = []
315
- components.forEach(n=>{
316
- labelList.push(this.createLabelFromNeuralNode(n[0]), lookUp)
317
- if (n.length === 2){
318
- labelList.push(this.createLabelFromNeuralNode(n[1]), lookUp)
319
- }
320
- })
321
- return labelList
322
- },
323
- createLabelFromNeuralNode: function(node, lookUp){
324
- let label = lookUp[node[0]]
325
- if (node.length === 2 && node[1].length > 0){
326
- node[1].forEach(n=>{
327
- if (lookUp[n] == undefined){
328
- label += `, ${n}`
329
- } else {
330
- label += `, ${lookUp[n]}`
331
- }
332
- })
333
- }
334
- return label
335
- },
336
- pathwayQuery: function(keastIds){
337
- this.destinations = []
338
- this.origins = []
339
- this.components = []
340
- this.loading = true
341
- if (!keastIds || keastIds.length == 0) return
342
- const data = { sql: this.buildConnectivitySqlStatement(keastIds)};
343
- fetch(`${this.flatmapAPI}knowledge/query/`, {
344
- method: 'POST',
345
- headers: {
346
- 'Content-Type': 'application/json',
347
- },
348
- body: JSON.stringify(data),
349
- })
350
- .then(response => response.json())
351
- .then(data => {
352
- let connectivity = JSON.parse(data.values[0][0])
353
- // Filter the origin and destinations from components
354
- let components = this.findComponents(connectivity)
355
-
356
- // process the nodes for finding datasets
357
- let componentsFlat = this.flattenConntectivity(components)
358
- let axons = removeDuplicates(connectivity.axons)
359
- let dendrites = removeDuplicates(connectivity.dendrites)
360
- let axonsFlat = this.flattenConntectivity(axons)
361
- let dendritesFlat = this.flattenConntectivity(dendrites)
362
-
363
- let conIds = this.findAllIdsFromConnectivity(connectivity) // Create list of ids to get labels for
364
-
365
- // Create readable labels from the nodes. Setting this to 'this.origins' updates the display
366
- this.createLabelLookup(conIds).then(lookUp=>{
367
- this.destinations = axons.map(a=>this.createLabelFromNeuralNode(a,lookUp))
368
- this.origins = dendrites.map(d=>this.createLabelFromNeuralNode(d,lookUp))
369
- this.components = components.map(c=>this.createLabelFromNeuralNode(c, lookUp))
370
- })
371
-
372
- // Filter for the anatomy which is annotated on datasets
373
- this.destinationsWithDatasets = this.uberons.filter(ub => axonsFlat.indexOf(ub.id) !== -1)
374
- this.originsWithDatasets = this.uberons.filter(ub => dendritesFlat.indexOf(ub.id) !== -1)
375
- this.componentsWithDatasets = this.uberons.filter(ub => componentsFlat.indexOf(ub.id) !== -1)
376
- this.loading = false
377
- })
378
- .catch((error) => {
379
- console.error('Error:', error);
380
- })
381
- }
382
- }
383
- };
384
- </script>
385
-
386
- <style scoped lang="scss">
387
- @import "~element-ui/packages/theme-chalk/src/button";
388
- @import "~element-ui/packages/theme-chalk/src/container";
389
- @import "~element-ui/packages/theme-chalk/src/header";
390
- @import "~element-ui/packages/theme-chalk/src/main";
391
-
392
- .tooltip-container {
393
- text-align:justify;
394
- border-radius: 4px;
395
- box-shadow: 0 1px 2px rgba(0,0,0,.1);
396
- pointer-events: auto;
397
- background: #fff;
398
- border: 1px solid $app-primary-color;
399
- display: flex;
400
- justify-content: center;
401
- align-items: center;
402
- }
403
-
404
- .display {
405
- width: 44px;
406
- word-break: normal;
407
- }
408
-
409
- .title {
410
- text-align: left;
411
- width: 16em;
412
- line-height: 1.5em !important;
413
- font-size: 1em;
414
- font-family: Helvetica;
415
- font-weight: 500;
416
- /* font-weight: bold; */
417
- padding-bottom: 8px;
418
- }
419
-
420
- .block {
421
- margin-bottom: 1.5em;
422
- }
423
-
424
- .pub {
425
- width: 16rem;
426
- }
427
-
428
- .icon {
429
- right: 0px;
430
- position: absolute;
431
- top: 10px;
432
- }
433
-
434
- .icon:hover {
435
- cursor: pointer;
436
- }
437
-
438
- .popover-origin-help {
439
- text-transform: none !important; // need to overide the tooltip text transform
440
- }
441
-
442
- .info{
443
- transform: rotate(180deg);
444
- color: #8300bf;
445
- margin-left: 8px;
446
- }
447
-
448
- .seperator {
449
- width:90%;
450
- height:0.5px;
451
- background-color:#bfbec2;
452
- }
453
-
454
- .main {
455
- font-size: 14px;
456
- text-align: left;
457
- line-height: 1.5em;
458
- font-family: Helvetica;
459
- font-weight: 400;
460
- /* outline: thin red solid; */
461
- padding: 1em !important;
462
- overflow: hidden;
463
- min-width: 16rem;
464
- }
465
-
466
- .title{
467
- font-size: 18px;
468
- font-weight: 500;
469
- font-weight: bold;
470
- padding-bottom: 8px;
471
- color: rgb(131, 0, 191);
472
-
473
- }
474
-
475
- .attribute-title{
476
- font-size: 16px;
477
- font-weight: 600;
478
- /* font-weight: bold; */
479
- text-transform: uppercase;
480
- }
481
-
482
- .attribute-content{
483
- font-size: 14px;
484
- font-weight: 500;
485
- }
486
-
487
- .popover-container {
488
- height: 100%;
489
- width: 100%;
490
- }
491
-
492
- .main {
493
- .el-button.is-round{
494
- border-radius: 4px;
495
- padding: 9px 20px 10px 20px;
496
- display: flex;
497
- height: 36px;
498
- }
499
- }
500
-
501
- .button {
502
- margin-left: 0px !important;
503
- margin-top: 0px !important;
504
- font-size: 14px !important;
505
- background-color: $app-primary-color;
506
- color: #fff;
507
- &+.button {
508
- margin-top: 10px !important;
509
- }
510
- &:hover {
511
- color: #fff !important;
512
- background: #ac76c5 !important;
513
- border: 1px solid #ac76c5 !important;
514
- }
515
- }
516
-
517
- .tooltip-container{
518
- &::after, &::before {
519
- content: '';
520
- display: block;
521
- position: absolute;
522
- width: 0;
523
- height: 0;
524
- border-style: solid;
525
- flex-shrink: 0;
526
- }
527
- }
528
-
529
- .mapboxgl-popup-anchor-bottom {
530
- .tooltip-container {
531
- &::after, &::before {
532
- top: 100%;
533
- border-width: 12px;
534
- }
535
- &::after {
536
- margin-top:-1px;
537
- border-color: rgb(255, 255, 255) transparent transparent transparent ;
538
- }
539
- &::before {
540
- margin: 0 auto;
541
- border-color: $app-primary-color transparent transparent transparent ;
542
- }
543
- }
544
- }
545
-
546
- .mapboxgl-popup-anchor-top {
547
- .tooltip-container {
548
- &::after, &::before {
549
- top: -24px;
550
- border-width: 12px;
551
- }
552
- &::after {
553
- margin-top: 1px;
554
- border-color: transparent transparent rgb(255, 255, 255) transparent ;
555
- }
556
- &::before {
557
- margin: 0 auto;
558
- border-color: transparent transparent $app-primary-color transparent ;
559
- }
560
- }
561
- }
562
-
563
- .content-container {
564
- overflow-y: scroll;
565
- scrollbar-width: thin !important;
566
- height: 200px;
567
- }
568
-
569
- .scrollbar::-webkit-scrollbar-track {
570
- border-radius: 10px;
571
- background-color: #f5f5f5;
572
- }
573
-
574
- .scrollbar::-webkit-scrollbar {
575
- width: 12px;
576
- right: -12px;
577
- background-color: #f5f5f5;
578
- }
579
-
580
- .scrollbar::-webkit-scrollbar-thumb {
581
- border-radius: 4px;
582
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
583
- background-color: #979797;
584
- }
585
-
586
-
587
- /* Fix for chrome bug where under triangle pops up above one on top of it */
588
- .selector:not(*:root), .tooltip-container::after{
589
- top: 99.4%;
590
- }
591
- </style>
1
+ <template>
2
+ <div class="tooltip-container">
3
+ <el-main v-if="content" class="main" v-loading="loading">
4
+ <div class="block" v-if="content.title">
5
+ <span class="title">{{capitalise(content.title)}}</span>
6
+ </div>
7
+ <div class="block" v-else>
8
+ <span class="title">{{content.featureId}}</span>
9
+ </div>
10
+ <div class="content-container scrollbar">
11
+ {{content.paths}}
12
+ <div v-if="this.origins" class="block">
13
+ <div>
14
+ <span class="attribute-title">Origin</span>
15
+ <el-popover
16
+ width="250"
17
+ trigger="hover"
18
+ :append-to-body=false
19
+ popper-class="popover-origin-help"
20
+ >
21
+ <i slot="reference" class="el-icon-warning-outline info"/>
22
+ <span style="word-break: keep-all;">
23
+ <i>Origin</i> {{originDescription}}
24
+ </span>
25
+ </el-popover>
26
+ </div>
27
+ <div v-for="(origin, i) in origins" class="attribute-content" :key="origin">
28
+ {{ capitalise(origin) }}
29
+ <div v-if="i != origins.length - 1" class="seperator"></div>
30
+ </div>
31
+ <el-button v-show="originsWithDatasets.length > 0" class="button" @click="openDendrites">
32
+ Explore origin data
33
+ </el-button>
34
+ </div>
35
+ <div v-if="this.components" class="block">
36
+ <div class="attribute-title">Components</div>
37
+ <div v-for="(component, i) in components" class="attribute-content" :key="component">
38
+ {{ capitalise(component) }}
39
+ <div v-if="i != components.length - 1" class="seperator"></div>
40
+ </div>
41
+ </div>
42
+ <div v-if="this.destinations" class="block">
43
+ <div>
44
+ <span class="attribute-title">Destination</span>
45
+ <el-popover
46
+ width="250"
47
+ trigger="hover"
48
+ :append-to-body=false
49
+ popper-class="popover-origin-help"
50
+ >
51
+ <i slot="reference" class="el-icon-warning-outline info"/>
52
+ <span style="word-break: keep-all;">
53
+ <i>Destination</i> is where the axons terminate
54
+ </span>
55
+ </el-popover>
56
+ </div>
57
+ <div v-for="(destination, i) in destinations" class="attribute-content" :key="destination">
58
+ {{ capitalise(destination) }}
59
+ <div v-if="i != destinations.length - 1" class="seperator"></div>
60
+ </div>
61
+ <el-button v-show="destinationsWithDatasets.length > 0" class="button" @click="openAxons">
62
+ Explore destination data
63
+ </el-button>
64
+ </div>
65
+
66
+ <el-button v-show="components.length > 0" class="button" @click="openAll">
67
+ Search for data on components
68
+ </el-button>
69
+
70
+ <!-- pubmed-viewer is just used for processing pubmed requests (no display) -->
71
+ <pubmed-viewer v-if="content.featureIds" v-show="false" :entry="content" @pubmedSearchUrl="pubmedSearchUrlUpdate"/>
72
+ <el-button v-if="pubmedSearchUrl != ''" class="button" icon="el-icon-notebook-2" @click="openUrl(pubmedSearchUrl)">
73
+ Open publications in pubmed
74
+ </el-button>
75
+ </div>
76
+ </el-main>
77
+ </div>
78
+ </template>
79
+
80
+
81
+ <script>
82
+ /* eslint-disable no-alert, no-console */
83
+ import Vue from "vue";
84
+ import {
85
+ Button,
86
+ Container,
87
+ Header,
88
+ Icon,
89
+ Main
90
+ } from "element-ui";
91
+ import lang from "element-ui/lib/locale/lang/en";
92
+ import locale from "element-ui/lib/locale";
93
+ locale.use(lang);
94
+ Vue.use(Button);
95
+ Vue.use(Container);
96
+ Vue.use(Header);
97
+ Vue.use(Icon);
98
+ Vue.use(Main);
99
+
100
+ // pubmedviewer is currently not in use, but still under review so not ready to delete yet
101
+ import PubmedViewer from './PubmedViewer.vue'
102
+ import EventBus from './EventBus'
103
+
104
+ const titleCase = (str) => {
105
+ return str.replace(/\w\S*/g, (t) => { return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase() });
106
+ }
107
+
108
+ const inArray = function(ar1, ar2){
109
+ let as1 = JSON.stringify(ar1)
110
+ let as2 = JSON.stringify(ar2)
111
+ return as1.indexOf(as2) !== -1
112
+ }
113
+
114
+ // remove duplicates by stringifying the objects
115
+ const removeDuplicates = function(arrayOfAnything){
116
+ return [...new Set(arrayOfAnything.map(e => JSON.stringify(e)))].map(e => JSON.parse(e))
117
+ }
118
+
119
+ const capitalise = function(str){
120
+ if (str)
121
+ return str.charAt(0).toUpperCase() + str.slice(1)
122
+ return ""
123
+ }
124
+
125
+ export default {
126
+ components: { PubmedViewer },
127
+ name: "Tooltip",
128
+ props: {
129
+ visible: {
130
+ type: Boolean,
131
+ default: false
132
+ },
133
+ content: {
134
+ type: Object,
135
+ default: undefined
136
+ },
137
+ },
138
+ data: function() {
139
+ return {
140
+ activeSpecies: undefined,
141
+ appendToBody: false,
142
+ pubmedSearchUrl: '',
143
+ loading: false,
144
+ destinations: [],
145
+ origins: [],
146
+ components: [],
147
+ destinationsWithDatasets: [],
148
+ originsWithDatasets: [],
149
+ originDescriptions: {
150
+ 'motor': 'is the location of the initial cell body of the circuit',
151
+ 'sensory': 'is the location of the initial cell body in the PNS circuit'
152
+ },
153
+ componentsWithDatasets: [],
154
+ uberons: [{id: undefined, name: undefined}]
155
+ };
156
+ },
157
+ inject: ['sparcAPI', 'flatmapAPI'],
158
+ watch: {
159
+ 'content.featureIds': {
160
+ handler: function(){
161
+ this.pathwayQuery(this.content.featureIds)
162
+ }
163
+ }
164
+ },
165
+ mounted: function(){
166
+ this.getOrganCuries()
167
+ },
168
+ computed: {
169
+ originDescription: function(){
170
+ if(this.content && this.content.title && this.content.title.toLowerCase().includes('motor')){
171
+ return this.originDescriptions.motor
172
+ } else {
173
+ return this.originDescriptions.sensory
174
+ }
175
+ }
176
+ },
177
+ methods: {
178
+ resourceSelected: function(action) {
179
+ this.$emit("resource-selected", action);
180
+ },
181
+ titleCase: function(title){
182
+ return titleCase(title)
183
+ },
184
+ capitalise: function(text){
185
+ return capitalise(text)
186
+ },
187
+ onClose: function() {
188
+ this.$emit("onClose")
189
+ },
190
+ openUrl: function(url){
191
+ window.open(url, '_blank')
192
+ },
193
+ openAll: function(){
194
+ EventBus.$emit('onActionClick', {type:'Facets', labels: this.components})
195
+ },
196
+ openAxons: function(){
197
+ EventBus.$emit('onActionClick', {type:'Facets', labels: this.destinationsWithDatasets.map(a=>a.name)})
198
+ },
199
+ openDendrites: function(){
200
+ EventBus.$emit('onActionClick', {type:'Facets', labels: this.originsWithDatasets.map(a=>a.name)})
201
+ },
202
+ pubmedSearchUrlUpdate: function (val){
203
+ this.pubmedSearchUrl = val
204
+ },
205
+ findAllIdsFromConnectivity(connectivity){
206
+ let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
207
+ let nodes = [...new Set(dnodes)] // remove duplicates
208
+ let found = []
209
+ nodes.forEach(n=>{
210
+ if (Array.isArray(n)){
211
+ found.push(n.flat())
212
+ } else {
213
+ found.push(n)
214
+ }
215
+ })
216
+ return [... new Set(found.flat())]
217
+ },
218
+ flattenConntectivity(connectivity){
219
+ let dnodes = connectivity.flat() // get nodes from edgelist
220
+ let nodes = [...new Set(dnodes)] // remove duplicates
221
+ let found = []
222
+ nodes.forEach(n=>{
223
+ if (Array.isArray(n)){
224
+ found.push(n.flat())
225
+ } else {
226
+ found.push(n)
227
+ }
228
+ })
229
+ return found.flat()
230
+ },
231
+ findComponents: function(connectivity){
232
+ let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
233
+ let nodes = removeDuplicates(dnodes)
234
+
235
+ let found = []
236
+ let terminal = false
237
+ nodes.forEach(node=>{
238
+ terminal = false
239
+ // Check if the node is an destination or origin (note that they are labelled dendrite and axon as opposed to origin and destination)
240
+ if(inArray(connectivity.axons,node)){
241
+ terminal = true
242
+ }
243
+ if(inArray(connectivity.dendrites, node)){
244
+ terminal = true
245
+ }
246
+ if (!terminal){
247
+ found.push(node)
248
+ }
249
+ })
250
+
251
+ return found
252
+ },
253
+ getOrganCuries: function(){
254
+ fetch(`${this.sparcAPI}get-organ-curies/`)
255
+ .then(response=>response.json())
256
+ .then(data=>{
257
+ this.uberons = data.uberon.array
258
+ })
259
+ },
260
+ buildConnectivitySqlStatement: function(keastIds) {
261
+ let sql = 'select knowledge from knowledge where entity in ('
262
+ if (keastIds.length === 1) {
263
+ sql += `'${keastIds[0]}')`
264
+ } else if (keastIds.length > 1) {
265
+ for (let i in keastIds) {
266
+ sql += `'${keastIds[i]}'${i >= keastIds.length - 1 ? ')' : ','} `
267
+ }
268
+ }
269
+ return sql
270
+ },
271
+ buildLabelSqlStatement: function(uberons) {
272
+ let sql = 'select entity, label from labels where entity in ('
273
+ if (uberons.length === 1) {
274
+ sql += `'${uberons[0]}')`
275
+ } else if (uberons.length > 1) {
276
+ for (let i in uberons) {
277
+ sql += `'${uberons[i]}'${i >= uberons.length - 1 ? ')' : ','} `
278
+ }
279
+ }
280
+ return sql
281
+ },
282
+ createLabelLookup: function(uberons) {
283
+ return new Promise(resolve=> {
284
+ let uberonMap = {}
285
+ const data = { sql: this.buildLabelSqlStatement(uberons)}
286
+ fetch(`${this.flatmapAPI}knowledge/query/`, {
287
+ method: 'POST',
288
+ headers: {
289
+ 'Content-Type': 'application/json',
290
+ },
291
+ body: JSON.stringify(data),
292
+ })
293
+ .then(response => response.json())
294
+ .then(payload => {
295
+ const entity = payload.keys.indexOf("entity");
296
+ const label = payload.keys.indexOf("label");
297
+ if (entity > -1 && label > -1) {
298
+ payload.values.forEach(pair => {
299
+ uberonMap[pair[entity]] = pair[label];
300
+ });
301
+ }
302
+ resolve(uberonMap)
303
+ })
304
+ })
305
+ },
306
+ createComponentsLabelList: function(components, lookUp){
307
+ let labelList = []
308
+ components.forEach(n=>{
309
+ labelList.push(this.createLabelFromNeuralNode(n[0]), lookUp)
310
+ if (n.length === 2){
311
+ labelList.push(this.createLabelFromNeuralNode(n[1]), lookUp)
312
+ }
313
+ })
314
+ return labelList
315
+ },
316
+ createLabelFromNeuralNode: function(node, lookUp){
317
+ let label = lookUp[node[0]]
318
+ if (node.length === 2 && node[1].length > 0){
319
+ node[1].forEach(n=>{
320
+ if (lookUp[n] == undefined){
321
+ label += `, ${n}`
322
+ } else {
323
+ label += `, ${lookUp[n]}`
324
+ }
325
+ })
326
+ }
327
+ return label
328
+ },
329
+ processConnectivity(connectivity){
330
+ // Filter the origin and destinations from components
331
+ let components = this.findComponents(connectivity)
332
+
333
+ // Remove duplicates
334
+ let axons = removeDuplicates(connectivity.axons)
335
+ let dendrites = removeDuplicates(connectivity.dendrites)
336
+
337
+ // Create list of ids to get labels for
338
+ let conIds = this.findAllIdsFromConnectivity(connectivity)
339
+
340
+ // Create readable labels from the nodes. Setting this to 'this.origins' updates the display
341
+ this.createLabelLookup(conIds).then(lookUp=>{
342
+ this.destinations = axons.map(a=>this.createLabelFromNeuralNode(a,lookUp))
343
+ this.origins = dendrites.map(d=>this.createLabelFromNeuralNode(d,lookUp))
344
+ this.components = components.map(c=>this.createLabelFromNeuralNode(c, lookUp))
345
+ })
346
+
347
+ this.flattenAndFindDatasets(components, axons, dendrites)
348
+ },
349
+ flattenAndFindDatasets(components, axons, dendrites){
350
+
351
+ // process the nodes for finding datasets (Note this is not critical to the tooltip, only for the 'search on components' button)
352
+ let componentsFlat = this.flattenConntectivity(components)
353
+ let axonsFlat = this.flattenConntectivity(axons)
354
+ let dendritesFlat = this.flattenConntectivity(dendrites)
355
+
356
+ // Filter for the anatomy which is annotated on datasets
357
+ this.destinationsWithDatasets = this.uberons.filter(ub => axonsFlat.indexOf(ub.id) !== -1)
358
+ this.originsWithDatasets = this.uberons.filter(ub => dendritesFlat.indexOf(ub.id) !== -1)
359
+ this.componentsWithDatasets = this.uberons.filter(ub => componentsFlat.indexOf(ub.id) !== -1)
360
+ },
361
+ pathwayQuery: function(keastIds){
362
+ this.destinations = []
363
+ this.origins = []
364
+ this.components = []
365
+ this.loading = true
366
+ if (!keastIds || keastIds.length == 0) return
367
+ const data = { sql: this.buildConnectivitySqlStatement(keastIds)};
368
+ fetch(`${this.flatmapAPI}knowledge/query/`, {
369
+ method: 'POST',
370
+ headers: {
371
+ 'Content-Type': 'application/json',
372
+ },
373
+ body: JSON.stringify(data),
374
+ })
375
+ .then(response => response.json())
376
+ .then(data => {
377
+ let connectivity = JSON.parse(data.values[0][0])
378
+ this.processConnectivity(connectivity)
379
+ this.loading = false
380
+ })
381
+ .catch((error) => {
382
+ console.error('Error:', error);
383
+ })
384
+ }
385
+ }
386
+ };
387
+ </script>
388
+
389
+ <style scoped lang="scss">
390
+ @import "~element-ui/packages/theme-chalk/src/button";
391
+ @import "~element-ui/packages/theme-chalk/src/container";
392
+ @import "~element-ui/packages/theme-chalk/src/header";
393
+ @import "~element-ui/packages/theme-chalk/src/main";
394
+
395
+ .tooltip-container {
396
+ text-align:justify;
397
+ border-radius: 4px;
398
+ box-shadow: 0 1px 2px rgba(0,0,0,.1);
399
+ pointer-events: auto;
400
+ background: #fff;
401
+ border: 1px solid $app-primary-color;
402
+ display: flex;
403
+ justify-content: center;
404
+ align-items: center;
405
+ }
406
+
407
+ .display {
408
+ width: 44px;
409
+ word-break: normal;
410
+ }
411
+
412
+ .title {
413
+ text-align: left;
414
+ width: 16em;
415
+ line-height: 1.5em !important;
416
+ font-size: 1em;
417
+ font-family: Helvetica;
418
+ font-weight: 500;
419
+ /* font-weight: bold; */
420
+ padding-bottom: 8px;
421
+ }
422
+
423
+ .block {
424
+ margin-bottom: 1.5em;
425
+ }
426
+
427
+ .pub {
428
+ width: 16rem;
429
+ }
430
+
431
+ .icon {
432
+ right: 0px;
433
+ position: absolute;
434
+ top: 10px;
435
+ }
436
+
437
+ .icon:hover {
438
+ cursor: pointer;
439
+ }
440
+
441
+ .popover-origin-help {
442
+ text-transform: none !important; // need to overide the tooltip text transform
443
+ }
444
+
445
+ .info{
446
+ transform: rotate(180deg);
447
+ color: #8300bf;
448
+ margin-left: 8px;
449
+ }
450
+
451
+ .seperator {
452
+ width:90%;
453
+ height:0.5px;
454
+ background-color:#bfbec2;
455
+ }
456
+
457
+ .main {
458
+ font-size: 14px;
459
+ text-align: left;
460
+ line-height: 1.5em;
461
+ font-family: Helvetica;
462
+ font-weight: 400;
463
+ /* outline: thin red solid; */
464
+ padding: 1em !important;
465
+ overflow: hidden;
466
+ min-width: 16rem;
467
+ }
468
+
469
+ .title{
470
+ font-size: 18px;
471
+ font-weight: 500;
472
+ font-weight: bold;
473
+ padding-bottom: 8px;
474
+ color: rgb(131, 0, 191);
475
+
476
+ }
477
+
478
+ .attribute-title{
479
+ font-size: 16px;
480
+ font-weight: 600;
481
+ /* font-weight: bold; */
482
+ text-transform: uppercase;
483
+ }
484
+
485
+ .attribute-content{
486
+ font-size: 14px;
487
+ font-weight: 500;
488
+ }
489
+
490
+ .popover-container {
491
+ height: 100%;
492
+ width: 100%;
493
+ }
494
+
495
+ .main {
496
+ .el-button.is-round{
497
+ border-radius: 4px;
498
+ padding: 9px 20px 10px 20px;
499
+ display: flex;
500
+ height: 36px;
501
+ }
502
+ }
503
+
504
+ .button {
505
+ margin-left: 0px !important;
506
+ margin-top: 0px !important;
507
+ font-size: 14px !important;
508
+ background-color: $app-primary-color;
509
+ color: #fff;
510
+ &+.button {
511
+ margin-top: 10px !important;
512
+ }
513
+ &:hover {
514
+ color: #fff !important;
515
+ background: #ac76c5 !important;
516
+ border: 1px solid #ac76c5 !important;
517
+ }
518
+ }
519
+
520
+ .tooltip-container{
521
+ &::after, &::before {
522
+ content: '';
523
+ display: block;
524
+ position: absolute;
525
+ width: 0;
526
+ height: 0;
527
+ border-style: solid;
528
+ flex-shrink: 0;
529
+ }
530
+ }
531
+
532
+ .mapboxgl-popup-anchor-bottom {
533
+ .tooltip-container {
534
+ &::after, &::before {
535
+ top: 100%;
536
+ border-width: 12px;
537
+ }
538
+ &::after {
539
+ margin-top:-1px;
540
+ border-color: rgb(255, 255, 255) transparent transparent transparent ;
541
+ }
542
+ &::before {
543
+ margin: 0 auto;
544
+ border-color: $app-primary-color transparent transparent transparent ;
545
+ }
546
+ }
547
+ }
548
+
549
+ .mapboxgl-popup-anchor-top {
550
+ .tooltip-container {
551
+ &::after, &::before {
552
+ top: -24px;
553
+ border-width: 12px;
554
+ }
555
+ &::after {
556
+ margin-top: 1px;
557
+ border-color: transparent transparent rgb(255, 255, 255) transparent ;
558
+ }
559
+ &::before {
560
+ margin: 0 auto;
561
+ border-color: transparent transparent $app-primary-color transparent ;
562
+ }
563
+ }
564
+ }
565
+
566
+ .content-container {
567
+ overflow-y: scroll;
568
+ scrollbar-width: thin !important;
569
+ height: 200px;
570
+ }
571
+
572
+ .scrollbar::-webkit-scrollbar-track {
573
+ border-radius: 10px;
574
+ background-color: #f5f5f5;
575
+ }
576
+
577
+ .scrollbar::-webkit-scrollbar {
578
+ width: 12px;
579
+ right: -12px;
580
+ background-color: #f5f5f5;
581
+ }
582
+
583
+ .scrollbar::-webkit-scrollbar-thumb {
584
+ border-radius: 4px;
585
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
586
+ background-color: #979797;
587
+ }
588
+
589
+
590
+ /* Fix for chrome bug where under triangle pops up above one on top of it */
591
+ .selector:not(*:root), .tooltip-container::after{
592
+ top: 99.4%;
593
+ }
594
+ </style>