@psnext/lscg 0.1.2 → 0.1.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/README.md +69 -3
- package/dist/src/cli.js +68 -13
- package/dist/src/graph/repository.d.ts +4 -1
- package/dist/src/graph/repository.js +106 -6
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +2 -0
- package/dist/src/mcp/server.js +2 -2
- package/dist/src/scanner/discover.js +0 -2
- package/dist/src/scanner/packagePlugin.d.ts +3 -0
- package/dist/src/scanner/packagePlugin.js +168 -0
- package/dist/src/scanner/plugins.d.ts +86 -0
- package/dist/src/scanner/plugins.js +503 -0
- package/dist/src/storage/connection.d.ts +6 -0
- package/dist/src/storage/connection.js +133 -0
- package/dist/src/storage/context-queries.d.ts +20 -0
- package/dist/src/storage/context-queries.js +137 -0
- package/dist/src/storage/database.d.ts +12 -71
- package/dist/src/storage/database.js +12 -562
- package/dist/src/storage/graph-writes.d.ts +17 -0
- package/dist/src/storage/graph-writes.js +126 -0
- package/dist/src/storage/plugin-contributions.d.ts +15 -0
- package/dist/src/storage/plugin-contributions.js +28 -0
- package/dist/src/storage/plugin-graph.d.ts +6 -0
- package/dist/src/storage/plugin-graph.js +81 -0
- package/dist/src/storage/queries.d.ts +25 -0
- package/dist/src/storage/queries.js +129 -0
- package/dist/src/storage/row-decoders.d.ts +8 -0
- package/dist/src/storage/row-decoders.js +37 -0
- package/dist/src/storage/schema.d.ts +2 -2
- package/dist/src/storage/schema.js +13 -2
- package/dist/src/storage/traversal-queries.d.ts +15 -0
- package/dist/src/storage/traversal-queries.js +87 -0
- package/dist/src/types.d.ts +10 -4
- package/dist/src/view/index.d.ts +1 -1
- package/dist/src/view/index.js +2 -2
- package/dist/src/view/model.d.ts +2 -0
- package/dist/src/view/render.d.ts +5 -2
- package/dist/src/view/render.js +81 -13
- package/dist/src/view/templates/icons/call.svg +13 -0
- package/dist/src/view/templates/icons/export.svg +1 -0
- package/dist/src/view/templates/icons/file.svg +9 -0
- package/dist/src/view/templates/icons/import.svg +1 -0
- package/dist/src/view/templates/icons/package.svg +1 -0
- package/dist/src/view/templates/icons/symbol.svg +7 -0
- package/dist/src/view/templates/icons/user.svg +15 -0
- package/dist/src/view/templates/interactive.css +17 -11
- package/dist/src/view/templates/interactive.html +236 -52
- package/package.json +1 -1
|
@@ -28,6 +28,28 @@
|
|
|
28
28
|
<input id="search" type="search" placeholder="Type to filter by label" autocomplete="off" value="{{SEARCH_TERM}}" />
|
|
29
29
|
</label>
|
|
30
30
|
</div>
|
|
31
|
+
<div class="section">
|
|
32
|
+
<label>
|
|
33
|
+
<span class="status">Node display</span>
|
|
34
|
+
<select id="display-select">
|
|
35
|
+
<option value="simple">Simple</option>
|
|
36
|
+
<option value="shape">Shape</option>
|
|
37
|
+
<option value="icon">Icon</option>
|
|
38
|
+
</select>
|
|
39
|
+
</label>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="section">
|
|
42
|
+
<label>
|
|
43
|
+
<span class="status">Layout</span>
|
|
44
|
+
<select id="layout-select">
|
|
45
|
+
<option value="force">Force-directed</option>
|
|
46
|
+
<option value="radial">Radial</option>
|
|
47
|
+
<option value="grid">Grid</option>
|
|
48
|
+
<option value="kind">By kind</option>
|
|
49
|
+
<option value="treemap">Treemap</option>
|
|
50
|
+
</select>
|
|
51
|
+
</label>
|
|
52
|
+
</div>
|
|
31
53
|
<div class="section">
|
|
32
54
|
<label>
|
|
33
55
|
<span class="status">View box zoom</span>
|
|
@@ -57,11 +79,15 @@
|
|
|
57
79
|
import * as d3 from 'https://cdn.jsdelivr.net/npm/d3@7/+esm';
|
|
58
80
|
|
|
59
81
|
const data = {{DATA}};
|
|
82
|
+
let display = data.display ?? 'shape';
|
|
83
|
+
let layoutMode = 'force';
|
|
60
84
|
const app = document.getElementById('app');
|
|
61
85
|
const svg = d3.select('#graph');
|
|
62
86
|
const graphPanel = document.querySelector('.graph-panel');
|
|
63
87
|
const tooltip = document.getElementById('tooltip');
|
|
64
88
|
const searchInput = document.getElementById('search');
|
|
89
|
+
const displaySelect = document.getElementById('display-select');
|
|
90
|
+
const layoutSelect = document.getElementById('layout-select');
|
|
65
91
|
const zoomInput = document.getElementById('zoom');
|
|
66
92
|
const zoomValue = document.getElementById('zoom-value');
|
|
67
93
|
const zoomAllButton = document.getElementById('zoom-all');
|
|
@@ -114,6 +140,30 @@
|
|
|
114
140
|
|
|
115
141
|
function appendNodeShape(group, node) {
|
|
116
142
|
const fill = node.color ?? '#94a3b8';
|
|
143
|
+
if (display === 'simple') {
|
|
144
|
+
group.append('circle')
|
|
145
|
+
.attr('class', 'node-shape node-shape--simple')
|
|
146
|
+
.attr('cx', 0)
|
|
147
|
+
.attr('cy', 0)
|
|
148
|
+
.attr('r', Math.min(node.width, node.height) / 2)
|
|
149
|
+
.attr('fill', fill);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (display === 'icon') {
|
|
153
|
+
const icon = data.icons?.[node.kind];
|
|
154
|
+
if (!icon) throw new Error('Missing icon asset for ' + node.kind);
|
|
155
|
+
group.append('svg')
|
|
156
|
+
.attr('class', 'node-shape node-shape--icon')
|
|
157
|
+
.attr('x', -node.width / 2)
|
|
158
|
+
.attr('y', -node.height / 2)
|
|
159
|
+
.attr('width', node.width)
|
|
160
|
+
.attr('height', node.height)
|
|
161
|
+
.attr('viewBox', icon.viewBox)
|
|
162
|
+
.attr('color', fill)
|
|
163
|
+
.attr('aria-hidden', 'true')
|
|
164
|
+
.html(icon.markup);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
117
167
|
switch (node.shape) {
|
|
118
168
|
case 'rounded-rect':
|
|
119
169
|
group.append('rect')
|
|
@@ -155,6 +205,17 @@
|
|
|
155
205
|
: (-node.width / 2 + node.height / 4) + ',' + (-node.height / 2) + ' ' + (node.width / 2 - node.height / 4) + ',' + (-node.height / 2) + ' ' + (node.width / 2) + ',0 ' + (node.width / 2 - node.height / 4) + ',' + (node.height / 2) + ' ' + (-node.width / 2 + node.height / 4) + ',' + (node.height / 2) + ' ' + (-node.width / 2) + ',0')
|
|
156
206
|
.attr('fill', fill);
|
|
157
207
|
break;
|
|
208
|
+
case 'package':
|
|
209
|
+
group.append('svg')
|
|
210
|
+
.attr('class', 'node-shape node-shape--package')
|
|
211
|
+
.attr('x', -node.width / 2)
|
|
212
|
+
.attr('y', -node.height / 2)
|
|
213
|
+
.attr('width', node.width)
|
|
214
|
+
.attr('height', node.height)
|
|
215
|
+
.attr('viewBox', '0 0 512 512')
|
|
216
|
+
.attr('aria-hidden', 'true')
|
|
217
|
+
.html('<polygon fill="#7E4F1F" points="511.516,107.105 511.788,107.036 264.065,31.004 0.479,106.898 0,106.759 0,411.849 238.676,480.721 238.676,480.159 238.888,480.996 512,412.125 512,107.036"/><polygon fill="#EDA637" points="264.065,31.004 0,107.036 238.676,175.907 511.788,107.036"/><polygon fill="#C27526" points="238.888,480.996 512,412.125 512,107.036 238.888,175.907"/><path fill="#FFEC99" d="M193.016 51.462 78.638 84.393l230.233 73.866v50.416c0 3.323 1.541 6.458 4.172 8.487 2.631 2.029 6.053 2.725 9.268 1.881l99.058-25.986c4.713-1.237 7.999-5.495 7.999-10.369v-54.816L193.016 51.462Z"/><path fill="#FFDC35" d="M486.158 374.572c0 6.29-4.278 11.775-10.378 13.307l-52.647 13.55c-2.779.698-5.736.663-8.41-.367-5.363-2.066-8.677-7.119-8.677-12.576V332.26c0-6.29 4.278-11.775 10.378-13.307l52.647-13.55c2.779-.698 5.736-.663 8.41.367 5.363 2.066 8.677 7.119 8.677 12.576v56.226Z"/><path fill="#FFDC35" d="M429.367 182.689c0 4.872-3.287 9.132-7.999 10.368l-99.058 25.986c-3.214.843-6.637.148-9.268-1.881-2.631-2.029-4.172-5.164-4.172-8.487v-50.416l120.497-30.386v54.816Z"/><path fill="#DF7400" d="m186.692 292.407-40.804-11.978c-5.273-1.548-8.895-6.385-8.895-11.881v-39.76c0-8.263 7.941-14.208 15.87-11.881l40.804 11.978c5.273 1.548 8.895 6.385 8.895 11.881v39.76c0 8.263-7.941 14.208-15.87 11.881Z"/>');
|
|
218
|
+
break;
|
|
158
219
|
case 'user':
|
|
159
220
|
const userIcon = group.append('svg')
|
|
160
221
|
.attr('class', 'node-shape node-shape--user')
|
|
@@ -193,31 +254,68 @@
|
|
|
193
254
|
.force('x', d3.forceX(width / 2).strength(0.05))
|
|
194
255
|
.force('y', d3.forceY(height / 2).strength(0.05));
|
|
195
256
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.join('line')
|
|
199
|
-
.attr('class', (d) => 'edge edge--' + d.kind)
|
|
200
|
-
.attr('marker-end', 'url(#arrow)');
|
|
201
|
-
|
|
202
|
-
const nodes = nodeLayer.selectAll('g')
|
|
203
|
-
.data(nodesData)
|
|
204
|
-
.join('g')
|
|
205
|
-
.attr('class', (d) => 'node node--' + d.kind)
|
|
206
|
-
.call(drag(simulation));
|
|
257
|
+
let links = linkLayer.selectAll('line');
|
|
258
|
+
let nodes = nodeLayer.selectAll('g');
|
|
207
259
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const group = d3.select(this);
|
|
212
|
-
appendNodeShape(group, d);
|
|
260
|
+
function renderNode(group, node) {
|
|
261
|
+
appendNodeShape(group, node);
|
|
262
|
+
group.append('title').text(node.label);
|
|
213
263
|
group.append('text')
|
|
214
264
|
.attr('class', 'node-label')
|
|
215
265
|
.attr('text-anchor', 'middle')
|
|
216
|
-
.attr('dominant-baseline',
|
|
217
|
-
.attr('y',
|
|
218
|
-
.text(
|
|
219
|
-
|
|
220
|
-
|
|
266
|
+
.attr('dominant-baseline', display === 'shape' && node.kind === 'file' ? 'middle' : 'hanging')
|
|
267
|
+
.attr('y', display === 'shape' && node.kind === 'file' ? 0 : (node.height / 2) + 14)
|
|
268
|
+
.text(node.label);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function renderGraph(visibleNodeIds, visibleEdgeIds) {
|
|
272
|
+
links = linkLayer.selectAll('line')
|
|
273
|
+
.data(linksData.filter((edge) => visibleEdgeIds.has(edge.id)), (edge) => edge.id)
|
|
274
|
+
.join(
|
|
275
|
+
(enter) => enter.append('line'),
|
|
276
|
+
(update) => update,
|
|
277
|
+
(exit) => exit.remove()
|
|
278
|
+
)
|
|
279
|
+
.attr('class', (d) => 'edge edge--' + d.kind)
|
|
280
|
+
.attr('marker-end', 'url(#arrow)');
|
|
281
|
+
|
|
282
|
+
nodes = nodeLayer.selectAll('g')
|
|
283
|
+
.data(nodesData.filter((node) => visibleNodeIds.has(node.id)), (node) => node.id)
|
|
284
|
+
.join(
|
|
285
|
+
(enter) => {
|
|
286
|
+
const selection = enter.append('g')
|
|
287
|
+
.attr('class', (d) => 'node node--' + d.kind)
|
|
288
|
+
if (layoutMode === 'force')
|
|
289
|
+
selection.call(drag(simulation));
|
|
290
|
+
|
|
291
|
+
selection.each(function(node) {
|
|
292
|
+
renderNode(d3.select(this), node);
|
|
293
|
+
});
|
|
294
|
+
return selection;
|
|
295
|
+
},
|
|
296
|
+
(update) => update,
|
|
297
|
+
(exit) => exit.remove()
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
nodeLayer.raise();
|
|
301
|
+
bindNodeInteractions();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function updateDisplay() {
|
|
305
|
+
nodes.each(function(d) {
|
|
306
|
+
const group = d3.select(this);
|
|
307
|
+
group.selectAll('.node-shape').remove();
|
|
308
|
+
appendNodeShape(group, d);
|
|
309
|
+
group.select('.node-label')
|
|
310
|
+
.attr('dominant-baseline', display === 'shape' && d.kind === 'file' ? 'middle' : 'hanging')
|
|
311
|
+
.attr('y', display === 'shape' && d.kind === 'file' ? 0 : (d.height / 2) + 14)
|
|
312
|
+
.raise();
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (displaySelect instanceof HTMLSelectElement) {
|
|
317
|
+
displaySelect.value = display;
|
|
318
|
+
}
|
|
221
319
|
|
|
222
320
|
const zoom = d3.zoom().scaleExtent([0.2, 4]).on('zoom', (event) => {
|
|
223
321
|
zoomLayer.attr('transform', event.transform);
|
|
@@ -299,6 +397,74 @@
|
|
|
299
397
|
return typeof endpoint === 'string' ? endpoint : endpoint?.id;
|
|
300
398
|
}
|
|
301
399
|
|
|
400
|
+
function applyLayout(visibleNodeIds) {
|
|
401
|
+
if (layoutMode === 'force') {
|
|
402
|
+
for (const node of nodesData) {
|
|
403
|
+
node.fx = null;
|
|
404
|
+
node.fy = null;
|
|
405
|
+
}
|
|
406
|
+
simulation.alpha(0.8).restart();
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
simulation.stop();
|
|
411
|
+
const visibleNodes = nodesData.filter((node) => visibleNodeIds.has(node.id));
|
|
412
|
+
const { width: canvasWidth, height: canvasHeight } = canvasSize();
|
|
413
|
+
const centerX = canvasWidth / 2;
|
|
414
|
+
const centerY = canvasHeight / 2;
|
|
415
|
+
const padding = 80;
|
|
416
|
+
const radius = Math.max(40, Math.min(canvasWidth, canvasHeight) / 2 - padding);
|
|
417
|
+
const columns = Math.max(1, Math.ceil(Math.sqrt(visibleNodes.length)));
|
|
418
|
+
const rows = Math.max(1, Math.ceil(visibleNodes.length / columns));
|
|
419
|
+
|
|
420
|
+
const orderedNodes = layoutMode === 'kind'
|
|
421
|
+
? [...visibleNodes].sort((left, right) => left.kind.localeCompare(right.kind) || left.label.localeCompare(right.label))
|
|
422
|
+
: visibleNodes;
|
|
423
|
+
const treemapRoot = layoutMode === 'treemap'
|
|
424
|
+
? d3.treemap().size([canvasWidth - padding * 2, canvasHeight - padding * 2]).paddingInner(12)(
|
|
425
|
+
d3.hierarchy({ children: visibleNodes }).sum((node) => node.degree ? Math.max(1, node.degree + 1) : 1)
|
|
426
|
+
)
|
|
427
|
+
: null;
|
|
428
|
+
|
|
429
|
+
orderedNodes.forEach((node, index) => {
|
|
430
|
+
let x = centerX;
|
|
431
|
+
let y = centerY;
|
|
432
|
+
if (layoutMode === 'treemap') {
|
|
433
|
+
const tile = treemapRoot?.leaves().find((leaf) => leaf.data === node);
|
|
434
|
+
x = padding + ((tile?.x0 ?? 0) + (tile?.x1 ?? 0)) / 2;
|
|
435
|
+
y = padding + ((tile?.y0 ?? 0) + (tile?.y1 ?? 0)) / 2;
|
|
436
|
+
} else if (layoutMode === 'radial') {
|
|
437
|
+
const angle = (index * Math.PI * 2) / Math.max(visibleNodes.length, 1) - Math.PI / 2;
|
|
438
|
+
x = centerX + Math.cos(angle) * radius;
|
|
439
|
+
y = centerY + Math.sin(angle) * radius;
|
|
440
|
+
} else {
|
|
441
|
+
const kindIndex = layoutMode === 'kind'
|
|
442
|
+
? orderedNodes.slice(0, index).filter((candidate) => candidate.kind === node.kind).length
|
|
443
|
+
: index;
|
|
444
|
+
const kindCount = layoutMode === 'kind'
|
|
445
|
+
? orderedNodes.filter((candidate) => candidate.kind === node.kind).length
|
|
446
|
+
: visibleNodes.length;
|
|
447
|
+
const kindColumn = layoutMode === 'kind'
|
|
448
|
+
? [...new Set(orderedNodes.map((candidate) => candidate.kind))].indexOf(node.kind)
|
|
449
|
+
: 0;
|
|
450
|
+
const kindColumns = layoutMode === 'kind'
|
|
451
|
+
? Math.max(1, new Set(orderedNodes.map((candidate) => candidate.kind)).size)
|
|
452
|
+
: columns;
|
|
453
|
+
const localColumns = layoutMode === 'kind' ? Math.max(1, Math.ceil(Math.sqrt(kindCount))) : columns;
|
|
454
|
+
const localRows = layoutMode === 'kind' ? Math.max(1, Math.ceil(kindCount / localColumns)) : rows;
|
|
455
|
+
const column = layoutMode === 'kind' ? kindIndex % localColumns : index % columns;
|
|
456
|
+
const row = layoutMode === 'kind' ? Math.floor(kindIndex / localColumns) : Math.floor(index / columns);
|
|
457
|
+
x = layoutMode === 'kind'
|
|
458
|
+
? ((kindColumn + 0.5) * canvasWidth) / kindColumns
|
|
459
|
+
: ((column + 1) * canvasWidth) / (columns + 1);
|
|
460
|
+
y = ((row + 1) * canvasHeight) / (localRows + 1);
|
|
461
|
+
}
|
|
462
|
+
node.x = node.fx = x;
|
|
463
|
+
node.y = node.fy = y;
|
|
464
|
+
});
|
|
465
|
+
ticked();
|
|
466
|
+
}
|
|
467
|
+
|
|
302
468
|
function updateStatus() {
|
|
303
469
|
if (anchorId && focusVisibleOnly) {
|
|
304
470
|
subtitle.textContent = 'Focus on ' + (nodeById.get(anchorId)?.label ?? anchorId);
|
|
@@ -354,22 +520,23 @@
|
|
|
354
520
|
const visibleEdgeIds = visibleSets.visibleEdgeIds;
|
|
355
521
|
const activeAnchorVisible = Boolean(anchorId && visibleNodeIds.has(anchorId));
|
|
356
522
|
|
|
523
|
+
renderGraph(visibleNodeIds, visibleEdgeIds);
|
|
524
|
+
applyLayout(visibleNodeIds);
|
|
525
|
+
|
|
357
526
|
nodes
|
|
358
|
-
.classed('is-
|
|
359
|
-
.classed('is-dimmed', (node) => !focusVisibleOnly && anchorId ? !visibleNodeIds.has(node.id) : false)
|
|
527
|
+
.classed('is-dimmed', () => false)
|
|
360
528
|
.classed('is-selected', (node) => anchorId === node.id)
|
|
361
529
|
.classed('is-highlighted', (node) => activeAnchorVisible && visibleNodeIds.has(node.id));
|
|
362
530
|
|
|
363
531
|
links
|
|
364
|
-
.classed('is-
|
|
365
|
-
.classed('is-
|
|
366
|
-
.classed('is-highlighted', (edge) => visibleEdgeIds.has(edge.id));
|
|
532
|
+
.classed('is-dimmed', () => false)
|
|
533
|
+
.classed('is-highlighted', () => true);
|
|
367
534
|
|
|
368
535
|
updateStatus();
|
|
369
536
|
updateInfo();
|
|
370
537
|
updateZoomState();
|
|
371
538
|
updateLegendState();
|
|
372
|
-
reframe();
|
|
539
|
+
reframe(visibleNodeIds);
|
|
373
540
|
}
|
|
374
541
|
|
|
375
542
|
function setAnchor(nodeId) {
|
|
@@ -392,7 +559,7 @@
|
|
|
392
559
|
}
|
|
393
560
|
const nextSize = canvasSize();
|
|
394
561
|
svg.attr('viewBox', [0, 0, nextSize.width, nextSize.height]);
|
|
395
|
-
reframe();
|
|
562
|
+
reframe(getVisibleSets().visibleNodeIds);
|
|
396
563
|
}
|
|
397
564
|
|
|
398
565
|
function zoomAll() {
|
|
@@ -412,11 +579,11 @@
|
|
|
412
579
|
applyState();
|
|
413
580
|
}
|
|
414
581
|
|
|
415
|
-
function reframe() {
|
|
582
|
+
function reframe(visibleNodeIds = allNodeIds) {
|
|
416
583
|
const selectedNode = anchorId ? nodesData.find((candidate) => candidate.id === anchorId) ?? null : null;
|
|
417
584
|
const { width: canvasWidth, height: canvasHeight } = canvasSize();
|
|
418
585
|
|
|
419
|
-
if (focusMode === 'selected' && selectedNode) {
|
|
586
|
+
if (!focusVisibleOnly && focusMode === 'selected' && selectedNode) {
|
|
420
587
|
const transform = d3.zoomIdentity
|
|
421
588
|
.translate(canvasWidth / 2, canvasHeight / 2)
|
|
422
589
|
.scale(focusScale / zoomFactor)
|
|
@@ -425,12 +592,13 @@
|
|
|
425
592
|
return;
|
|
426
593
|
}
|
|
427
594
|
|
|
428
|
-
|
|
595
|
+
const visibleNodes = nodesData.filter((node) => visibleNodeIds.has(node.id));
|
|
596
|
+
if (visibleNodes.length === 0) {
|
|
429
597
|
svg.call(zoom.transform, d3.zoomIdentity);
|
|
430
598
|
return;
|
|
431
599
|
}
|
|
432
600
|
|
|
433
|
-
const bounds =
|
|
601
|
+
const bounds = visibleNodes.reduce((acc, current) => ({
|
|
434
602
|
minX: Math.min(acc.minX, current.x),
|
|
435
603
|
maxX: Math.max(acc.maxX, current.x),
|
|
436
604
|
minY: Math.min(acc.minY, current.y),
|
|
@@ -491,25 +659,27 @@
|
|
|
491
659
|
nodes.attr('transform', (d) => 'translate(' + d.x + ',' + d.y + ')');
|
|
492
660
|
}
|
|
493
661
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
662
|
+
function bindNodeInteractions() {
|
|
663
|
+
nodes
|
|
664
|
+
.on('click', (event, node) => {
|
|
665
|
+
event.stopPropagation();
|
|
666
|
+
setAnchor(node.id);
|
|
667
|
+
})
|
|
668
|
+
.on('mouseenter', function(event, node) {
|
|
669
|
+
if (!tooltip) return;
|
|
670
|
+
tooltip.style.display = 'block';
|
|
671
|
+
tooltip.textContent = node.label + ' · ' + node.kind;
|
|
672
|
+
})
|
|
673
|
+
.on('mousemove', function(event) {
|
|
674
|
+
if (!tooltip) return;
|
|
675
|
+
tooltip.style.left = event.clientX + 14 + 'px';
|
|
676
|
+
tooltip.style.top = event.clientY + 14 + 'px';
|
|
677
|
+
})
|
|
678
|
+
.on('mouseleave', function() {
|
|
679
|
+
if (!tooltip) return;
|
|
680
|
+
tooltip.style.display = 'none';
|
|
681
|
+
});
|
|
682
|
+
}
|
|
513
683
|
|
|
514
684
|
svg.on('click', (event) => {
|
|
515
685
|
if (event.target === svg.node()) {
|
|
@@ -519,6 +689,20 @@
|
|
|
519
689
|
}
|
|
520
690
|
});
|
|
521
691
|
|
|
692
|
+
displaySelect?.addEventListener('change', (event) => {
|
|
693
|
+
const value = event.target instanceof HTMLSelectElement ? event.target.value : 'shape';
|
|
694
|
+
if (value !== 'simple' && value !== 'shape' && value !== 'icon') return;
|
|
695
|
+
display = value;
|
|
696
|
+
updateDisplay();
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
layoutSelect?.addEventListener('change', (event) => {
|
|
700
|
+
const value = event.target instanceof HTMLSelectElement ? event.target.value : 'force';
|
|
701
|
+
if (value !== 'force' && value !== 'radial' && value !== 'grid' && value !== 'kind' && value !== 'treemap') return;
|
|
702
|
+
layoutMode = value;
|
|
703
|
+
applyState();
|
|
704
|
+
});
|
|
705
|
+
|
|
522
706
|
searchInput.value = filterText;
|
|
523
707
|
searchInput.addEventListener('input', (event) => {
|
|
524
708
|
filterText = event.target.value;
|
|
@@ -560,7 +744,7 @@
|
|
|
560
744
|
if (!graphPanel) return;
|
|
561
745
|
const nextSize = canvasSize();
|
|
562
746
|
svg.attr('viewBox', [0, 0, nextSize.width, nextSize.height]);
|
|
563
|
-
reframe();
|
|
747
|
+
reframe(getVisibleSets().visibleNodeIds);
|
|
564
748
|
});
|
|
565
749
|
|
|
566
750
|
simulation.on('tick', ticked);
|