@abi-software/map-side-bar 2.7.2-beta.2 → 2.7.2-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/map-side-bar.js +7019 -7394
- package/dist/map-side-bar.umd.cjs +62 -59
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/App.vue +30 -95
- package/src/components/ConnectivityInfo.vue +189 -384
- package/src/components/SearchFilters.vue +51 -66
- package/src/components/SearchHistory.vue +1 -0
- package/src/components/SideBar.vue +116 -79
- package/src/components/SidebarContent.vue +1 -2
- package/src/components/Tabs.vue +95 -56
- package/src/components/flatmapQueries.js +291 -0
- package/src/components.d.ts +23 -2
- package/src/exampleConnectivityInput.js +1 -1
- package/src/components/ConnectivityCard.vue +0 -78
- package/src/components/ConnectivityExplorer.vue +0 -518
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporary: flatmapQueries from flatmapVuer
|
|
3
|
+
**/
|
|
4
|
+
const cachedTaxonLabels = [];
|
|
5
|
+
let uberons = [];
|
|
6
|
+
|
|
7
|
+
const query = async function (flatmapApi, sql, params) {
|
|
8
|
+
const url = `${flatmapApi}knowledge/query/`;
|
|
9
|
+
const query = { sql, params };
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
"Accept": "application/json; charset=utf-8",
|
|
16
|
+
"Cache-Control": "no-store",
|
|
17
|
+
"Content-Type": "application/json"
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify(query)
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`Cannot access ${url}`);
|
|
24
|
+
}
|
|
25
|
+
return await response.json();
|
|
26
|
+
} catch {
|
|
27
|
+
return {
|
|
28
|
+
values: []
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const removeDuplicates = function (arrayOfAnything) {
|
|
34
|
+
if (!arrayOfAnything) return []
|
|
35
|
+
return [...new Set(arrayOfAnything.map((e) => JSON.stringify(e)))].map((e) =>
|
|
36
|
+
JSON.parse(e)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const queryLabels = async function (flatmapApi, knowledgeSource, entities) {
|
|
41
|
+
const entityLabels = []
|
|
42
|
+
const entityArray = Array.isArray(entities) ? entities
|
|
43
|
+
: entities ? [entities]
|
|
44
|
+
: []
|
|
45
|
+
if (entityArray.length > 0) {
|
|
46
|
+
const rows = await query(
|
|
47
|
+
flatmapApi,
|
|
48
|
+
`select source, entity, knowledge from knowledge
|
|
49
|
+
where (source=? or source is null)
|
|
50
|
+
and entity in (?${', ?'.repeat(entityArray.length-1)})
|
|
51
|
+
order by entity, source desc`,
|
|
52
|
+
[knowledgeSource, ...entityArray]
|
|
53
|
+
)
|
|
54
|
+
let last_entity = null
|
|
55
|
+
for (const row of rows.values) {
|
|
56
|
+
// In entity, source[desc] order; we use the most recent label
|
|
57
|
+
if (row[1] !== last_entity) {
|
|
58
|
+
const knowledge = JSON.parse(row[2])
|
|
59
|
+
entityLabels.push({
|
|
60
|
+
entity: row[1],
|
|
61
|
+
label: knowledge['label'] || row[1]
|
|
62
|
+
})
|
|
63
|
+
last_entity = row[1]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return entityLabels
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const inArray = function (ar1, ar2) {
|
|
71
|
+
if (!ar1 || !ar2) return false
|
|
72
|
+
let as1 = JSON.stringify(ar1)
|
|
73
|
+
let as2 = JSON.stringify(ar2)
|
|
74
|
+
return as1.indexOf(as2) !== -1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const findComponents = function (connectivity) {
|
|
78
|
+
let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
|
|
79
|
+
let nodes = removeDuplicates(dnodes)
|
|
80
|
+
|
|
81
|
+
let found = []
|
|
82
|
+
let terminal = false
|
|
83
|
+
nodes.forEach((node) => {
|
|
84
|
+
terminal = false
|
|
85
|
+
// Check if the node is an destination or origin (note that they are labelled dendrite and axon as opposed to origin and destination)
|
|
86
|
+
if (inArray(connectivity.axons, node)) {
|
|
87
|
+
terminal = true
|
|
88
|
+
}
|
|
89
|
+
if (connectivity.somas && inArray(connectivity.somas, node)) {
|
|
90
|
+
terminal = true
|
|
91
|
+
}
|
|
92
|
+
if (inArray(connectivity.dendrites, node)) {
|
|
93
|
+
terminal = true
|
|
94
|
+
}
|
|
95
|
+
if (!terminal) {
|
|
96
|
+
found.push(node)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return found
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const findAllIdsFromConnectivity = function (connectivity) {
|
|
104
|
+
let dnodes = connectivity.connectivity.flat() // get nodes from edgelist
|
|
105
|
+
let nodes = [...new Set(dnodes)] // remove duplicates
|
|
106
|
+
let found = []
|
|
107
|
+
nodes.forEach((n) => {
|
|
108
|
+
if (Array.isArray(n)) {
|
|
109
|
+
found.push(n.flat())
|
|
110
|
+
} else {
|
|
111
|
+
found.push(n)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
return [...new Set(found.flat())]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const findTaxonomyLabels = async function (flatmapApi, knowledgeSource, taxonomies) {
|
|
118
|
+
const intersectionTaxonomies = taxonomies.filter((taxonomy) =>
|
|
119
|
+
cachedTaxonLabels.some((obj) => obj.taxon === taxonomy)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const foundCachedTaxonLabels = cachedTaxonLabels.filter((obj) =>
|
|
123
|
+
intersectionTaxonomies.includes(obj.taxon)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const leftoverTaxonomies = taxonomies.filter((taxonomy) =>
|
|
127
|
+
!intersectionTaxonomies.includes(taxonomy)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (!leftoverTaxonomies.length) {
|
|
131
|
+
return foundCachedTaxonLabels;
|
|
132
|
+
} else {
|
|
133
|
+
const entityLabels = await queryLabels(flatmapApi, knowledgeSource, leftoverTaxonomies);
|
|
134
|
+
if (entityLabels.length) {
|
|
135
|
+
entityLabels.forEach((entityLabel) => {
|
|
136
|
+
let { entity: taxon, label } = entityLabel;
|
|
137
|
+
if (label === 'Mammalia') {
|
|
138
|
+
label = 'Mammalia not otherwise specified'
|
|
139
|
+
}
|
|
140
|
+
const item = { taxon, label };
|
|
141
|
+
foundCachedTaxonLabels.push(item);
|
|
142
|
+
cachedTaxonLabels.push(item);
|
|
143
|
+
});
|
|
144
|
+
return foundCachedTaxonLabels;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const createLabelLookup = function (flatmapApi, knowledgeSource, _uberons) {
|
|
150
|
+
return new Promise(async (resolve) => {
|
|
151
|
+
let uberonMap = {}
|
|
152
|
+
uberons = []
|
|
153
|
+
const entityLabels = await findTaxonomyLabels(flatmapApi, knowledgeSource, _uberons);
|
|
154
|
+
if (entityLabels.length) {
|
|
155
|
+
entityLabels.forEach((entityLabel) => {
|
|
156
|
+
const { taxon: entity, label } = entityLabel;
|
|
157
|
+
uberonMap[entity] = label;
|
|
158
|
+
uberons.push({
|
|
159
|
+
id: entity,
|
|
160
|
+
name: label,
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
resolve(uberonMap)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const findIfNodeIsSingle = function (node) {
|
|
169
|
+
if (node.length === 1) { // If the node is in the form [id]
|
|
170
|
+
console.error("Server returns a single node", node)
|
|
171
|
+
return node[0]
|
|
172
|
+
} else {
|
|
173
|
+
if (node.length === 2 && node[1].length === 0) { // If the node is in the form [id, []]
|
|
174
|
+
return node[0]
|
|
175
|
+
} else {
|
|
176
|
+
return false // If the node is in the form [id, [id1, id2]]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const createLabelFromNeuralNode = function (node, lookUp) {
|
|
182
|
+
|
|
183
|
+
// Check if the node is a single node or a node with multiple children
|
|
184
|
+
let nodeIsSingle = findIfNodeIsSingle(node)
|
|
185
|
+
|
|
186
|
+
// Case where node is in the form [id]
|
|
187
|
+
if (nodeIsSingle) {
|
|
188
|
+
return lookUp[nodeIsSingle]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Case where node is in the form [id, [id1 (,id2)]]
|
|
192
|
+
let label = lookUp[node[0]]
|
|
193
|
+
if (node.length === 2 && node[1].length > 0) {
|
|
194
|
+
node[1].forEach((n) => {
|
|
195
|
+
if (lookUp[n] == undefined) {
|
|
196
|
+
label += `, ${n}`
|
|
197
|
+
} else {
|
|
198
|
+
label += `, ${lookUp[n]}`
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
return label
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const flattenConntectivity = function (connectivity) {
|
|
206
|
+
let dnodes = 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 found.flat()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const flattenAndFindDatasets = function (components, axons, dendrites) {
|
|
220
|
+
// process the nodes for finding datasets (Note this is not critical to the tooltip, only for the 'search on components' button)
|
|
221
|
+
let componentsFlat = flattenConntectivity(components)
|
|
222
|
+
let axonsFlat = flattenConntectivity(axons)
|
|
223
|
+
let dendritesFlat = flattenConntectivity(dendrites)
|
|
224
|
+
|
|
225
|
+
// Filter for the anatomy which is annotated on datasets
|
|
226
|
+
const destinationsWithDatasets = uberons.filter(
|
|
227
|
+
(ub) => axonsFlat.indexOf(ub.id) !== -1
|
|
228
|
+
)
|
|
229
|
+
const originsWithDatasets = uberons.filter(
|
|
230
|
+
(ub) => dendritesFlat.indexOf(ub.id) !== -1
|
|
231
|
+
)
|
|
232
|
+
const componentsWithDatasets = uberons.filter(
|
|
233
|
+
(ub) => componentsFlat.indexOf(ub.id) !== -1
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
destinationsWithDatasets,
|
|
238
|
+
originsWithDatasets,
|
|
239
|
+
componentsWithDatasets
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const processConnectivity = async function (flatmapApi, knowledgeSource, connectivity) {
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
// Filter the origin and destinations from components
|
|
246
|
+
let components = findComponents(connectivity)
|
|
247
|
+
|
|
248
|
+
// Remove duplicates
|
|
249
|
+
let axons = removeDuplicates(connectivity.axons)
|
|
250
|
+
//Somas will become part of origins, support this for future proof
|
|
251
|
+
let dendrites = []
|
|
252
|
+
if (connectivity.somas && connectivity.somas.length > 0) {
|
|
253
|
+
dendrites.push(...connectivity.somas)
|
|
254
|
+
}
|
|
255
|
+
if (connectivity.dendrites && connectivity.dendrites.length > 0) {
|
|
256
|
+
dendrites.push(...connectivity.dendrites)
|
|
257
|
+
}
|
|
258
|
+
dendrites = removeDuplicates(dendrites)
|
|
259
|
+
|
|
260
|
+
// Create list of ids to get labels for
|
|
261
|
+
let conIds = findAllIdsFromConnectivity(connectivity)
|
|
262
|
+
|
|
263
|
+
// Create readable labels from the nodes. Setting this to 'this.origins' updates the display
|
|
264
|
+
createLabelLookup(flatmapApi, knowledgeSource, conIds).then((lookUp) => {
|
|
265
|
+
const _destinations = axons.map((a) =>
|
|
266
|
+
createLabelFromNeuralNode(a, lookUp)
|
|
267
|
+
)
|
|
268
|
+
const _origins = dendrites.map((d) =>
|
|
269
|
+
createLabelFromNeuralNode(d, lookUp)
|
|
270
|
+
)
|
|
271
|
+
const _components = components.map((c) =>
|
|
272
|
+
createLabelFromNeuralNode(c, lookUp)
|
|
273
|
+
)
|
|
274
|
+
const withDatasets = flattenAndFindDatasets(components, axons, dendrites)
|
|
275
|
+
const result = {
|
|
276
|
+
ids: {
|
|
277
|
+
axons: axons,
|
|
278
|
+
dendrites: dendrites,
|
|
279
|
+
components: components,
|
|
280
|
+
},
|
|
281
|
+
labels: {
|
|
282
|
+
destinations: _destinations,
|
|
283
|
+
origins: _origins,
|
|
284
|
+
components: _components,
|
|
285
|
+
},
|
|
286
|
+
withDatasets
|
|
287
|
+
}
|
|
288
|
+
resolve(result);
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
}
|
package/src/components.d.ts
CHANGED
|
@@ -9,14 +9,32 @@ declare module 'vue' {
|
|
|
9
9
|
export interface GlobalComponents {
|
|
10
10
|
AnnotationTool: typeof import('./components/AnnotationTool.vue')['default']
|
|
11
11
|
BadgesGroup: typeof import('./components/BadgesGroup.vue')['default']
|
|
12
|
-
ConnectivityCard: typeof import('./components/ConnectivityCard.vue')['default']
|
|
13
|
-
ConnectivityExplorer: typeof import('./components/ConnectivityExplorer.vue')['default']
|
|
14
12
|
ConnectivityInfo: typeof import('./components/ConnectivityInfo.vue')['default']
|
|
15
13
|
DatasetCard: typeof import('./components/DatasetCard.vue')['default']
|
|
14
|
+
ElButton: typeof import('element-plus/es')['ElButton']
|
|
15
|
+
ElCard: typeof import('element-plus/es')['ElCard']
|
|
16
|
+
ElCascader: typeof import('element-plus/es')['ElCascader']
|
|
17
|
+
ElCol: typeof import('element-plus/es')['ElCol']
|
|
16
18
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
|
19
|
+
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
|
20
|
+
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
|
21
|
+
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
|
17
22
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
|
23
|
+
ElIconArrowDown: typeof import('@element-plus/icons-vue')['ArrowDown']
|
|
18
24
|
ElIconArrowLeft: typeof import('@element-plus/icons-vue')['ArrowLeft']
|
|
19
25
|
ElIconArrowRight: typeof import('@element-plus/icons-vue')['ArrowRight']
|
|
26
|
+
ElIconDelete: typeof import('@element-plus/icons-vue')['Delete']
|
|
27
|
+
ElIconLocation: typeof import('@element-plus/icons-vue')['Location']
|
|
28
|
+
ElIconWarnTriangleFilled: typeof import('@element-plus/icons-vue')['WarnTriangleFilled']
|
|
29
|
+
ElInput: typeof import('element-plus/es')['ElInput']
|
|
30
|
+
ElOption: typeof import('element-plus/es')['ElOption']
|
|
31
|
+
ElPagination: typeof import('element-plus/es')['ElPagination']
|
|
32
|
+
ElPopover: typeof import('element-plus/es')['ElPopover']
|
|
33
|
+
ElRadio: typeof import('element-plus/es')['ElRadio']
|
|
34
|
+
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
|
35
|
+
ElRow: typeof import('element-plus/es')['ElRow']
|
|
36
|
+
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
37
|
+
ElTag: typeof import('element-plus/es')['ElTag']
|
|
20
38
|
ImageGallery: typeof import('./components/ImageGallery.vue')['default']
|
|
21
39
|
SearchFilters: typeof import('./components/SearchFilters.vue')['default']
|
|
22
40
|
SearchHistory: typeof import('./components/SearchHistory.vue')['default']
|
|
@@ -24,4 +42,7 @@ declare module 'vue' {
|
|
|
24
42
|
SidebarContent: typeof import('./components/SidebarContent.vue')['default']
|
|
25
43
|
Tabs: typeof import('./components/Tabs.vue')['default']
|
|
26
44
|
}
|
|
45
|
+
export interface ComponentCustomProperties {
|
|
46
|
+
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
|
47
|
+
}
|
|
27
48
|
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="connectivity-card-container" ref="container">
|
|
3
|
-
<div class="connectivity-card" ref="card">
|
|
4
|
-
<div class="seperator-path"></div>
|
|
5
|
-
<div class="card" @click="cardClicked(entry)">
|
|
6
|
-
<div class="title">{{ entry.label }}</div>
|
|
7
|
-
<template v-for="field in displayFields" :key="field">
|
|
8
|
-
<div class="details" v-if="entry[field]">
|
|
9
|
-
<strong>{{ field }}:</strong> {{ entry[field] }}
|
|
10
|
-
</div>
|
|
11
|
-
</template>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
</template>
|
|
16
|
-
|
|
17
|
-
<script>
|
|
18
|
-
export default {
|
|
19
|
-
name: "ConnectivityCard",
|
|
20
|
-
data() {
|
|
21
|
-
return {
|
|
22
|
-
displayFields: ["id"],
|
|
23
|
-
};
|
|
24
|
-
},
|
|
25
|
-
props: {
|
|
26
|
-
/**
|
|
27
|
-
* Object containing information for
|
|
28
|
-
* the required viewing.
|
|
29
|
-
*/
|
|
30
|
-
entry: {
|
|
31
|
-
type: Object,
|
|
32
|
-
default: () => {},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
methods: {
|
|
36
|
-
cardClicked: function (data) {
|
|
37
|
-
this.$emit("connectivity-explorer-clicked", data);
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<style lang="scss" scoped>
|
|
44
|
-
.connectivity-card {
|
|
45
|
-
padding-left: 5px;
|
|
46
|
-
position: relative;
|
|
47
|
-
min-height: 5rem;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.card {
|
|
51
|
-
padding-top: 18px;
|
|
52
|
-
padding-left: 6px;
|
|
53
|
-
cursor: pointer;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.title {
|
|
57
|
-
padding-bottom: 0.75rem;
|
|
58
|
-
font-weight: bold;
|
|
59
|
-
line-height: 1.5;
|
|
60
|
-
letter-spacing: 1.05px;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.details {
|
|
64
|
-
line-height: 1.5;
|
|
65
|
-
letter-spacing: 1.05px;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.button {
|
|
69
|
-
margin-right: 3.5rem;
|
|
70
|
-
font-family: Asap;
|
|
71
|
-
font-size: 14px;
|
|
72
|
-
font-weight: normal;
|
|
73
|
-
background-color: $app-primary-color;
|
|
74
|
-
border: $app-primary-color;
|
|
75
|
-
color: white;
|
|
76
|
-
margin-top: 8px;
|
|
77
|
-
}
|
|
78
|
-
</style>
|