@abi-software/flatmapvuer 1.1.3 → 1.2.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 (41) 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 +43580 -38546
  5. package/dist/flatmapvuer.umd.cjs +189 -181
  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 +379 -379
  12. package/src/assets/_variables.scss +43 -43
  13. package/src/assets/styles.scss +5 -5
  14. package/src/components/AnnotationTool.vue +501 -501
  15. package/src/components/ConnectionDialog.vue +134 -134
  16. package/src/components/DrawTool.vue +502 -502
  17. package/src/components/EventBus.js +3 -3
  18. package/src/components/ExternalResourceCard.vue +109 -109
  19. package/src/components/FlatmapVuer.vue +3515 -3455
  20. package/src/components/HelpModeDialog.vue +360 -360
  21. package/src/components/MultiFlatmapVuer.vue +814 -814
  22. package/src/components/ProvenancePopup.vue +530 -530
  23. package/src/components/SelectionsGroup.vue +363 -363
  24. package/src/components/Tooltip.vue +50 -50
  25. package/src/components/TreeControls.vue +236 -236
  26. package/src/components/index.js +8 -8
  27. package/src/components/legends/DynamicLegends.vue +106 -106
  28. package/src/components/legends/SvgLegends.vue +112 -112
  29. package/src/icons/flatmap-marker.js +9 -1
  30. package/src/icons/fonts/mapicon-species.svg +14 -14
  31. package/src/icons/fonts/mapicon-species.ttf +0 -0
  32. package/src/icons/fonts/mapicon-species.woff +0 -0
  33. package/src/icons/mapicon-species-style.css +42 -42
  34. package/src/icons/yellowstar.js +5 -5
  35. package/src/legends/legend.svg +25 -25
  36. package/src/main.js +19 -19
  37. package/src/services/flatmapQueries.js +475 -475
  38. package/src/store/index.js +23 -23
  39. package/vite.config.js +73 -73
  40. package/vite.static-build.js +12 -12
  41. package/vuese-generator.js +64 -64
@@ -1,475 +1,475 @@
1
- /* eslint-disable no-alert, no-console */
2
- // remove duplicates by stringifying the objects
3
- const removeDuplicates = function (arrayOfAnything) {
4
- if (!arrayOfAnything) return []
5
- return [...new Set(arrayOfAnything.map((e) => JSON.stringify(e)))].map((e) =>
6
- JSON.parse(e)
7
- )
8
- }
9
-
10
- const cachedLabels = {}
11
-
12
- const findTaxonomyLabel = async function (flatmapAPI, taxonomy) {
13
- if (cachedLabels && cachedLabels.hasOwnProperty(taxonomy)) {
14
- return cachedLabels[taxonomy]
15
- }
16
-
17
- return new Promise((resolve) => {
18
- fetch(`${flatmapAPI}knowledge/label/${taxonomy}`, {
19
- method: 'GET',
20
- })
21
- .then((response) => response.json())
22
- .then((data) => {
23
- let label = data.label
24
- if (label === 'Mammalia') {
25
- label = 'Mammalia not otherwise specified'
26
- }
27
- cachedLabels[taxonomy] = label
28
- resolve(label)
29
- })
30
- .catch((error) => {
31
- console.error('Error:', error)
32
- cachedLabels[taxonomy] = taxonomy
33
- resolve(taxonomy)
34
- })
35
- })
36
- }
37
-
38
- const inArray = function (ar1, ar2) {
39
- if (!ar1 || !ar2) return false
40
- let as1 = JSON.stringify(ar1)
41
- let as2 = JSON.stringify(ar2)
42
- return as1.indexOf(as2) !== -1
43
- }
44
-
45
- let FlatmapQueries = function () {
46
- this.initialise = function (flatmapApi) {
47
- this.flatmapApi = flatmapApi
48
- this.destinations = []
49
- this.origins = []
50
- this.components = []
51
- this.urls = []
52
- this.controller = undefined
53
- this.uberons = []
54
- this.lookUp = []
55
- }
56
-
57
- this.createTooltipData = async function (eventData) {
58
- let hyperlinks = []
59
- if (
60
- eventData.feature.hyperlinks &&
61
- eventData.feature.hyperlinks.length > 0
62
- ) {
63
- hyperlinks = eventData.feature.hyperlinks
64
- } else {
65
- hyperlinks = this.urls.map((url) => ({ url: url, id: 'pubmed' }))
66
- }
67
- let taxonomyLabel = undefined
68
- if (eventData.provenanceTaxonomy) {
69
- taxonomyLabel = []
70
- for (let i = 0; eventData.provenanceTaxonomy.length > i; i++) {
71
- taxonomyLabel.push(
72
- await findTaxonomyLabel(
73
- this.flatmapAPI,
74
- eventData.provenanceTaxonomy[i]
75
- )
76
- )
77
- }
78
- }
79
-
80
- let tooltipData = {
81
- destinations: this.destinations,
82
- origins: this.origins,
83
- components: this.components,
84
- destinationsWithDatasets: this.destinationsWithDatasets,
85
- originsWithDatasets: this.originsWithDatasets,
86
- componentsWithDatasets: this.componentsWithDatasets,
87
- title: eventData.label,
88
- featureId: eventData.resource,
89
- hyperlinks: hyperlinks,
90
- provenanceTaxonomy: eventData.provenanceTaxonomy,
91
- provenanceTaxonomyLabel: taxonomyLabel,
92
- }
93
- return tooltipData
94
- }
95
-
96
- this.createComponentsLabelList = function (components, lookUp) {
97
- let labelList = []
98
- components.forEach((n) => {
99
- labelList.push(this.createLabelFromNeuralNode(n[0]), lookUp)
100
- if (n.length === 2) {
101
- labelList.push(this.createLabelFromNeuralNode(n[1]), lookUp)
102
- }
103
- })
104
- return labelList
105
- }
106
-
107
- this.createLabelLookup = function (uberons) {
108
- return new Promise((resolve) => {
109
- let uberonMap = {}
110
- this.uberons = []
111
- const data = { sql: this.buildLabelSqlStatement(uberons) }
112
- fetch(`${this.flatmapApi}knowledge/query/`, {
113
- method: 'POST',
114
- headers: {
115
- 'Content-Type': 'application/json',
116
- },
117
- body: JSON.stringify(data),
118
- })
119
- .then((response) => response.json())
120
- .then((payload) => {
121
- const entity = payload.keys.indexOf('entity')
122
- const label = payload.keys.indexOf('label')
123
- if (entity > -1 && label > -1) {
124
- payload.values.forEach((pair) => {
125
- uberonMap[pair[entity]] = pair[label]
126
- this.uberons.push({
127
- id: pair[entity],
128
- name: pair[label],
129
- })
130
- })
131
- }
132
- resolve(uberonMap)
133
- })
134
- })
135
- }
136
-
137
- this.buildConnectivitySqlStatement = function (keastIds) {
138
- let sql = 'select knowledge from knowledge where entity in ('
139
- if (keastIds.length === 1) {
140
- sql += `'${keastIds[0]}')`
141
- } else if (keastIds.length > 1) {
142
- for (let i in keastIds) {
143
- sql += `'${keastIds[i]}'${i >= keastIds.length - 1 ? ')' : ','} `
144
- }
145
- }
146
- return sql
147
- }
148
-
149
- this.buildLabelSqlStatement = function (uberons) {
150
- let sql = 'select entity, label from labels where entity in ('
151
- if (uberons.length === 1) {
152
- sql += `'${uberons[0]}')`
153
- } else if (uberons.length > 1) {
154
- for (let i in uberons) {
155
- sql += `'${uberons[i]}'${i >= uberons.length - 1 ? ')' : ','} `
156
- }
157
- }
158
- return sql
159
- }
160
-
161
- this.findAllIdsFromConnectivity = function (connectivity) {
162
- let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
163
- let nodes = [...new Set(dnodes)] // remove duplicates
164
- let found = []
165
- nodes.forEach((n) => {
166
- if (Array.isArray(n)) {
167
- found.push(n.flat())
168
- } else {
169
- found.push(n)
170
- }
171
- })
172
- return [...new Set(found.flat())]
173
- }
174
-
175
- this.flattenConntectivity = function (connectivity) {
176
- let dnodes = connectivity.flat() // get nodes from edgelist
177
- let nodes = [...new Set(dnodes)] // remove duplicates
178
- let found = []
179
- nodes.forEach((n) => {
180
- if (Array.isArray(n)) {
181
- found.push(n.flat())
182
- } else {
183
- found.push(n)
184
- }
185
- })
186
- return found.flat()
187
- }
188
-
189
- this.findComponents = function (connectivity) {
190
- let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
191
- let nodes = removeDuplicates(dnodes)
192
-
193
- let found = []
194
- let terminal = false
195
- nodes.forEach((node) => {
196
- terminal = false
197
- // Check if the node is an destination or origin (note that they are labelled dendrite and axon as opposed to origin and destination)
198
- if (inArray(connectivity.axons, node)) {
199
- terminal = true
200
- }
201
- if (inArray(connectivity.dendrites, node)) {
202
- terminal = true
203
- }
204
- if (!terminal) {
205
- found.push(node)
206
- }
207
- })
208
-
209
- return found
210
- }
211
-
212
- this.retrieveFlatmapKnowledgeForEvent = async function (eventData) {
213
- // check if there is an existing query
214
- if (this.controller) this.controller.abort()
215
-
216
- // set up the abort controller
217
- this.controller = new AbortController()
218
- const signal = this.controller.signal
219
-
220
- const keastIds = eventData.resource
221
- this.destinations = []
222
- this.origins = []
223
- this.components = []
224
- if (!keastIds || keastIds.length == 0) return
225
-
226
- let prom1 = this.queryForConnectivity(keastIds, signal) // This on returns a promise so dont need 'await'
227
- let prom2 = await this.pubmedQueryOnIds(eventData)
228
- let results = await Promise.all([prom1, prom2])
229
- return results
230
- }
231
-
232
- this.queryForConnectivity = function (keastIds, signal, processConnectivity=true) {
233
- const data = { sql: this.buildConnectivitySqlStatement(keastIds) }
234
- const headers = {
235
- method: 'POST',
236
- headers: {
237
- 'Content-Type': 'application/json',
238
- },
239
- body: JSON.stringify(data),
240
- ...(signal ? { signal: signal } : {}), // add signal to header if it exists
241
- }
242
- return new Promise((resolve) => {
243
- fetch(`${this.flatmapApi}knowledge/query/`, headers)
244
- .then((response) => response.json())
245
- .then((data) => {
246
- if (this.connectivityExists(data)) {
247
- let connectivity = JSON.parse(data.values[0][0])
248
- if (processConnectivity) {
249
- this.processConnectivity(connectivity).then((processedConnectivity) => {
250
- resolve(processedConnectivity)
251
- })
252
- }
253
- else resolve(connectivity)
254
- } else {
255
- resolve(false)
256
- }
257
- })
258
- .catch((error) => {
259
- console.error('Error:', error)
260
- resolve(false)
261
- })
262
- })
263
- }
264
-
265
- this.connectivityExists = function (data) {
266
- if (
267
- data.values &&
268
- data.values.length > 0 &&
269
- JSON.parse(data.values[0][0]).connectivity &&
270
- JSON.parse(data.values[0][0]).connectivity.length > 0
271
- ) {
272
- return true
273
- } else {
274
- return false
275
- }
276
- }
277
-
278
- // This function is used to determine if a node is a single node or a node with multiple children
279
- // Returns the id of the node if it is a single node, otherwise returns false
280
- this.findIfNodeIsSingle = function (node) {
281
- if (node.length === 1) { // If the node is in the form [id]
282
- console.error("Server returns a single node", node)
283
- return node[0]
284
- } else {
285
- if (node.length === 2 && node[1].length === 0) { // If the node is in the form [id, []]
286
- return node[0]
287
- } else {
288
- return false // If the node is in the form [id, [id1, id2]]
289
- }
290
- }
291
- }
292
-
293
- this.createLabelFromNeuralNode = function (node, lookUp) {
294
-
295
- // Check if the node is a single node or a node with multiple children
296
- let nodeIsSingle = this.findIfNodeIsSingle(node)
297
-
298
- // Case where node is in the form [id]
299
- if (nodeIsSingle) {
300
- return lookUp[nodeIsSingle]
301
- }
302
-
303
- // Case where node is in the form [id, [id1 (,id2)]]
304
- let label = lookUp[node[0]]
305
- if (node.length === 2 && node[1].length > 0) {
306
- node[1].forEach((n) => {
307
- if (lookUp[n] == undefined) {
308
- label += `, ${n}`
309
- } else {
310
- label += `, ${lookUp[n]}`
311
- }
312
- })
313
- }
314
- return label
315
- }
316
-
317
- this.flattenAndFindDatasets = function (components, axons, dendrites) {
318
- // process the nodes for finding datasets (Note this is not critical to the tooltip, only for the 'search on components' button)
319
- let componentsFlat = this.flattenConntectivity(components)
320
- let axonsFlat = this.flattenConntectivity(axons)
321
- let dendritesFlat = this.flattenConntectivity(dendrites)
322
-
323
- // Filter for the anatomy which is annotated on datasets
324
- this.destinationsWithDatasets = this.uberons.filter(
325
- (ub) => axonsFlat.indexOf(ub.id) !== -1
326
- )
327
- this.originsWithDatasets = this.uberons.filter(
328
- (ub) => dendritesFlat.indexOf(ub.id) !== -1
329
- )
330
- this.componentsWithDatasets = this.uberons.filter(
331
- (ub) => componentsFlat.indexOf(ub.id) !== -1
332
- )
333
- }
334
-
335
- this.processConnectivity = function (connectivity) {
336
- return new Promise((resolve) => {
337
- // Filter the origin and destinations from components
338
- let components = this.findComponents(connectivity)
339
-
340
- // Remove duplicates
341
- let axons = removeDuplicates(connectivity.axons)
342
- let dendrites = removeDuplicates(connectivity.dendrites)
343
-
344
- // Create list of ids to get labels for
345
- let conIds = this.findAllIdsFromConnectivity(connectivity)
346
-
347
- // Create readable labels from the nodes. Setting this to 'this.origins' updates the display
348
- this.createLabelLookup(conIds).then((lookUp) => {
349
- this.destinations = axons.map((a) =>
350
- this.createLabelFromNeuralNode(a, lookUp)
351
- )
352
- this.origins = dendrites.map((d) =>
353
- this.createLabelFromNeuralNode(d, lookUp)
354
- )
355
- this.components = components.map((c) =>
356
- this.createLabelFromNeuralNode(c, lookUp)
357
- )
358
- this.flattenAndFindDatasets(components, axons, dendrites)
359
- resolve({
360
- ids: {
361
- axons: axons,
362
- dendrites: dendrites,
363
- components: components,
364
- },
365
- labels: {
366
- destinations: this.destinations,
367
- origins: this.origins,
368
- components: this.components,
369
- }
370
- })
371
- })
372
- })
373
- }
374
-
375
- this.flattenConntectivity = function (connectivity) {
376
- let dnodes = connectivity.flat() // get nodes from edgelist
377
- let nodes = [...new Set(dnodes)] // remove duplicates
378
- let found = []
379
- nodes.forEach((n) => {
380
- if (Array.isArray(n)) {
381
- found.push(n.flat())
382
- } else {
383
- found.push(n)
384
- }
385
- })
386
- return found.flat()
387
- }
388
-
389
- this.stripPMIDPrefix = function (pubmedId) {
390
- return pubmedId.split(':')[1]
391
- }
392
-
393
- this.buildPubmedSqlStatement = function (keastIds) {
394
- let sql = 'select distinct publication from publications where entity in ('
395
- if (keastIds.length === 1) {
396
- sql += `'${keastIds[0]}')`
397
- } else if (keastIds.length > 1) {
398
- for (let i in keastIds) {
399
- sql += `'${keastIds[i]}'${i >= keastIds.length - 1 ? ')' : ','} `
400
- }
401
- }
402
- return sql
403
- }
404
-
405
- this.buildPubmedSqlStatementForModels = function (model) {
406
- return `select distinct publication from publications where entity = '${model}'`
407
- }
408
-
409
- this.flatmapQuery = function (sql) {
410
- const data = { sql: sql }
411
- return fetch(`${this.flatmapApi}knowledge/query/`, {
412
- method: 'POST',
413
- headers: {
414
- 'Content-Type': 'application/json',
415
- },
416
- body: JSON.stringify(data),
417
- })
418
- .then((response) => response.json())
419
- .catch((error) => {
420
- console.error('Error:', error)
421
- })
422
- }
423
- // Note that this functin WILL run to the end, as it doesn not catch the second level of promises
424
- this.pubmedQueryOnIds = function (eventData) {
425
- return new Promise((resolve) => {
426
- const keastIds = eventData.resource
427
- const source = eventData.feature.source
428
- if (!keastIds || keastIds.length === 0) return
429
- const sql = this.buildPubmedSqlStatement(keastIds)
430
- this.flatmapQuery(sql).then((data) => {
431
- // Create pubmed url on paths if we have them
432
- if (data.values.length > 0) {
433
- this.urls = [
434
- this.pubmedSearchUrl(
435
- data.values.map((id) => this.stripPMIDPrefix(id[0]))
436
- ),
437
- ]
438
- resolve(true)
439
- } else {
440
- // Create pubmed url on models
441
- this.pubmedQueryOnModels(source).then((result) => {
442
- resolve(result)
443
- })
444
- }
445
- })
446
- })
447
- }
448
-
449
- this.pubmedQueryOnModels = function (source) {
450
- return this.flatmapQuery(
451
- this.buildPubmedSqlStatementForModels(source)
452
- ).then((data) => {
453
- if (Array.isArray(data.values) && data.values.length > 0) {
454
- this.urls = [
455
- this.pubmedSearchUrl(
456
- data.values.map((id) => this.stripPMIDPrefix(id[0]))
457
- ),
458
- ]
459
- return true
460
- } else {
461
- this.urls = [] // Clears the pubmed search button
462
- }
463
- return false
464
- })
465
- }
466
-
467
- this.pubmedSearchUrl = function (ids) {
468
- let url = 'https://pubmed.ncbi.nlm.nih.gov/?'
469
- let params = new URLSearchParams()
470
- params.append('term', ids)
471
- return url + params.toString()
472
- }
473
- }
474
-
475
- export { FlatmapQueries, findTaxonomyLabel }
1
+ /* eslint-disable no-alert, no-console */
2
+ // remove duplicates by stringifying the objects
3
+ const removeDuplicates = function (arrayOfAnything) {
4
+ if (!arrayOfAnything) return []
5
+ return [...new Set(arrayOfAnything.map((e) => JSON.stringify(e)))].map((e) =>
6
+ JSON.parse(e)
7
+ )
8
+ }
9
+
10
+ const cachedLabels = {}
11
+
12
+ const findTaxonomyLabel = async function (flatmapAPI, taxonomy) {
13
+ if (cachedLabels && cachedLabels.hasOwnProperty(taxonomy)) {
14
+ return cachedLabels[taxonomy]
15
+ }
16
+
17
+ return new Promise((resolve) => {
18
+ fetch(`${flatmapAPI}knowledge/label/${taxonomy}`, {
19
+ method: 'GET',
20
+ })
21
+ .then((response) => response.json())
22
+ .then((data) => {
23
+ let label = data.label
24
+ if (label === 'Mammalia') {
25
+ label = 'Mammalia not otherwise specified'
26
+ }
27
+ cachedLabels[taxonomy] = label
28
+ resolve(label)
29
+ })
30
+ .catch((error) => {
31
+ console.error('Error:', error)
32
+ cachedLabels[taxonomy] = taxonomy
33
+ resolve(taxonomy)
34
+ })
35
+ })
36
+ }
37
+
38
+ const inArray = function (ar1, ar2) {
39
+ if (!ar1 || !ar2) return false
40
+ let as1 = JSON.stringify(ar1)
41
+ let as2 = JSON.stringify(ar2)
42
+ return as1.indexOf(as2) !== -1
43
+ }
44
+
45
+ let FlatmapQueries = function () {
46
+ this.initialise = function (flatmapApi) {
47
+ this.flatmapApi = flatmapApi
48
+ this.destinations = []
49
+ this.origins = []
50
+ this.components = []
51
+ this.urls = []
52
+ this.controller = undefined
53
+ this.uberons = []
54
+ this.lookUp = []
55
+ }
56
+
57
+ this.createTooltipData = async function (eventData) {
58
+ let hyperlinks = []
59
+ if (
60
+ eventData.feature.hyperlinks &&
61
+ eventData.feature.hyperlinks.length > 0
62
+ ) {
63
+ hyperlinks = eventData.feature.hyperlinks
64
+ } else {
65
+ hyperlinks = this.urls.map((url) => ({ url: url, id: 'pubmed' }))
66
+ }
67
+ let taxonomyLabel = undefined
68
+ if (eventData.provenanceTaxonomy) {
69
+ taxonomyLabel = []
70
+ for (let i = 0; eventData.provenanceTaxonomy.length > i; i++) {
71
+ taxonomyLabel.push(
72
+ await findTaxonomyLabel(
73
+ this.flatmapAPI,
74
+ eventData.provenanceTaxonomy[i]
75
+ )
76
+ )
77
+ }
78
+ }
79
+
80
+ let tooltipData = {
81
+ destinations: this.destinations,
82
+ origins: this.origins,
83
+ components: this.components,
84
+ destinationsWithDatasets: this.destinationsWithDatasets,
85
+ originsWithDatasets: this.originsWithDatasets,
86
+ componentsWithDatasets: this.componentsWithDatasets,
87
+ title: eventData.label,
88
+ featureId: eventData.resource,
89
+ hyperlinks: hyperlinks,
90
+ provenanceTaxonomy: eventData.provenanceTaxonomy,
91
+ provenanceTaxonomyLabel: taxonomyLabel,
92
+ }
93
+ return tooltipData
94
+ }
95
+
96
+ this.createComponentsLabelList = function (components, lookUp) {
97
+ let labelList = []
98
+ components.forEach((n) => {
99
+ labelList.push(this.createLabelFromNeuralNode(n[0]), lookUp)
100
+ if (n.length === 2) {
101
+ labelList.push(this.createLabelFromNeuralNode(n[1]), lookUp)
102
+ }
103
+ })
104
+ return labelList
105
+ }
106
+
107
+ this.createLabelLookup = function (uberons) {
108
+ return new Promise((resolve) => {
109
+ let uberonMap = {}
110
+ this.uberons = []
111
+ const data = { sql: this.buildLabelSqlStatement(uberons) }
112
+ fetch(`${this.flatmapApi}knowledge/query/`, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ },
117
+ body: JSON.stringify(data),
118
+ })
119
+ .then((response) => response.json())
120
+ .then((payload) => {
121
+ const entity = payload.keys.indexOf('entity')
122
+ const label = payload.keys.indexOf('label')
123
+ if (entity > -1 && label > -1) {
124
+ payload.values.forEach((pair) => {
125
+ uberonMap[pair[entity]] = pair[label]
126
+ this.uberons.push({
127
+ id: pair[entity],
128
+ name: pair[label],
129
+ })
130
+ })
131
+ }
132
+ resolve(uberonMap)
133
+ })
134
+ })
135
+ }
136
+
137
+ this.buildConnectivitySqlStatement = function (keastIds) {
138
+ let sql = 'select knowledge from knowledge where entity in ('
139
+ if (keastIds.length === 1) {
140
+ sql += `'${keastIds[0]}')`
141
+ } else if (keastIds.length > 1) {
142
+ for (let i in keastIds) {
143
+ sql += `'${keastIds[i]}'${i >= keastIds.length - 1 ? ')' : ','} `
144
+ }
145
+ }
146
+ return sql
147
+ }
148
+
149
+ this.buildLabelSqlStatement = function (uberons) {
150
+ let sql = 'select entity, label from labels where entity in ('
151
+ if (uberons.length === 1) {
152
+ sql += `'${uberons[0]}')`
153
+ } else if (uberons.length > 1) {
154
+ for (let i in uberons) {
155
+ sql += `'${uberons[i]}'${i >= uberons.length - 1 ? ')' : ','} `
156
+ }
157
+ }
158
+ return sql
159
+ }
160
+
161
+ this.findAllIdsFromConnectivity = function (connectivity) {
162
+ let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
163
+ let nodes = [...new Set(dnodes)] // remove duplicates
164
+ let found = []
165
+ nodes.forEach((n) => {
166
+ if (Array.isArray(n)) {
167
+ found.push(n.flat())
168
+ } else {
169
+ found.push(n)
170
+ }
171
+ })
172
+ return [...new Set(found.flat())]
173
+ }
174
+
175
+ this.flattenConntectivity = function (connectivity) {
176
+ let dnodes = connectivity.flat() // get nodes from edgelist
177
+ let nodes = [...new Set(dnodes)] // remove duplicates
178
+ let found = []
179
+ nodes.forEach((n) => {
180
+ if (Array.isArray(n)) {
181
+ found.push(n.flat())
182
+ } else {
183
+ found.push(n)
184
+ }
185
+ })
186
+ return found.flat()
187
+ }
188
+
189
+ this.findComponents = function (connectivity) {
190
+ let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
191
+ let nodes = removeDuplicates(dnodes)
192
+
193
+ let found = []
194
+ let terminal = false
195
+ nodes.forEach((node) => {
196
+ terminal = false
197
+ // Check if the node is an destination or origin (note that they are labelled dendrite and axon as opposed to origin and destination)
198
+ if (inArray(connectivity.axons, node)) {
199
+ terminal = true
200
+ }
201
+ if (inArray(connectivity.dendrites, node)) {
202
+ terminal = true
203
+ }
204
+ if (!terminal) {
205
+ found.push(node)
206
+ }
207
+ })
208
+
209
+ return found
210
+ }
211
+
212
+ this.retrieveFlatmapKnowledgeForEvent = async function (eventData) {
213
+ // check if there is an existing query
214
+ if (this.controller) this.controller.abort()
215
+
216
+ // set up the abort controller
217
+ this.controller = new AbortController()
218
+ const signal = this.controller.signal
219
+
220
+ const keastIds = eventData.resource
221
+ this.destinations = []
222
+ this.origins = []
223
+ this.components = []
224
+ if (!keastIds || keastIds.length == 0) return
225
+
226
+ let prom1 = this.queryForConnectivity(keastIds, signal) // This on returns a promise so dont need 'await'
227
+ let prom2 = await this.pubmedQueryOnIds(eventData)
228
+ let results = await Promise.all([prom1, prom2])
229
+ return results
230
+ }
231
+
232
+ this.queryForConnectivity = function (keastIds, signal, processConnectivity=true) {
233
+ const data = { sql: this.buildConnectivitySqlStatement(keastIds) }
234
+ const headers = {
235
+ method: 'POST',
236
+ headers: {
237
+ 'Content-Type': 'application/json',
238
+ },
239
+ body: JSON.stringify(data),
240
+ ...(signal ? { signal: signal } : {}), // add signal to header if it exists
241
+ }
242
+ return new Promise((resolve) => {
243
+ fetch(`${this.flatmapApi}knowledge/query/`, headers)
244
+ .then((response) => response.json())
245
+ .then((data) => {
246
+ if (this.connectivityExists(data)) {
247
+ let connectivity = JSON.parse(data.values[0][0])
248
+ if (processConnectivity) {
249
+ this.processConnectivity(connectivity).then((processedConnectivity) => {
250
+ resolve(processedConnectivity)
251
+ })
252
+ }
253
+ else resolve(connectivity)
254
+ } else {
255
+ resolve(false)
256
+ }
257
+ })
258
+ .catch((error) => {
259
+ console.error('Error:', error)
260
+ resolve(false)
261
+ })
262
+ })
263
+ }
264
+
265
+ this.connectivityExists = function (data) {
266
+ if (
267
+ data.values &&
268
+ data.values.length > 0 &&
269
+ JSON.parse(data.values[0][0]).connectivity &&
270
+ JSON.parse(data.values[0][0]).connectivity.length > 0
271
+ ) {
272
+ return true
273
+ } else {
274
+ return false
275
+ }
276
+ }
277
+
278
+ // This function is used to determine if a node is a single node or a node with multiple children
279
+ // Returns the id of the node if it is a single node, otherwise returns false
280
+ this.findIfNodeIsSingle = function (node) {
281
+ if (node.length === 1) { // If the node is in the form [id]
282
+ console.error("Server returns a single node", node)
283
+ return node[0]
284
+ } else {
285
+ if (node.length === 2 && node[1].length === 0) { // If the node is in the form [id, []]
286
+ return node[0]
287
+ } else {
288
+ return false // If the node is in the form [id, [id1, id2]]
289
+ }
290
+ }
291
+ }
292
+
293
+ this.createLabelFromNeuralNode = function (node, lookUp) {
294
+
295
+ // Check if the node is a single node or a node with multiple children
296
+ let nodeIsSingle = this.findIfNodeIsSingle(node)
297
+
298
+ // Case where node is in the form [id]
299
+ if (nodeIsSingle) {
300
+ return lookUp[nodeIsSingle]
301
+ }
302
+
303
+ // Case where node is in the form [id, [id1 (,id2)]]
304
+ let label = lookUp[node[0]]
305
+ if (node.length === 2 && node[1].length > 0) {
306
+ node[1].forEach((n) => {
307
+ if (lookUp[n] == undefined) {
308
+ label += `, ${n}`
309
+ } else {
310
+ label += `, ${lookUp[n]}`
311
+ }
312
+ })
313
+ }
314
+ return label
315
+ }
316
+
317
+ this.flattenAndFindDatasets = function (components, axons, dendrites) {
318
+ // process the nodes for finding datasets (Note this is not critical to the tooltip, only for the 'search on components' button)
319
+ let componentsFlat = this.flattenConntectivity(components)
320
+ let axonsFlat = this.flattenConntectivity(axons)
321
+ let dendritesFlat = this.flattenConntectivity(dendrites)
322
+
323
+ // Filter for the anatomy which is annotated on datasets
324
+ this.destinationsWithDatasets = this.uberons.filter(
325
+ (ub) => axonsFlat.indexOf(ub.id) !== -1
326
+ )
327
+ this.originsWithDatasets = this.uberons.filter(
328
+ (ub) => dendritesFlat.indexOf(ub.id) !== -1
329
+ )
330
+ this.componentsWithDatasets = this.uberons.filter(
331
+ (ub) => componentsFlat.indexOf(ub.id) !== -1
332
+ )
333
+ }
334
+
335
+ this.processConnectivity = function (connectivity) {
336
+ return new Promise((resolve) => {
337
+ // Filter the origin and destinations from components
338
+ let components = this.findComponents(connectivity)
339
+
340
+ // Remove duplicates
341
+ let axons = removeDuplicates(connectivity.axons)
342
+ let dendrites = removeDuplicates(connectivity.dendrites)
343
+
344
+ // Create list of ids to get labels for
345
+ let conIds = this.findAllIdsFromConnectivity(connectivity)
346
+
347
+ // Create readable labels from the nodes. Setting this to 'this.origins' updates the display
348
+ this.createLabelLookup(conIds).then((lookUp) => {
349
+ this.destinations = axons.map((a) =>
350
+ this.createLabelFromNeuralNode(a, lookUp)
351
+ )
352
+ this.origins = dendrites.map((d) =>
353
+ this.createLabelFromNeuralNode(d, lookUp)
354
+ )
355
+ this.components = components.map((c) =>
356
+ this.createLabelFromNeuralNode(c, lookUp)
357
+ )
358
+ this.flattenAndFindDatasets(components, axons, dendrites)
359
+ resolve({
360
+ ids: {
361
+ axons: axons,
362
+ dendrites: dendrites,
363
+ components: components,
364
+ },
365
+ labels: {
366
+ destinations: this.destinations,
367
+ origins: this.origins,
368
+ components: this.components,
369
+ }
370
+ })
371
+ })
372
+ })
373
+ }
374
+
375
+ this.flattenConntectivity = function (connectivity) {
376
+ let dnodes = connectivity.flat() // get nodes from edgelist
377
+ let nodes = [...new Set(dnodes)] // remove duplicates
378
+ let found = []
379
+ nodes.forEach((n) => {
380
+ if (Array.isArray(n)) {
381
+ found.push(n.flat())
382
+ } else {
383
+ found.push(n)
384
+ }
385
+ })
386
+ return found.flat()
387
+ }
388
+
389
+ this.stripPMIDPrefix = function (pubmedId) {
390
+ return pubmedId.split(':')[1]
391
+ }
392
+
393
+ this.buildPubmedSqlStatement = function (keastIds) {
394
+ let sql = 'select distinct publication from publications where entity in ('
395
+ if (keastIds.length === 1) {
396
+ sql += `'${keastIds[0]}')`
397
+ } else if (keastIds.length > 1) {
398
+ for (let i in keastIds) {
399
+ sql += `'${keastIds[i]}'${i >= keastIds.length - 1 ? ')' : ','} `
400
+ }
401
+ }
402
+ return sql
403
+ }
404
+
405
+ this.buildPubmedSqlStatementForModels = function (model) {
406
+ return `select distinct publication from publications where entity = '${model}'`
407
+ }
408
+
409
+ this.flatmapQuery = function (sql) {
410
+ const data = { sql: sql }
411
+ return fetch(`${this.flatmapApi}knowledge/query/`, {
412
+ method: 'POST',
413
+ headers: {
414
+ 'Content-Type': 'application/json',
415
+ },
416
+ body: JSON.stringify(data),
417
+ })
418
+ .then((response) => response.json())
419
+ .catch((error) => {
420
+ console.error('Error:', error)
421
+ })
422
+ }
423
+ // Note that this functin WILL run to the end, as it doesn not catch the second level of promises
424
+ this.pubmedQueryOnIds = function (eventData) {
425
+ return new Promise((resolve) => {
426
+ const keastIds = eventData.resource
427
+ const source = eventData.feature.source
428
+ if (!keastIds || keastIds.length === 0) return
429
+ const sql = this.buildPubmedSqlStatement(keastIds)
430
+ this.flatmapQuery(sql).then((data) => {
431
+ // Create pubmed url on paths if we have them
432
+ if (data.values.length > 0) {
433
+ this.urls = [
434
+ this.pubmedSearchUrl(
435
+ data.values.map((id) => this.stripPMIDPrefix(id[0]))
436
+ ),
437
+ ]
438
+ resolve(true)
439
+ } else {
440
+ // Create pubmed url on models
441
+ this.pubmedQueryOnModels(source).then((result) => {
442
+ resolve(result)
443
+ })
444
+ }
445
+ })
446
+ })
447
+ }
448
+
449
+ this.pubmedQueryOnModels = function (source) {
450
+ return this.flatmapQuery(
451
+ this.buildPubmedSqlStatementForModels(source)
452
+ ).then((data) => {
453
+ if (Array.isArray(data.values) && data.values.length > 0) {
454
+ this.urls = [
455
+ this.pubmedSearchUrl(
456
+ data.values.map((id) => this.stripPMIDPrefix(id[0]))
457
+ ),
458
+ ]
459
+ return true
460
+ } else {
461
+ this.urls = [] // Clears the pubmed search button
462
+ }
463
+ return false
464
+ })
465
+ }
466
+
467
+ this.pubmedSearchUrl = function (ids) {
468
+ let url = 'https://pubmed.ncbi.nlm.nih.gov/?'
469
+ let params = new URLSearchParams()
470
+ params.append('term', ids)
471
+ return url + params.toString()
472
+ }
473
+ }
474
+
475
+ export { FlatmapQueries, findTaxonomyLabel }