@abi-software/map-utilities 1.2.0-beta.7 → 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.
- package/dist/map-utilities.js +23739 -7001
- package/dist/map-utilities.umd.cjs +66 -41
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/App.vue +37 -2
- package/src/components/ConnectivityGraph/ConnectivityGraph.vue +631 -0
- package/src/components/ConnectivityGraph/graph.js +355 -0
- package/src/components/Tooltip/AnnotationPopup.vue +2 -0
- package/src/components/TreeControls/TreeControls.vue +3 -3
- package/src/components/index.js +11 -1
- package/src/components.d.ts +6 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/*==============================================================================
|
|
2
|
+
|
|
3
|
+
A viewer for neuron connectivity graphs.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 - 2024 David Brooks
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
==============================================================================*/
|
|
20
|
+
|
|
21
|
+
import cytoscape from 'cytoscape'
|
|
22
|
+
|
|
23
|
+
//==============================================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
//==============================================================================
|
|
27
|
+
|
|
28
|
+
export class ConnectivityGraph extends EventTarget
|
|
29
|
+
{
|
|
30
|
+
cyg = null
|
|
31
|
+
nodes = []
|
|
32
|
+
edges = []
|
|
33
|
+
axons = []
|
|
34
|
+
dendrites = []
|
|
35
|
+
somas = []
|
|
36
|
+
labelCache = new Map()
|
|
37
|
+
graphCanvas = null
|
|
38
|
+
|
|
39
|
+
constructor(labelCache, graphCanvas)
|
|
40
|
+
{
|
|
41
|
+
super()
|
|
42
|
+
this.labelCache = labelCache;
|
|
43
|
+
this.graphCanvas = graphCanvas;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async addConnectivity(knowledge)
|
|
47
|
+
//=====================================================
|
|
48
|
+
{
|
|
49
|
+
this.axons = knowledge.axons.map(node => JSON.stringify(node))
|
|
50
|
+
this.dendrites = knowledge.dendrites.map(node => JSON.stringify(node))
|
|
51
|
+
if (knowledge.somas?.length) {
|
|
52
|
+
this.somas = knowledge.somas.map(node => JSON.stringify(node))
|
|
53
|
+
}
|
|
54
|
+
if (knowledge.connectivity.length) {
|
|
55
|
+
for (const edge of knowledge.connectivity) {
|
|
56
|
+
const e0 = await this.graphNode(edge[0])
|
|
57
|
+
const e1 = await this.graphNode(edge[1])
|
|
58
|
+
this.nodes.push(e0)
|
|
59
|
+
this.nodes.push(e1)
|
|
60
|
+
this.edges.push({
|
|
61
|
+
id: `${e0.id}_${e1.id}`,
|
|
62
|
+
source: e0.id,
|
|
63
|
+
target: e1.id
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
this.nodes.push({
|
|
68
|
+
id: 'MISSING',
|
|
69
|
+
label: 'NO PATHS'
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
showConnectivity(graphCanvas)
|
|
75
|
+
//================
|
|
76
|
+
{
|
|
77
|
+
this.cyg = new CytoscapeGraph(this, graphCanvas)
|
|
78
|
+
|
|
79
|
+
this.cyg.on('tap-node', (event) => {
|
|
80
|
+
const tapEvent = new CustomEvent('tap-node', {
|
|
81
|
+
detail: event.detail
|
|
82
|
+
})
|
|
83
|
+
this.dispatchEvent(tapEvent);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
clearConnectivity()
|
|
88
|
+
//=================
|
|
89
|
+
{
|
|
90
|
+
if (this.cyg?.cy) {
|
|
91
|
+
this.cyg.cy.remove()
|
|
92
|
+
this.cyg.cy = null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
reset()
|
|
97
|
+
//=====
|
|
98
|
+
{
|
|
99
|
+
if (this.cyg?.cy) {
|
|
100
|
+
this.cyg.cy.reset()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
zoom(val)
|
|
105
|
+
//=======
|
|
106
|
+
{
|
|
107
|
+
if (this.cyg?.cy) {
|
|
108
|
+
const currentZoom = this.cyg.cy.zoom()
|
|
109
|
+
const width = this.cyg.cy.width()
|
|
110
|
+
const height = this.cyg.cy.height()
|
|
111
|
+
const positionToRender = {
|
|
112
|
+
x: width/2,
|
|
113
|
+
y: height/2,
|
|
114
|
+
}
|
|
115
|
+
this.cyg.cy.zoom({
|
|
116
|
+
level: currentZoom + val,
|
|
117
|
+
renderedPosition: positionToRender,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
enableZoom(option)
|
|
123
|
+
//================
|
|
124
|
+
{
|
|
125
|
+
if (this.cyg?.cy) {
|
|
126
|
+
this.cyg.cy.userZoomingEnabled(option)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get elements()
|
|
131
|
+
//============
|
|
132
|
+
{
|
|
133
|
+
return [
|
|
134
|
+
...this.nodes.map(n => { return {data: n}}),
|
|
135
|
+
...this.edges.map(e => { return {data: e}})
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get roots()
|
|
140
|
+
//===================
|
|
141
|
+
{
|
|
142
|
+
return [
|
|
143
|
+
...this.dendrites,
|
|
144
|
+
...this.somas
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async graphNode(node)
|
|
149
|
+
//=======================================================
|
|
150
|
+
{
|
|
151
|
+
const id = JSON.stringify(node)
|
|
152
|
+
const label = [node[0], ...node[1]]
|
|
153
|
+
const humanLabels = []
|
|
154
|
+
for (const term of label) {
|
|
155
|
+
const humanLabel = this.labelCache.has(term) ? this.labelCache.get(term) : ''
|
|
156
|
+
humanLabels.push(humanLabel)
|
|
157
|
+
}
|
|
158
|
+
label.push(...humanLabels)
|
|
159
|
+
|
|
160
|
+
const result = {
|
|
161
|
+
id,
|
|
162
|
+
label: label.join('\n')
|
|
163
|
+
}
|
|
164
|
+
if (this.axons.includes(id)) {
|
|
165
|
+
if (this.dendrites.includes(id) || this.somas.includes(id)) {
|
|
166
|
+
result['both-a-d'] = true
|
|
167
|
+
} else {
|
|
168
|
+
result['axon'] = true
|
|
169
|
+
}
|
|
170
|
+
} else if (this.dendrites.includes(id) || this.somas.includes(id)) {
|
|
171
|
+
result['dendrite'] = true
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
return result
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
on(eventName, callback)
|
|
178
|
+
//=====================
|
|
179
|
+
{
|
|
180
|
+
this.addEventListener(eventName, callback)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
//==============================================================================
|
|
185
|
+
|
|
186
|
+
const GRAPH_STYLE = [
|
|
187
|
+
{
|
|
188
|
+
'selector': 'node',
|
|
189
|
+
'style': {
|
|
190
|
+
'label': function(ele) { return trimLabel(ele.data('label')) },
|
|
191
|
+
// 'background-color': '#80F0F0',
|
|
192
|
+
'background-color': 'transparent',
|
|
193
|
+
'background-opacity': '0',
|
|
194
|
+
'text-valign': 'center',
|
|
195
|
+
'text-wrap': 'wrap',
|
|
196
|
+
'width': '80px',
|
|
197
|
+
'height': '80px',
|
|
198
|
+
'text-max-width': '80px',
|
|
199
|
+
'font-size': '6px',
|
|
200
|
+
'shape': 'round-rectangle',
|
|
201
|
+
'border-width': 1,
|
|
202
|
+
'border-style': 'solid',
|
|
203
|
+
'border-color': 'gray',
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
'selector': 'node[axon]',
|
|
208
|
+
'style': {
|
|
209
|
+
// 'background-color': 'green',
|
|
210
|
+
'shape': 'round-diamond',
|
|
211
|
+
'width': '100px',
|
|
212
|
+
'height': '100px',
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
'selector': 'node[dendrite]',
|
|
217
|
+
'style': {
|
|
218
|
+
// 'background-color': 'red',
|
|
219
|
+
'shape': 'ellipse',
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
'selector': 'node[both-a-d]',
|
|
224
|
+
'style': {
|
|
225
|
+
// 'background-color': 'gray',
|
|
226
|
+
'shape': 'round-rectangle',
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
'selector': 'edge',
|
|
231
|
+
'style': {
|
|
232
|
+
'width': 1,
|
|
233
|
+
'line-color': 'dimgray',
|
|
234
|
+
'target-arrow-color': 'dimgray',
|
|
235
|
+
'target-arrow-shape': 'triangle',
|
|
236
|
+
'curve-style': 'bezier'
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
function trimLabel(label) {
|
|
242
|
+
const labels = label.split('\n')
|
|
243
|
+
const half = labels.length/2
|
|
244
|
+
const trimLabels = labels.slice(half)
|
|
245
|
+
return trimLabels.join('\n')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function capitalizeLabels(input) {
|
|
249
|
+
return input.split('\n').map(label => {
|
|
250
|
+
if (label && label[0] >= 'a' && label[0] <= 'z') {
|
|
251
|
+
return label.charAt(0).toUpperCase() + label.slice(1)
|
|
252
|
+
}
|
|
253
|
+
return label
|
|
254
|
+
}).join('\n')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
//==============================================================================
|
|
258
|
+
|
|
259
|
+
class CytoscapeGraph extends EventTarget
|
|
260
|
+
{
|
|
261
|
+
cy
|
|
262
|
+
tooltip
|
|
263
|
+
|
|
264
|
+
constructor(connectivityGraph, graphCanvas)
|
|
265
|
+
{
|
|
266
|
+
super()
|
|
267
|
+
this.cy = cytoscape({
|
|
268
|
+
container: graphCanvas,
|
|
269
|
+
elements: connectivityGraph.elements,
|
|
270
|
+
layout: {
|
|
271
|
+
name: 'breadthfirst',
|
|
272
|
+
circle: false,
|
|
273
|
+
roots: connectivityGraph.roots
|
|
274
|
+
},
|
|
275
|
+
directed: true,
|
|
276
|
+
style: GRAPH_STYLE,
|
|
277
|
+
minZoom: 0.5,
|
|
278
|
+
maxZoom: 10,
|
|
279
|
+
wheelSensitivity: 0.4,
|
|
280
|
+
}).on('mouseover', 'node', this.overNode.bind(this))
|
|
281
|
+
.on('mouseout', 'node', this.exitNode.bind(this))
|
|
282
|
+
.on('position', 'node', this.moveNode.bind(this))
|
|
283
|
+
.on('tap', this.tapNode.bind(this))
|
|
284
|
+
|
|
285
|
+
this.tooltip = document.createElement('div')
|
|
286
|
+
this.tooltip.className = 'cy-graph-tooltip'
|
|
287
|
+
this.tooltip.hidden = true
|
|
288
|
+
graphCanvas?.lastChild?.appendChild(this.tooltip)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
remove()
|
|
292
|
+
//======
|
|
293
|
+
{
|
|
294
|
+
if (this.cy) {
|
|
295
|
+
this.cy.destroy()
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
checkRightBoundary(leftPos)
|
|
300
|
+
//==================================
|
|
301
|
+
{
|
|
302
|
+
if ((leftPos + this.tooltip.offsetWidth) >= this.tooltip.parentElement?.offsetWidth) {
|
|
303
|
+
this.tooltip.style.left = `${leftPos - this.tooltip.offsetWidth}px`
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
overNode(event)
|
|
308
|
+
//==============
|
|
309
|
+
{
|
|
310
|
+
const node = event.target
|
|
311
|
+
const label = capitalizeLabels(node.data().label)
|
|
312
|
+
|
|
313
|
+
this.tooltip.innerText = label
|
|
314
|
+
this.tooltip.style.left = `${event.renderedPosition.x}px`
|
|
315
|
+
this.tooltip.style.top = `${event.renderedPosition.y}px`
|
|
316
|
+
this.tooltip.style.maxWidth = '240px'
|
|
317
|
+
this.tooltip.hidden = false
|
|
318
|
+
|
|
319
|
+
this.checkRightBoundary(event.renderedPosition.x)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
moveNode(event)
|
|
323
|
+
//==============
|
|
324
|
+
{
|
|
325
|
+
const node = event.target
|
|
326
|
+
this.tooltip.style.left = `${node.renderedPosition().x}px`
|
|
327
|
+
this.tooltip.style.top = `${node.renderedPosition().y}px`
|
|
328
|
+
this.checkRightBoundary(node.renderedPosition().x)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
exitNode(event)
|
|
332
|
+
//==============
|
|
333
|
+
{
|
|
334
|
+
this.tooltip.hidden = true
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
tapNode(event)
|
|
338
|
+
//============
|
|
339
|
+
{
|
|
340
|
+
const node = event.target
|
|
341
|
+
const data = node.data()
|
|
342
|
+
const tapEvent = new CustomEvent('tap-node', {
|
|
343
|
+
detail: data
|
|
344
|
+
})
|
|
345
|
+
this.dispatchEvent(tapEvent);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
on(eventName, callback)
|
|
349
|
+
//=====================
|
|
350
|
+
{
|
|
351
|
+
this.addEventListener(eventName, callback)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
//==============================================================================
|
|
@@ -202,7 +202,7 @@ export default {
|
|
|
202
202
|
methods: {
|
|
203
203
|
filterNode: function(value, data) {
|
|
204
204
|
if (!value) return true;
|
|
205
|
-
return data.label ? data.label.toLowerCase().includes(value) : false;
|
|
205
|
+
return data.label ? data.label.toLowerCase().includes(value.toLowerCase()) : false;
|
|
206
206
|
},
|
|
207
207
|
setColour: function (nodeData, value) {
|
|
208
208
|
this.$emit("setColour", nodeData, value);
|
|
@@ -368,7 +368,7 @@ export default {
|
|
|
368
368
|
|
|
369
369
|
:deep(.el-checkbox__label) {
|
|
370
370
|
padding-left: 5px;
|
|
371
|
-
color:
|
|
371
|
+
color: inherit !important;
|
|
372
372
|
font-size: 12px;
|
|
373
373
|
font-weight: 500;
|
|
374
374
|
letter-spacing: 0px;
|
|
@@ -385,7 +385,7 @@ export default {
|
|
|
385
385
|
|
|
386
386
|
.region-tree-node {
|
|
387
387
|
flex: 1;
|
|
388
|
-
color:
|
|
388
|
+
color: inherit !important;
|
|
389
389
|
display: flex;
|
|
390
390
|
font-size: 12px;
|
|
391
391
|
line-height: 14px;
|
package/src/components/index.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import AnnotationPopup from "./Tooltip/AnnotationPopup.vue";
|
|
2
2
|
import CreateTooltipContent from "./Tooltip/CreateTooltipContent.vue";
|
|
3
|
+
import ConnectivityGraph from "./ConnectivityGraph/ConnectivityGraph.vue";
|
|
3
4
|
import CopyToClipboard from "./CopyToClipboard/CopyToClipboard.vue";
|
|
4
5
|
import DrawToolbar from "./DrawToolbar/DrawToolbar.vue";
|
|
5
6
|
import HelpModeDialog from "./HelpModeDialog/HelpModeDialog.vue";
|
|
6
7
|
import Tooltip from "./Tooltip/Tooltip.vue";
|
|
7
8
|
import TreeControls from "./TreeControls/TreeControls.vue";
|
|
8
9
|
|
|
9
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
AnnotationPopup,
|
|
12
|
+
CreateTooltipContent,
|
|
13
|
+
ConnectivityGraph,
|
|
14
|
+
CopyToClipboard,
|
|
15
|
+
DrawToolbar,
|
|
16
|
+
HelpModeDialog,
|
|
17
|
+
Tooltip,
|
|
18
|
+
TreeControls,
|
|
19
|
+
};
|
package/src/components.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare module 'vue' {
|
|
|
9
9
|
export interface GlobalComponents {
|
|
10
10
|
AnnotationPopup: typeof import('./components/Tooltip/AnnotationPopup.vue')['default']
|
|
11
11
|
ConnectionDialog: typeof import('./components/DrawToolbar/ConnectionDialog.vue')['default']
|
|
12
|
+
ConnectivityGraph: typeof import('./components/ConnectivityGraph/ConnectivityGraph.vue')['default']
|
|
12
13
|
CopyToClipboard: typeof import('./components/CopyToClipboard/CopyToClipboard.vue')['default']
|
|
13
14
|
CreateTooltipContent: typeof import('./components/Tooltip/CreateTooltipContent.vue')['default']
|
|
14
15
|
DrawToolbar: typeof import('./components/DrawToolbar/DrawToolbar.vue')['default']
|
|
@@ -20,6 +21,7 @@ declare module 'vue' {
|
|
|
20
21
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
|
21
22
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
|
22
23
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
|
24
|
+
ElIconAim: typeof import('@element-plus/icons-vue')['Aim']
|
|
23
25
|
ElIconArrowDown: typeof import('@element-plus/icons-vue')['ArrowDown']
|
|
24
26
|
ElIconArrowUp: typeof import('@element-plus/icons-vue')['ArrowUp']
|
|
25
27
|
ElIconClose: typeof import('@element-plus/icons-vue')['Close']
|
|
@@ -27,7 +29,11 @@ declare module 'vue' {
|
|
|
27
29
|
ElIconDelete: typeof import('@element-plus/icons-vue')['Delete']
|
|
28
30
|
ElIconEdit: typeof import('@element-plus/icons-vue')['Edit']
|
|
29
31
|
ElIconFinished: typeof import('@element-plus/icons-vue')['Finished']
|
|
32
|
+
ElIconLock: typeof import('@element-plus/icons-vue')['Lock']
|
|
33
|
+
ElIconUnlock: typeof import('@element-plus/icons-vue')['Unlock']
|
|
30
34
|
ElIconWarning: typeof import('@element-plus/icons-vue')['Warning']
|
|
35
|
+
ElIconZoomIn: typeof import('@element-plus/icons-vue')['ZoomIn']
|
|
36
|
+
ElIconZoomOut: typeof import('@element-plus/icons-vue')['ZoomOut']
|
|
31
37
|
ElInput: typeof import('element-plus/es')['ElInput']
|
|
32
38
|
ElMain: typeof import('element-plus/es')['ElMain']
|
|
33
39
|
ElOption: typeof import('element-plus/es')['ElOption']
|