@abi-software/map-utilities 1.4.2-beta.0 → 1.4.3-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-utilities",
3
- "version": "1.4.2-beta.0",
3
+ "version": "1.4.3-beta.0",
4
4
  "files": [
5
5
  "dist/*",
6
6
  "src/*",
@@ -30,6 +30,11 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@abi-software/svg-sprite": "^1.0.1",
33
+ "@citation-js/core": "^0.7.14",
34
+ "@citation-js/plugin-bibtex": "^0.7.17",
35
+ "@citation-js/plugin-csl": "^0.7.14",
36
+ "@citation-js/plugin-doi": "^0.7.16",
37
+ "@citation-js/plugin-pubmed": "^0.3.0",
33
38
  "@element-plus/icons-vue": "^2.3.1",
34
39
  "cytoscape": "^3.30.2",
35
40
  "element-plus": "2.8.4",
@@ -160,6 +160,10 @@ export default {
160
160
  type: Array,
161
161
  default: [],
162
162
  },
163
+ connectivityFromMap: {
164
+ type: Object,
165
+ default: () => null,
166
+ },
163
167
  },
164
168
  data: function () {
165
169
  return {
@@ -184,26 +188,20 @@ export default {
184
188
  connectivityGraphContainer: null,
185
189
  };
186
190
  },
191
+ watch: {
192
+ connectivityFromMap: function (oldVal, newVal) {
193
+ if (oldVal != newVal) {
194
+ this.showSpinner();
195
+ this.start();
196
+ }
197
+ }
198
+ },
187
199
  mounted() {
188
200
  this.showSpinner();
189
201
  this.updateTooltipContainer();
190
202
  this.refreshCache();
191
203
  this.loadCacheData();
192
- this.run()
193
- .then((res) => {
194
- if (res?.success) {
195
- this.showGraph(this.entry);
196
- } else if (res?.error) {
197
- this.loadingError = res.error;
198
- } else {
199
- this.loadingError = 'Loading error!';
200
- }
201
- this.hideSpinner();
202
- })
203
- .catch((error) => {
204
- this.loadingError = 'Loading error!';
205
- this.hideSpinner();
206
- });
204
+ this.start();
207
205
  },
208
206
  methods: {
209
207
  updateTooltipContainer: function () {
@@ -275,6 +273,22 @@ export default {
275
273
 
276
274
  sessionStorage.setItem('connectivity-graph-expiry', expiry);
277
275
  },
276
+ start: function () {
277
+ this.run()
278
+ .then((res) => {
279
+ if (res?.success) {
280
+ this.showGraph(this.entry);
281
+ } else if (res?.error) {
282
+ this.loadingError = res.error;
283
+ } else {
284
+ this.loadingError = 'Loading error!';
285
+ }
286
+ })
287
+ .catch((error) => {
288
+ this.loadingError = 'Loading error!';
289
+ this.hideSpinner();
290
+ });
291
+ },
278
292
  run: async function () {
279
293
  if (!this.schemaVersion) {
280
294
  this.schemaVersion = await this.getSchemaVersion();
@@ -305,8 +319,24 @@ export default {
305
319
  showGraph: async function (neuronPath) {
306
320
  const graphCanvas = this.$refs.graphCanvas;
307
321
 
322
+ // Update label data
323
+ if (this.connectivityFromMap) {
324
+ this.cacheLabels(this.connectivityFromMap);
325
+ await this.getCachedTermLabels();
326
+ }
327
+
308
328
  this.connectivityGraph = new ConnectivityGraph(this.labelCache, graphCanvas);
309
- await this.connectivityGraph.addConnectivity(this.knowledgeByPath.get(neuronPath));
329
+ const connectivityInfo = this.knowledgeByPath.get(neuronPath);
330
+
331
+ // Update connectivity
332
+ if (this.connectivityFromMap) {
333
+ connectivityInfo.axons = this.connectivityFromMap.axons;
334
+ connectivityInfo.connectivity = this.connectivityFromMap.connectivity;
335
+ connectivityInfo.dendrites = this.connectivityFromMap.dendrites;
336
+ connectivityInfo.somas = this.connectivityFromMap.somas;
337
+ }
338
+
339
+ await this.connectivityGraph.addConnectivity(connectivityInfo);
310
340
 
311
341
  this.connectivityGraph.showConnectivity(graphCanvas);
312
342
 
@@ -322,6 +352,8 @@ export default {
322
352
  */
323
353
  this.$emit('tap-node', data);
324
354
  });
355
+
356
+ this.hideSpinner();
325
357
  },
326
358
  query: async function (sql, params) {
327
359
  const url = `${this.mapServer}knowledge/query/`;
@@ -72,6 +72,7 @@ export class ConnectivityGraph extends EventTarget
72
72
  somas = []
73
73
  labelCache = new Map()
74
74
  graphCanvas = null
75
+ hasPhenotypes = false
75
76
 
76
77
  constructor(labelCache, graphCanvas)
77
78
  {
@@ -83,23 +84,32 @@ export class ConnectivityGraph extends EventTarget
83
84
  async addConnectivity(knowledge)
84
85
  //=====================================================
85
86
  {
86
- const sourceKey = ["ilxtr:hasSomaLocatedIn"]
87
- const destinationKey = ["ilxtr:hasAxonPresynapticElementIn", "ilxtr:hasAxonSensorySubcellularElementIn"]
88
-
89
- const source = []
90
- const destination = []
91
- sourceKey.forEach((key)=>{
92
- source.push(...knowledge["node-phenotypes"][key])
93
- })
94
- destinationKey.forEach((key)=>{
95
- destination.push(...knowledge["node-phenotypes"][key])
96
- })
97
- const via = findComponents(knowledge, source, destination)
98
-
99
- this.dendrites = source.map(node => JSON.stringify(node))
100
- this.axons = destination.map(node => JSON.stringify(node))
101
- if (via?.length) {
102
- this.somas = via.map(node => JSON.stringify(node))
87
+ if (knowledge && knowledge["node-phenotypes"]) {
88
+ const sourceKey = ["ilxtr:hasSomaLocatedIn"]
89
+ const destinationKey = ["ilxtr:hasAxonPresynapticElementIn", "ilxtr:hasAxonSensorySubcellularElementIn"]
90
+
91
+ const source = []
92
+ const destination = []
93
+ sourceKey.forEach((key)=>{
94
+ source.push(...knowledge["node-phenotypes"][key])
95
+ })
96
+ destinationKey.forEach((key)=>{
97
+ destination.push(...knowledge["node-phenotypes"][key])
98
+ })
99
+ const via = findComponents(knowledge, source, destination)
100
+ this.dendrites = source.map(node => JSON.stringify(node))
101
+ this.axons = destination.map(node => JSON.stringify(node))
102
+ if (via?.length) {
103
+ this.somas = via.map(node => JSON.stringify(node))
104
+ }
105
+ this.hasPhenotypes = true
106
+ } else {
107
+ this.axons = knowledge.axons.map(node => JSON.stringify(node))
108
+ this.dendrites = knowledge.dendrites.map(node => JSON.stringify(node))
109
+ if (knowledge.somas?.length) {
110
+ this.somas = knowledge.somas.map(node => JSON.stringify(node))
111
+ }
112
+ this.hasPhenotypes = false
103
113
  }
104
114
  if (knowledge.connectivity.length) {
105
115
  for (const edge of knowledge.connectivity) {
@@ -208,9 +218,17 @@ export class ConnectivityGraph extends EventTarget
208
218
  get roots()
209
219
  //===================
210
220
  {
211
- return [
212
- ...this.dendrites,
213
- ]
221
+ if (this.hasPhenotypes) {
222
+ return [
223
+ ...this.dendrites,
224
+ ]
225
+ } else {
226
+ return [
227
+ ...this.dendrites,
228
+ ...this.somas
229
+ ]
230
+ }
231
+
214
232
  }
215
233
 
216
234
  async graphNode(node)
@@ -229,12 +247,25 @@ export class ConnectivityGraph extends EventTarget
229
247
  id,
230
248
  label: label.join('\n')
231
249
  }
232
- if (this.axons.includes(id)) {
233
- result['axon'] = true
234
- } else if (this.dendrites.includes(id)) {
235
- result['dendrite'] = true
250
+ if (this.hasPhenotypes) {
251
+ if (this.axons.includes(id)) {
252
+ result['axon'] = true
253
+ } else if (this.dendrites.includes(id)) {
254
+ result['dendrite'] = true
255
+ } else {
256
+ result['somas'] = true
257
+ }
236
258
  } else {
237
- result['somas'] = true
259
+ if (this.axons.includes(id)) {
260
+ if (this.dendrites.includes(id) || this.somas.includes(id)) {
261
+ result['somas'] = true
262
+ } else {
263
+ result['axon'] = true
264
+ }
265
+ } else if (this.dendrites.includes(id) || this.somas.includes(id)) {
266
+ result['dendrite'] = true
267
+
268
+ }
238
269
  }
239
270
  return result
240
271
  }
@@ -0,0 +1,394 @@
1
+ <template>
2
+ <div class="connectivity-list">
3
+ {{ entry.paths }}
4
+ <div v-if="origins && origins.length > 0" class="block">
5
+ <div class="attribute-title-container">
6
+ <span class="attribute-title">Origin</span>
7
+ <el-popover
8
+ width="250"
9
+ trigger="hover"
10
+ :teleported="false"
11
+ popper-class="popover-origin-help"
12
+ >
13
+ <template #reference>
14
+ <el-icon class="info"><el-icon-warning /></el-icon>
15
+ </template>
16
+ <span style="word-break: keep-all">
17
+ <i>Origin</i> {{ originDescription }}
18
+ </span>
19
+ </el-popover>
20
+ </div>
21
+ <div
22
+ v-for="(origin, i) in origins"
23
+ class="attribute-content"
24
+ :origin-item-label="origin"
25
+ :key="origin"
26
+ @mouseenter="toggleConnectivityTooltip(origin, {show: true})"
27
+ @mouseleave="toggleConnectivityTooltip(origin, {show: false})"
28
+ >
29
+ {{ capitalise(origin) }}
30
+ </div>
31
+ <el-button
32
+ v-show="
33
+ originsWithDatasets && originsWithDatasets.length > 0 &&
34
+ shouldShowExploreButton(originsWithDatasets)
35
+ "
36
+ class="button"
37
+ id="open-dendrites-button"
38
+ @click="openDendrites"
39
+ >
40
+ Explore origin data
41
+ </el-button>
42
+ </div>
43
+ <div
44
+ v-if="components && components.length > 0"
45
+ class="block"
46
+ >
47
+ <div class="attribute-title-container">
48
+ <div class="attribute-title">Components</div>
49
+ </div>
50
+ <div
51
+ v-for="(component, i) in components"
52
+ class="attribute-content"
53
+ :component-item-label="component"
54
+ :key="component"
55
+ @mouseenter="toggleConnectivityTooltip(component, {show: true})"
56
+ @mouseleave="toggleConnectivityTooltip(component, {show: false})"
57
+ >
58
+ {{ capitalise(component) }}
59
+ </div>
60
+ </div>
61
+ <div
62
+ v-if="destinations && destinations.length > 0"
63
+ class="block"
64
+ >
65
+ <div class="attribute-title-container">
66
+ <span class="attribute-title">Destination</span>
67
+ <el-popover
68
+ width="250"
69
+ trigger="hover"
70
+ :teleported="false"
71
+ popper-class="popover-origin-help"
72
+ >
73
+ <template #reference>
74
+ <el-icon class="info"><el-icon-warning /></el-icon>
75
+ </template>
76
+ <span style="word-break: keep-all">
77
+ <i>Destination</i> is where the axons terminate
78
+ </span>
79
+ </el-popover>
80
+ </div>
81
+ <div
82
+ v-for="(destination, i) in destinations"
83
+ class="attribute-content"
84
+ :destination-item-label="destination"
85
+ :key="destination"
86
+ @mouseenter="toggleConnectivityTooltip(destination, {show: true})"
87
+ @mouseleave="toggleConnectivityTooltip(destination, {show: false})"
88
+ >
89
+ {{ capitalise(destination) }}
90
+ </div>
91
+ <el-button
92
+ v-show="
93
+ destinationsWithDatasets &&
94
+ destinationsWithDatasets.length > 0 &&
95
+ shouldShowExploreButton(destinationsWithDatasets)
96
+ "
97
+ class="button"
98
+ @click="openAxons"
99
+ >
100
+ Explore destination data
101
+ </el-button>
102
+ </div>
103
+ <div
104
+ v-show="
105
+ componentsWithDatasets &&
106
+ componentsWithDatasets.length > 0 &&
107
+ shouldShowExploreButton(componentsWithDatasets)
108
+ "
109
+ class="block"
110
+ >
111
+ <el-button
112
+ class="button"
113
+ @click="openAll"
114
+ >
115
+ Search for data on components
116
+ </el-button>
117
+ </div>
118
+
119
+ <div class="connectivity-error-container">
120
+ <div class="connectivity-error" v-if="connectivityError">
121
+ <strong v-if="connectivityError.errorConnectivities">
122
+ {{ connectivityError.errorConnectivities }}
123
+ </strong>
124
+ {{ connectivityError.errorMessage }}
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </template>
129
+
130
+ <script>
131
+ import {
132
+ Warning as ElIconWarning,
133
+ } from '@element-plus/icons-vue'
134
+ import {
135
+ ElButton as Button,
136
+ ElContainer as Container,
137
+ ElIcon as Icon,
138
+ } from 'element-plus'
139
+ import { capitalise } from '../utilities'
140
+
141
+ export default {
142
+ name: 'ConnectivityList',
143
+ components: {
144
+ Button,
145
+ Container,
146
+ Icon,
147
+ ElIconWarning,
148
+ },
149
+ props: {
150
+ entry: {
151
+ type: Object,
152
+ default: () => ({
153
+ destinations: [],
154
+ origins: [],
155
+ components: [],
156
+ destinationsWithDatasets: [],
157
+ originsWithDatasets: [],
158
+ componentsWithDatasets: [],
159
+ resource: undefined,
160
+ featuresAlert: undefined,
161
+ }),
162
+ },
163
+ origins: {
164
+ type: Array,
165
+ default: () => []
166
+ },
167
+ components: {
168
+ type: Array,
169
+ default: () => []
170
+ },
171
+ destinations: {
172
+ type: Array,
173
+ default: () => []
174
+ },
175
+ originsWithDatasets: {
176
+ type: Array,
177
+ default: () => []
178
+ },
179
+ componentsWithDatasets: {
180
+ type: Array,
181
+ default: () => []
182
+ },
183
+ destinationsWithDatasets: {
184
+ type: Array,
185
+ default: () => []
186
+ },
187
+ availableAnatomyFacets: {
188
+ type: Array,
189
+ default: () => [],
190
+ },
191
+ connectivityError: {
192
+ type: Object,
193
+ default: () => null,
194
+ }
195
+ },
196
+ data: function () {
197
+ return {
198
+ originDescriptions: {
199
+ motor: 'is the location of the initial cell body of the circuit',
200
+ sensory: 'is the location of the initial cell body in the PNS circuit',
201
+ },
202
+ facetList: [],
203
+ sckanVersion: '',
204
+ }
205
+ },
206
+ watch: {
207
+ availableAnatomyFacets: {
208
+ handler: function (val) {
209
+ this.convertFacetsToList(val)
210
+ },
211
+ immediate: true,
212
+ deep: true,
213
+ },
214
+ },
215
+ computed: {
216
+ originDescription: function () {
217
+ if (
218
+ this.entry &&
219
+ this.entry.title &&
220
+ this.entry.title.toLowerCase().includes('motor')
221
+ ) {
222
+ return this.originDescriptions.motor
223
+ } else {
224
+ return this.originDescriptions.sensory
225
+ }
226
+ },
227
+ },
228
+ methods: {
229
+ capitalise: function (text) {
230
+ return capitalise(text)
231
+ },
232
+ toggleConnectivityTooltip: function (name, option) {
233
+ this.$emit('toggle-connectivity-tooltip', {
234
+ name,
235
+ option
236
+ });
237
+ },
238
+ // shouldShowExploreButton: Checks if the feature is in the list of available anatomy facets
239
+ shouldShowExploreButton: function (features) {
240
+ for (let i = 0; i < features.length; i++) {
241
+ if (this.facetList.includes(features[i].name.toLowerCase())) {
242
+ return true
243
+ }
244
+ }
245
+ return false
246
+ },
247
+ // convertFacetsToList: Converts the available anatomy facets to a list for easy searching
248
+ convertFacetsToList: function (facets) {
249
+ facets.forEach((facet) => {
250
+ if(facet.children) {
251
+ this.convertFacetsToList(facet.children)
252
+ } else {
253
+ this.facetList.push(facet.label.toLowerCase())
254
+ }
255
+ })
256
+ },
257
+ openAll: function () {
258
+ this.$emit('connectivity-action-click', {
259
+ type: 'Facets',
260
+ labels: this.componentsWithDatasets.map((a) => a.name.toLowerCase()),
261
+ })
262
+ },
263
+ openAxons: function () {
264
+ this.$emit('connectivity-action-click', {
265
+ type: 'Facets',
266
+ labels: this.destinationsWithDatasets.map((a) => a.name.toLowerCase()),
267
+ })
268
+ },
269
+ openDendrites: function () {
270
+ this.$emit('connectivity-action-click', {
271
+ type: 'Facets',
272
+ labels: this.originsWithDatasets.map((a) => a.name.toLowerCase()),
273
+ })
274
+ },
275
+ }
276
+ }
277
+ </script>
278
+
279
+ <style lang="scss" scoped>
280
+ .connectivity-list {
281
+ display: flex;
282
+ flex-direction: column;
283
+ gap: 1rem;
284
+ position: relative;
285
+ }
286
+
287
+ .button {
288
+ margin-left: 0px !important;
289
+ margin-top: 0px !important;
290
+ font-size: 14px !important;
291
+ background-color: $app-primary-color;
292
+ color: #fff;
293
+
294
+ &:hover {
295
+ color: #fff !important;
296
+ background-color: #ac76c5 !important;
297
+ border: 1px solid #ac76c5 !important;
298
+ }
299
+
300
+ & + .button {
301
+ margin-top: 10px !important;
302
+ }
303
+ }
304
+
305
+ .icon {
306
+ right: 0px;
307
+ position: absolute;
308
+ top: 10px;
309
+ }
310
+
311
+ .icon:hover {
312
+ cursor: pointer;
313
+ }
314
+
315
+ :deep(.popover-origin-help.el-popover) {
316
+ text-transform: none !important; // need to overide the tooltip text transform
317
+ border: 1px solid $app-primary-color;
318
+ font-weight: 400;
319
+ font-family: Asap, sans-serif, Helvetica;
320
+
321
+ .el-popper__arrow {
322
+ &:before {
323
+ border-color: $app-primary-color;
324
+ background-color: #ffffff;
325
+ }
326
+ }
327
+ }
328
+
329
+ .info {
330
+ color: #8300bf;
331
+ transform: rotate(180deg);
332
+ margin-left: 8px;
333
+ }
334
+
335
+ .attribute-title-container {
336
+ margin-bottom: 0.5em;
337
+ }
338
+
339
+ .attribute-title {
340
+ font-size: 16px;
341
+ font-weight: 600;
342
+ /* font-weight: bold; */
343
+ text-transform: uppercase;
344
+ }
345
+
346
+ .attribute-content {
347
+ font-size: 14px;
348
+ font-weight: 500;
349
+ transition: color 0.25s ease;
350
+ position: relative;
351
+ cursor: default;
352
+
353
+ &:hover {
354
+ color: $app-primary-color;
355
+ }
356
+
357
+ + .attribute-content {
358
+ &::before {
359
+ content: "";
360
+ width: 90%;
361
+ height: 1px;
362
+ background-color: var(--el-border-color);
363
+ position: absolute;
364
+ top: 0;
365
+ left: 0;
366
+ }
367
+ }
368
+
369
+ &:last-of-type {
370
+ margin-bottom: 0.5em;
371
+ }
372
+ }
373
+
374
+ .connectivity-error-container {
375
+ position: sticky;
376
+ bottom: 0.5rem;
377
+ width: 100%;
378
+ min-height: 31px; // placeholder
379
+ margin-top: -10px !important;
380
+ display: flex;
381
+ flex-direction: row;
382
+ align-items: center;
383
+ justify-content: center;
384
+ }
385
+
386
+ .connectivity-error {
387
+ width: fit-content;
388
+ font-size: 12px;
389
+ padding: 0.25rem 0.5rem;
390
+ background-color: var(--el-color-error-light-9);
391
+ border-radius: var(--el-border-radius-small);
392
+ border: 1px solid var(--el-color-error);
393
+ }
394
+ </style>