@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abi-software/map-utilities",
|
|
3
|
-
"version": "1.2.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist/*",
|
|
6
6
|
"src/*",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@abi-software/svg-sprite": "^1.0.1",
|
|
33
33
|
"@element-plus/icons-vue": "^2.3.1",
|
|
34
|
+
"cytoscape": "^3.30.2",
|
|
34
35
|
"element-plus": "2.8.4",
|
|
35
36
|
"mitt": "^3.0.1",
|
|
36
37
|
"vue": "^3.4.21"
|
package/src/App.vue
CHANGED
|
@@ -38,6 +38,10 @@ const drawnTypes = [
|
|
|
38
38
|
{ value: "Polygon", label: "Polygon" },
|
|
39
39
|
{ value: "None", label: "None" },
|
|
40
40
|
];
|
|
41
|
+
const showConnectivityGraph = ref(false);
|
|
42
|
+
const connectivityGraphEntry = "ilxtr:neuron-type-aacar-13";
|
|
43
|
+
// const connectivityGraphEntry = "ilxtr:sparc-nlp/kidney/134";
|
|
44
|
+
const mapServer = "https://mapcore-demo.org/curation/flatmap/";
|
|
41
45
|
|
|
42
46
|
onMounted(() => {
|
|
43
47
|
console.log("🚀 ~ onMounted ~ appRef:", appRef.value);
|
|
@@ -452,6 +456,25 @@ function confirmCreate(value) {
|
|
|
452
456
|
</el-button>
|
|
453
457
|
</el-col>
|
|
454
458
|
</el-row>
|
|
459
|
+
<el-row>
|
|
460
|
+
<el-col>
|
|
461
|
+
<h3>Connectivity Graph</h3>
|
|
462
|
+
</el-col>
|
|
463
|
+
<el-col>
|
|
464
|
+
<el-button
|
|
465
|
+
@click="showConnectivityGraph = true"
|
|
466
|
+
size="small"
|
|
467
|
+
>
|
|
468
|
+
Show connectivity graph
|
|
469
|
+
</el-button>
|
|
470
|
+
<el-button
|
|
471
|
+
@click="showConnectivityGraph = false"
|
|
472
|
+
size="small"
|
|
473
|
+
>
|
|
474
|
+
Hide connectivity graph
|
|
475
|
+
</el-button>
|
|
476
|
+
</el-col>
|
|
477
|
+
</el-row>
|
|
455
478
|
|
|
456
479
|
<DrawToolbar
|
|
457
480
|
v-show="isFlatmap"
|
|
@@ -521,7 +544,11 @@ function confirmCreate(value) {
|
|
|
521
544
|
@setColour="setColour"
|
|
522
545
|
@checkChanged="checkChanged"
|
|
523
546
|
/>
|
|
524
|
-
|
|
547
|
+
<ConnectivityGraph
|
|
548
|
+
v-if="showConnectivityGraph"
|
|
549
|
+
:entry="connectivityGraphEntry"
|
|
550
|
+
:map-server="mapServer"
|
|
551
|
+
/>
|
|
525
552
|
</div>
|
|
526
553
|
</template>
|
|
527
554
|
|
|
@@ -545,6 +572,14 @@ function confirmCreate(value) {
|
|
|
545
572
|
border-style: solid;
|
|
546
573
|
border-width: 1px;
|
|
547
574
|
border-color: black;
|
|
548
|
-
|
|
575
|
+
}
|
|
576
|
+
.toolbar-container {
|
|
577
|
+
height: 80px;
|
|
578
|
+
position: relative;
|
|
579
|
+
}
|
|
580
|
+
.connectivity-graph {
|
|
581
|
+
width: 600px;
|
|
582
|
+
height: 600px;
|
|
583
|
+
margin-top: 1rem;
|
|
549
584
|
}
|
|
550
585
|
</style>
|
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="connectivity-graph" v-loading="loading">
|
|
3
|
+
|
|
4
|
+
<div ref="graphCanvas" class="graph-canvas"></div>
|
|
5
|
+
|
|
6
|
+
<div class="control-panel control-panel-tools">
|
|
7
|
+
<div class="tools" :class="{'zoom-locked': zoomEnabled}">
|
|
8
|
+
<el-tooltip
|
|
9
|
+
:content="resetLabel"
|
|
10
|
+
placement="top"
|
|
11
|
+
effect="control-tooltip"
|
|
12
|
+
>
|
|
13
|
+
<el-button
|
|
14
|
+
class="control-button"
|
|
15
|
+
:class="theme"
|
|
16
|
+
size="small"
|
|
17
|
+
@click="reset"
|
|
18
|
+
>
|
|
19
|
+
<el-icon color="white">
|
|
20
|
+
<el-icon-aim />
|
|
21
|
+
</el-icon>
|
|
22
|
+
<span class="visually-hidden">{{ resetLabel }}</span>
|
|
23
|
+
</el-button>
|
|
24
|
+
</el-tooltip>
|
|
25
|
+
|
|
26
|
+
<el-tooltip
|
|
27
|
+
:content="zoomLockLabel"
|
|
28
|
+
placement="top"
|
|
29
|
+
effect="control-tooltip"
|
|
30
|
+
>
|
|
31
|
+
<el-button
|
|
32
|
+
class="control-button"
|
|
33
|
+
:class="theme"
|
|
34
|
+
size="small"
|
|
35
|
+
@click="toggleZoom"
|
|
36
|
+
>
|
|
37
|
+
<el-icon color="white">
|
|
38
|
+
<template v-if="zoomEnabled">
|
|
39
|
+
<el-icon-lock />
|
|
40
|
+
</template>
|
|
41
|
+
<template v-else>
|
|
42
|
+
<el-icon-unlock />
|
|
43
|
+
</template>
|
|
44
|
+
</el-icon>
|
|
45
|
+
<span class="visually-hidden">{{ zoomLockLabel }}</span>
|
|
46
|
+
</el-button>
|
|
47
|
+
</el-tooltip>
|
|
48
|
+
|
|
49
|
+
<el-tooltip
|
|
50
|
+
:content="zoomInLabel"
|
|
51
|
+
placement="left"
|
|
52
|
+
effect="control-tooltip"
|
|
53
|
+
>
|
|
54
|
+
<el-button
|
|
55
|
+
class="control-button"
|
|
56
|
+
:class="theme"
|
|
57
|
+
size="small"
|
|
58
|
+
@click="zoomIn"
|
|
59
|
+
>
|
|
60
|
+
<el-icon color="white">
|
|
61
|
+
<el-icon-zoom-in />
|
|
62
|
+
</el-icon>
|
|
63
|
+
<span class="visually-hidden">{{ zoomInLabel }}</span>
|
|
64
|
+
</el-button>
|
|
65
|
+
</el-tooltip>
|
|
66
|
+
|
|
67
|
+
<el-tooltip
|
|
68
|
+
:content="zoomOutLabel"
|
|
69
|
+
placement="left"
|
|
70
|
+
effect="control-tooltip"
|
|
71
|
+
>
|
|
72
|
+
<el-button
|
|
73
|
+
class="control-button"
|
|
74
|
+
:class="theme"
|
|
75
|
+
size="small"
|
|
76
|
+
@click="zoomOut"
|
|
77
|
+
>
|
|
78
|
+
<el-icon color="white">
|
|
79
|
+
<el-icon-zoom-out />
|
|
80
|
+
</el-icon>
|
|
81
|
+
<span class="visually-hidden">{{ zoomOutLabel }}</span>
|
|
82
|
+
</el-button>
|
|
83
|
+
</el-tooltip>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="control-panel control-panel-nodes">
|
|
88
|
+
<div class="node-key">
|
|
89
|
+
<!-- <div class="key-head">Node type:</div> -->
|
|
90
|
+
<div class="key-box-container">
|
|
91
|
+
<div class="key-box key-box-dendrite">
|
|
92
|
+
Origin
|
|
93
|
+
</div>
|
|
94
|
+
<div class="key-box key-box-node">
|
|
95
|
+
Components
|
|
96
|
+
</div>
|
|
97
|
+
<div class="key-box key-box-axon">
|
|
98
|
+
Destination
|
|
99
|
+
</div>
|
|
100
|
+
<!--
|
|
101
|
+
<div class="key-box key-box-both">
|
|
102
|
+
Both
|
|
103
|
+
</div>
|
|
104
|
+
-->
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div class="connectivity-graph-error" v-if="errorMessage">
|
|
110
|
+
{{ errorMessage }}
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<script>
|
|
117
|
+
import { ConnectivityGraph } from './graph';
|
|
118
|
+
|
|
119
|
+
const MIN_SCHEMA_VERSION = 1.3;
|
|
120
|
+
const CACHE_LIFETIME = 24 * 60 * 60 * 1000; // One day
|
|
121
|
+
const RESET_LABEL = 'Reset position';
|
|
122
|
+
const ZOOM_LOCK_LABEL = 'Lock zoom';
|
|
123
|
+
const ZOOM_UNLOCK_LABEL = 'Unlock zoom';
|
|
124
|
+
const ZOOM_IN_LABEL = 'Zoom in';
|
|
125
|
+
const ZOOM_OUT_LABEL = 'Zoom out';
|
|
126
|
+
const ZOOM_INCREMENT = 0.25;
|
|
127
|
+
const APP_PRIMARY_COLOR = '#8300bf';
|
|
128
|
+
|
|
129
|
+
export default {
|
|
130
|
+
name: 'ConnectivityGraph',
|
|
131
|
+
props: {
|
|
132
|
+
/**
|
|
133
|
+
* Entity to load its connectivity graph.
|
|
134
|
+
*/
|
|
135
|
+
entry: {
|
|
136
|
+
type: String,
|
|
137
|
+
default: '',
|
|
138
|
+
},
|
|
139
|
+
mapServer: {
|
|
140
|
+
type: String,
|
|
141
|
+
default: '',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
data: function () {
|
|
145
|
+
return {
|
|
146
|
+
loading: true,
|
|
147
|
+
connectivityGraph: null,
|
|
148
|
+
selectedSource: '',
|
|
149
|
+
pathList: [],
|
|
150
|
+
schemaVersion: '',
|
|
151
|
+
knowledgeByPath: new Map(),
|
|
152
|
+
labelledTerms: new Set(),
|
|
153
|
+
labelCache: new Map(),
|
|
154
|
+
resetLabel: RESET_LABEL,
|
|
155
|
+
zoomLockLabel: ZOOM_LOCK_LABEL,
|
|
156
|
+
zoomInLabel: ZOOM_IN_LABEL,
|
|
157
|
+
zoomOutLabel: ZOOM_OUT_LABEL,
|
|
158
|
+
iconColor: APP_PRIMARY_COLOR,
|
|
159
|
+
zoomEnabled: false,
|
|
160
|
+
errorMessage: '',
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
mounted() {
|
|
164
|
+
this.refreshCache();
|
|
165
|
+
this.loadCacheData();
|
|
166
|
+
this.run().then((res) => {
|
|
167
|
+
this.showGraph(this.entry);
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
methods: {
|
|
171
|
+
loadCacheData: function () {
|
|
172
|
+
const selectedSource = sessionStorage.getItem('connectivity-graph-source');
|
|
173
|
+
const labelCache = sessionStorage.getItem('connectivity-graph-labels');
|
|
174
|
+
const pathList = sessionStorage.getItem('connectivity-graph-pathlist');
|
|
175
|
+
const schemaVersion = sessionStorage.getItem('connectivity-graph-schema-version');
|
|
176
|
+
|
|
177
|
+
if (selectedSource) {
|
|
178
|
+
this.selectedSource = selectedSource;
|
|
179
|
+
}
|
|
180
|
+
if (pathList) {
|
|
181
|
+
this.pathList = JSON.parse(pathList);
|
|
182
|
+
}
|
|
183
|
+
if (labelCache) {
|
|
184
|
+
const labelCacheObj = JSON.parse(labelCache);
|
|
185
|
+
this.labelCache = new Map(Object.entries(labelCacheObj));
|
|
186
|
+
}
|
|
187
|
+
if (schemaVersion) {
|
|
188
|
+
this.schemaVersion = schemaVersion;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
removeAllCacheData: function () {
|
|
192
|
+
const keys = [
|
|
193
|
+
'connectivity-graph-expiry',
|
|
194
|
+
'connectivity-graph-source',
|
|
195
|
+
'connectivity-graph-labels',
|
|
196
|
+
'connectivity-graph-pathlist',
|
|
197
|
+
'connectivity-graph-schema-version',
|
|
198
|
+
];
|
|
199
|
+
keys.forEach((key) => {
|
|
200
|
+
sessionStorage.removeItem(key);
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
refreshCache: function () {
|
|
204
|
+
const expiry = sessionStorage.getItem('connectivity-graph-expiry');
|
|
205
|
+
const now = new Date();
|
|
206
|
+
|
|
207
|
+
if (now.getTime() > expiry) {
|
|
208
|
+
this.removeAllCacheData();
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
updateCacheExpiry: function () {
|
|
212
|
+
const now = new Date();
|
|
213
|
+
const expiry = now.getTime() + CACHE_LIFETIME;
|
|
214
|
+
|
|
215
|
+
sessionStorage.setItem('connectivity-graph-expiry', expiry);
|
|
216
|
+
},
|
|
217
|
+
run: async function () {
|
|
218
|
+
if (!this.schemaVersion) {
|
|
219
|
+
this.schemaVersion = await this.getSchemaVersion();
|
|
220
|
+
sessionStorage.setItem('connectivity-graph-schema-version', this.schemaVersion);
|
|
221
|
+
this.updateCacheExpiry();
|
|
222
|
+
}
|
|
223
|
+
if (this.schemaVersion < MIN_SCHEMA_VERSION) {
|
|
224
|
+
console.warn('No Server!');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.showSpinner();
|
|
228
|
+
if (!this.selectedSource) {
|
|
229
|
+
this.selectedSource = await this.setSourceList();
|
|
230
|
+
sessionStorage.setItem('connectivity-graph-source', this.selectedSource);
|
|
231
|
+
this.updateCacheExpiry();
|
|
232
|
+
}
|
|
233
|
+
await this.setPathList(this.selectedSource);
|
|
234
|
+
this.hideSpinner();
|
|
235
|
+
},
|
|
236
|
+
showGraph: async function (neuronPath) {
|
|
237
|
+
const graphCanvas = this.$refs.graphCanvas;
|
|
238
|
+
|
|
239
|
+
this.showSpinner();
|
|
240
|
+
|
|
241
|
+
this.connectivityGraph = new ConnectivityGraph(this.labelCache, graphCanvas);
|
|
242
|
+
await this.connectivityGraph.addConnectivity(this.knowledgeByPath.get(neuronPath));
|
|
243
|
+
|
|
244
|
+
this.hideSpinner();
|
|
245
|
+
|
|
246
|
+
this.connectivityGraph.showConnectivity(graphCanvas);
|
|
247
|
+
|
|
248
|
+
this.connectivityGraph.on('tap-node', (event) => {
|
|
249
|
+
const { label } = event.detail;
|
|
250
|
+
const labels = label ? label.split(`\n`) : [];
|
|
251
|
+
/**
|
|
252
|
+
* This event is triggered after a node on the connectivity graph is clicked.
|
|
253
|
+
*/
|
|
254
|
+
this.$emit('tap-node', labels);
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
query: async function (sql, params) {
|
|
258
|
+
const url = `${this.mapServer}knowledge/query/`;
|
|
259
|
+
const query = { sql, params };
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetch(url, {
|
|
263
|
+
method: 'POST',
|
|
264
|
+
headers: {
|
|
265
|
+
"Accept": "application/json; charset=utf-8",
|
|
266
|
+
"Cache-Control": "no-store",
|
|
267
|
+
"Content-Type": "application/json"
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify(query)
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(`Cannot access ${url}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return await response.json();
|
|
277
|
+
} catch {
|
|
278
|
+
return {
|
|
279
|
+
values: []
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
setSourceList: async function () {
|
|
284
|
+
const data = await this.getJsonData(`${this.mapServer}knowledge/sources`);
|
|
285
|
+
const sources = data ? (data.sources || []) : [];
|
|
286
|
+
|
|
287
|
+
// Order with most recent first...
|
|
288
|
+
let firstSource = '';
|
|
289
|
+
const sourceList = [];
|
|
290
|
+
|
|
291
|
+
for (const source of sources) {
|
|
292
|
+
if (source) {
|
|
293
|
+
sourceList.push(source);
|
|
294
|
+
|
|
295
|
+
if (firstSource === '') {
|
|
296
|
+
firstSource = source;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return firstSource;
|
|
302
|
+
},
|
|
303
|
+
loadPathData: async function (source) {
|
|
304
|
+
const data = await this.query(
|
|
305
|
+
`select entity, knowledge from knowledge
|
|
306
|
+
where entity like 'ilxtr:%' and source=?
|
|
307
|
+
order by entity`,
|
|
308
|
+
[source]);
|
|
309
|
+
const pathList = data ? data.values : [];
|
|
310
|
+
return pathList;
|
|
311
|
+
},
|
|
312
|
+
setPathList: async function (source) {
|
|
313
|
+
if (!this.pathList.length) {
|
|
314
|
+
this.pathList = await this.loadPathData(source);
|
|
315
|
+
sessionStorage.setItem('connectivity-graph-pathlist', JSON.stringify(this.pathList));
|
|
316
|
+
this.updateCacheExpiry();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this.knowledgeByPath.clear();
|
|
320
|
+
this.labelledTerms = new Set();
|
|
321
|
+
|
|
322
|
+
for (const [key, jsonKnowledge] of this.pathList) {
|
|
323
|
+
const knowledge = JSON.parse(jsonKnowledge);
|
|
324
|
+
if ('connectivity' in knowledge) {
|
|
325
|
+
this.knowledgeByPath.set(key, knowledge);
|
|
326
|
+
this.cacheLabels(knowledge);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!this.labelCache.size) {
|
|
331
|
+
await this.getCachedTermLabels();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return '';
|
|
335
|
+
},
|
|
336
|
+
getSchemaVersion: async function () {
|
|
337
|
+
const data = await this.getJsonData(`${this.mapServer}knowledge/schema-version`);
|
|
338
|
+
return data ? (+data.version || 0) : 0;
|
|
339
|
+
},
|
|
340
|
+
getJsonData: async function (url) {
|
|
341
|
+
try {
|
|
342
|
+
const response = await fetch(url, {
|
|
343
|
+
method: 'GET',
|
|
344
|
+
headers: {
|
|
345
|
+
"Accept": "application/json; charset=utf-8",
|
|
346
|
+
"Cache-Control": "no-store",
|
|
347
|
+
"Content-Type": "application/json"
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
if (!response.ok) {
|
|
352
|
+
console.error(`Cannot access ${url}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return await response.json();
|
|
356
|
+
} catch {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
getCachedTermLabels: async function () {
|
|
361
|
+
if (this.labelledTerms.size) {
|
|
362
|
+
const data = await this.query(
|
|
363
|
+
`select entity, knowledge from knowledge
|
|
364
|
+
where entity in (?${', ?'.repeat(this.labelledTerms.size-1)})
|
|
365
|
+
order by source desc`,
|
|
366
|
+
[...this.labelledTerms.values()]
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
let last_entity = null;
|
|
370
|
+
for (const [key, jsonKnowledge] of data.values) {
|
|
371
|
+
if (key !== last_entity) {
|
|
372
|
+
const knowledge = JSON.parse(jsonKnowledge);
|
|
373
|
+
this.labelCache.set(key, knowledge['label'] || key);
|
|
374
|
+
last_entity = key;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const labelCacheObj = Object.fromEntries(this.labelCache);
|
|
379
|
+
sessionStorage.setItem('connectivity-graph-labels', JSON.stringify(labelCacheObj));
|
|
380
|
+
this.updateCacheExpiry();
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
cacheNodeLabels: function (node) {
|
|
384
|
+
for (const term of [node[0], ...node[1]]) {
|
|
385
|
+
this.labelledTerms.add(term);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
cacheLabels: async function (knowledge) {
|
|
389
|
+
for (const edge of knowledge.connectivity) {
|
|
390
|
+
this.cacheNodeLabels(edge[0]);
|
|
391
|
+
this.cacheNodeLabels(edge[1]);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
showSpinner: function () {
|
|
395
|
+
this.loading = true;
|
|
396
|
+
},
|
|
397
|
+
hideSpinner: function () {
|
|
398
|
+
this.loading = false;
|
|
399
|
+
},
|
|
400
|
+
reset: function () {
|
|
401
|
+
this.connectivityGraph.reset();
|
|
402
|
+
},
|
|
403
|
+
zoomIn: function () {
|
|
404
|
+
this.connectivityGraph.zoom(ZOOM_INCREMENT);
|
|
405
|
+
},
|
|
406
|
+
zoomOut: function () {
|
|
407
|
+
this.connectivityGraph.zoom(-ZOOM_INCREMENT);
|
|
408
|
+
},
|
|
409
|
+
/**
|
|
410
|
+
* Enable/disable user zoom for scrolling
|
|
411
|
+
*/
|
|
412
|
+
toggleZoom: function () {
|
|
413
|
+
this.zoomEnabled = !this.zoomEnabled;
|
|
414
|
+
this.zoomLockLabel = this.zoomEnabled ? ZOOM_UNLOCK_LABEL : ZOOM_LOCK_LABEL;
|
|
415
|
+
this.connectivityGraph.enableZoom(!this.zoomEnabled);
|
|
416
|
+
},
|
|
417
|
+
showErrorMessage: function (errorMessage) {
|
|
418
|
+
this.errorMessage = errorMessage;
|
|
419
|
+
// Show error for 3 seconds
|
|
420
|
+
setTimeout(() => {
|
|
421
|
+
this.errorMessage = '';
|
|
422
|
+
}, 3000);
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
</script>
|
|
427
|
+
|
|
428
|
+
<style lang="scss" scoped>
|
|
429
|
+
.connectivity-graph,
|
|
430
|
+
.graph-canvas {
|
|
431
|
+
width: 100%;
|
|
432
|
+
height: 600px;
|
|
433
|
+
background-color: white;
|
|
434
|
+
position: relative;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.connectivity-graph {
|
|
438
|
+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
|
|
439
|
+
border: solid 1px #e4e7ed;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.control-panel {
|
|
443
|
+
position: absolute;
|
|
444
|
+
right: 1rem;
|
|
445
|
+
|
|
446
|
+
&-tools {
|
|
447
|
+
top: 1rem;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
&-nodes {
|
|
451
|
+
bottom: 1rem;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.node-key {
|
|
456
|
+
padding: 0.5rem;
|
|
457
|
+
font-size: 12px;
|
|
458
|
+
border: 1px solid var(--el-border-color);
|
|
459
|
+
background-color: rgba(#f7faff, 0.85);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.key-head {
|
|
463
|
+
text-align: center;
|
|
464
|
+
font-weight: bold;
|
|
465
|
+
border-bottom: 1px solid var(--el-border-color);
|
|
466
|
+
padding-bottom: 4px;
|
|
467
|
+
margin-bottom: 0.5rem;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.key-box-container {
|
|
471
|
+
display: flex;
|
|
472
|
+
flex-direction: row;
|
|
473
|
+
gap: 1rem;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.key-box {
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
gap: 0.35rem;
|
|
480
|
+
position: relative;
|
|
481
|
+
line-height: 1;
|
|
482
|
+
|
|
483
|
+
&::before {
|
|
484
|
+
content: "";
|
|
485
|
+
display: block;
|
|
486
|
+
width: 14px;
|
|
487
|
+
height: 14px;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
&-node::before,
|
|
491
|
+
&-both::before {
|
|
492
|
+
border: 1px solid gray;
|
|
493
|
+
border-radius: var(--el-border-radius-small);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// &-node {
|
|
497
|
+
// background: #80F0F0;
|
|
498
|
+
// }
|
|
499
|
+
|
|
500
|
+
// &-both {
|
|
501
|
+
// background: gray;
|
|
502
|
+
// }
|
|
503
|
+
|
|
504
|
+
&-axon::before {
|
|
505
|
+
border: 1px solid gray;
|
|
506
|
+
border-radius: var(--el-border-radius-small);
|
|
507
|
+
transform: rotate(45deg);
|
|
508
|
+
// background: green;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
&-dendrite::before {
|
|
512
|
+
border: 1px solid gray;
|
|
513
|
+
border-radius: 50%;
|
|
514
|
+
// background: red;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.tools {
|
|
519
|
+
display: grid;
|
|
520
|
+
grid-template-columns: repeat(2, 1fr);
|
|
521
|
+
grid-template-rows: repeat(3, 1fr);
|
|
522
|
+
gap: 0.5rem;
|
|
523
|
+
|
|
524
|
+
:deep(.el-button:nth-child(3)) {
|
|
525
|
+
grid-column: 2;
|
|
526
|
+
grid-row: 2;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
:deep(.el-button:nth-child(4)) {
|
|
530
|
+
grid-column: 2;
|
|
531
|
+
grid-row: 3;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
:deep(.el-button:nth-child(3)),
|
|
535
|
+
:deep(.el-button:nth-child(4)) {
|
|
536
|
+
opacity: 0;
|
|
537
|
+
visibility: hidden;
|
|
538
|
+
pointer-events: none;
|
|
539
|
+
transform: translateY(-100%);
|
|
540
|
+
transition: all 0.25s ease;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
&.zoom-locked {
|
|
544
|
+
:deep(.el-button:nth-child(3)),
|
|
545
|
+
:deep(.el-button:nth-child(4)) {
|
|
546
|
+
opacity: 1;
|
|
547
|
+
visibility: visible;
|
|
548
|
+
pointer-events: initial;
|
|
549
|
+
transform: translateY(0%);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
:deep(.el-button:nth-child(4)) {
|
|
553
|
+
transition-delay: 0.125s;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.control-button {
|
|
559
|
+
width: 24px;
|
|
560
|
+
height: 24px;
|
|
561
|
+
margin: 0 !important;
|
|
562
|
+
padding: 0 !important;
|
|
563
|
+
font-size: 16px !important;
|
|
564
|
+
border-color: $app-primary-color !important;
|
|
565
|
+
border-radius: 50%;
|
|
566
|
+
background: $app-primary-color !important;
|
|
567
|
+
transition: all 0.25s ease;
|
|
568
|
+
|
|
569
|
+
svg {
|
|
570
|
+
margin: 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
&,
|
|
574
|
+
&:focus,
|
|
575
|
+
&:active {
|
|
576
|
+
box-shadow: none !important;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
:deep(.cy-graph-tooltip) {
|
|
581
|
+
padding: 4px 10px;
|
|
582
|
+
font-family: Asap;
|
|
583
|
+
font-size: 12px;
|
|
584
|
+
background: #f3ecf6 !important;
|
|
585
|
+
border: 1px solid $app-primary-color;
|
|
586
|
+
border-radius: var(--el-border-radius-base);
|
|
587
|
+
position: relative;
|
|
588
|
+
top: 0;
|
|
589
|
+
left: 0;
|
|
590
|
+
width: fit-content;
|
|
591
|
+
z-index: 1;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.connectivity-graph-error {
|
|
595
|
+
position: absolute;
|
|
596
|
+
top: 1rem;
|
|
597
|
+
left: 50%;
|
|
598
|
+
transform: translateX(-50%);
|
|
599
|
+
width: fit-content;
|
|
600
|
+
font-size: 12px;
|
|
601
|
+
padding: 0.25rem 0.5rem;
|
|
602
|
+
background-color: var(--el-color-error-light-9);
|
|
603
|
+
border-radius: var(--el-border-radius-small);
|
|
604
|
+
border: 1px solid var(--el-color-error);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.visually-hidden {
|
|
608
|
+
clip: rect(0 0 0 0);
|
|
609
|
+
clip-path: inset(50%);
|
|
610
|
+
height: 1px;
|
|
611
|
+
overflow: hidden;
|
|
612
|
+
position: absolute;
|
|
613
|
+
white-space: nowrap;
|
|
614
|
+
width: 1px;
|
|
615
|
+
}
|
|
616
|
+
</style>
|
|
617
|
+
|
|
618
|
+
<style lang="scss">
|
|
619
|
+
.el-popper.is-control-tooltip {
|
|
620
|
+
padding: 4px 10px;
|
|
621
|
+
font-family: Asap;
|
|
622
|
+
background: #f3ecf6 !important;
|
|
623
|
+
border: 1px solid $app-primary-color;
|
|
624
|
+
|
|
625
|
+
& .el-popper__arrow::before {
|
|
626
|
+
border: 1px solid;
|
|
627
|
+
border-color: $app-primary-color;
|
|
628
|
+
background: #f3ecf6;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
</style>
|