This commit is contained in:
team3
2026-05-23 01:40:36 +02:00
parent cf26fa2a23
commit f37d15cac9

View File

@@ -160,18 +160,7 @@ export class GraphView {
}, },
}, },
], ],
layout: { layout: { name: "preset", fit: true, padding: 30 },
name: "concentric",
concentric: (node) => -((node.data("depth") as number) ?? 0),
levelWidth: () => 1,
minNodeSpacing: 40,
// @ts-ignore — spacingFactor exists in concentric layout
spacingFactor: 0.9,
fit: true,
padding: 30,
startAngle: -Math.PI / 2,
clockwise: true,
},
}); });
// @ts-ignore — added by cytoscape-node-html-label // @ts-ignore — added by cytoscape-node-html-label
@@ -191,7 +180,6 @@ export class GraphView {
private buildElements(data: GraphData, collections: string[]): cytoscape.ElementDefinition[] { private buildElements(data: GraphData, collections: string[]): cytoscape.ElementDefinition[] {
const els: cytoscape.ElementDefinition[] = []; const els: cytoscape.ElementDefinition[] = [];
els.push({ data: { id: ROOT_ID, label: this.project, depth: 0 } });
const childrenMap = new Map<string | null, string[]>(); const childrenMap = new Map<string | null, string[]>();
for (const e of data.edges) { for (const e of data.edges) {
@@ -201,6 +189,13 @@ export class GraphView {
} }
const known = new Set(collections); const known = new Set(collections);
const positions = this.computePositions(childrenMap, known);
els.push({
data: { id: ROOT_ID, label: this.project, depth: 0 },
position: positions.get(ROOT_ID) ?? { x: 0, y: 0 },
});
const visited = new Set<string>(); const visited = new Set<string>();
const walk = (parentName: string | null, parentId: string, depth: number) => { const walk = (parentName: string | null, parentId: string, depth: number) => {
const children = (childrenMap.get(parentName) ?? []).filter((c) => known.has(c)); const children = (childrenMap.get(parentName) ?? []).filter((c) => known.has(c));
@@ -213,6 +208,7 @@ export class GraphView {
const { w, h } = this.nodeSize(features.length); const { w, h } = this.nodeSize(features.length);
els.push({ els.push({
data: { id, name: child, depth, features, w, h }, data: { id, name: child, depth, features, w, h },
position: positions.get(id) ?? { x: 0, y: 0 },
}); });
els.push({ data: { source: parentId, target: id } }); els.push({ data: { source: parentId, target: id } });
walk(child, id, depth + 1); walk(child, id, depth + 1);
@@ -222,6 +218,52 @@ export class GraphView {
return els; return els;
} }
private computePositions(
childrenMap: Map<string | null, string[]>,
known: Set<string>,
): Map<string, { x: number; y: number }> {
const R1 = 220;
const STEP = 200;
const positions = new Map<string, { x: number; y: number }>();
positions.set(ROOT_ID, { x: 0, y: 0 });
const direct = (childrenMap.get(null) ?? []).filter((c) => known.has(c));
const N = Math.max(direct.length, 1);
const sectorPerDirect = (2 * Math.PI) / N;
const layoutSubtree = (
parentName: string,
parentAngle: number,
allowedSector: number,
parentRadius: number,
) => {
const children = (childrenMap.get(parentName) ?? []).filter((c) => known.has(c));
const k = children.length;
if (k === 0) return;
const newRadius = parentRadius + STEP;
const perChild = allowedSector / k;
for (let i = 0; i < k; i++) {
const angle = parentAngle - allowedSector / 2 + (i + 0.5) * perChild;
positions.set(`n_${children[i]}`, {
x: Math.cos(angle) * newRadius,
y: Math.sin(angle) * newRadius,
});
layoutSubtree(children[i], angle, perChild, newRadius);
}
};
direct.forEach((child, i) => {
const angle = i * sectorPerDirect - Math.PI / 2;
positions.set(`n_${child}`, {
x: Math.cos(angle) * R1,
y: Math.sin(angle) * R1,
});
layoutSubtree(child, angle, sectorPerDirect, R1);
});
return positions;
}
private nodeSize(featureCount: number): { w: number; h: number } { private nodeSize(featureCount: number): { w: number; h: number } {
const w = 180; const w = 180;
const rows = Math.max(1, Math.ceil(featureCount / 2)); const rows = Math.max(1, Math.ceil(featureCount / 2));