spytial-core 1.5.1 → 1.5.2
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.
|
@@ -91601,6 +91601,7 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91601
91601
|
this.initializeDOM();
|
|
91602
91602
|
this.initializeD3();
|
|
91603
91603
|
this.lineFunction = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveBasis);
|
|
91604
|
+
this.gridLineFunction = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveLinear);
|
|
91604
91605
|
this.inputModeEnabled = isInputAllowed;
|
|
91605
91606
|
this.initializeInputModeHandlers();
|
|
91606
91607
|
}
|
|
@@ -91913,6 +91914,13 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91913
91914
|
<button id="zoom-out" title="Zoom Out" aria-label="Zoom out">\u2212</button>
|
|
91914
91915
|
<button id="zoom-fit" title="Fit to View" aria-label="Fit graph to view">\u2922</button>
|
|
91915
91916
|
</div>
|
|
91917
|
+
<div id="routing-control">
|
|
91918
|
+
<label for="routing-mode">Routing:</label>
|
|
91919
|
+
<select id="routing-mode" title="Edge routing mode">
|
|
91920
|
+
<option value="default">Default</option>
|
|
91921
|
+
<option value="grid">Grid</option>
|
|
91922
|
+
</select>
|
|
91923
|
+
</div>
|
|
91916
91924
|
</div>
|
|
91917
91925
|
<div id="svg-container">
|
|
91918
91926
|
<span id="error-icon" title="This graph is depicting an error state">\u26A0\uFE0F</span>
|
|
@@ -91986,8 +91994,42 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91986
91994
|
this.resetViewToFitContent();
|
|
91987
91995
|
});
|
|
91988
91996
|
}
|
|
91997
|
+
const routingModeSelect = this.shadowRoot.querySelector("#routing-mode");
|
|
91998
|
+
if (routingModeSelect) {
|
|
91999
|
+
const currentFormat = this.layoutFormat || "default";
|
|
92000
|
+
routingModeSelect.value = currentFormat;
|
|
92001
|
+
routingModeSelect.addEventListener("change", () => {
|
|
92002
|
+
this.handleRoutingModeChange(routingModeSelect.value);
|
|
92003
|
+
});
|
|
92004
|
+
}
|
|
91989
92005
|
this.updateZoomControlStates();
|
|
91990
92006
|
}
|
|
92007
|
+
/**
|
|
92008
|
+
* Handle routing mode change from dropdown
|
|
92009
|
+
*/
|
|
92010
|
+
handleRoutingModeChange(mode) {
|
|
92011
|
+
this.setAttribute("layoutFormat", mode);
|
|
92012
|
+
if (this.currentLayout && this.colaLayout) {
|
|
92013
|
+
if (mode === "grid") {
|
|
92014
|
+
this.gridify(10, 25, 10);
|
|
92015
|
+
} else {
|
|
92016
|
+
this.routeEdges();
|
|
92017
|
+
}
|
|
92018
|
+
this.dispatchEvent(new CustomEvent("routing-mode-changed", {
|
|
92019
|
+
detail: { mode }
|
|
92020
|
+
}));
|
|
92021
|
+
}
|
|
92022
|
+
}
|
|
92023
|
+
/**
|
|
92024
|
+
* Update the routing mode dropdown to match current layoutFormat attribute
|
|
92025
|
+
*/
|
|
92026
|
+
updateRoutingModeDropdown() {
|
|
92027
|
+
const routingModeSelect = this.shadowRoot?.querySelector("#routing-mode");
|
|
92028
|
+
if (routingModeSelect) {
|
|
92029
|
+
const currentFormat = this.layoutFormat || "default";
|
|
92030
|
+
routingModeSelect.value = currentFormat;
|
|
92031
|
+
}
|
|
92032
|
+
}
|
|
91991
92033
|
/**
|
|
91992
92034
|
* Initialize keyboard event handlers for input mode activation
|
|
91993
92035
|
*/
|
|
@@ -92485,6 +92527,7 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
92485
92527
|
nodePositions: this.getNodePositions()
|
|
92486
92528
|
}
|
|
92487
92529
|
}));
|
|
92530
|
+
this.updateRoutingModeDropdown();
|
|
92488
92531
|
this.hideLoading();
|
|
92489
92532
|
});
|
|
92490
92533
|
try {
|
|
@@ -93351,6 +93394,7 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93351
93394
|
}).attr("opacity", this.isInputModeActive ? 0.8 : 0).style("pointer-events", this.isInputModeActive ? "all" : "none").raise();
|
|
93352
93395
|
}
|
|
93353
93396
|
gridUpdatePositions() {
|
|
93397
|
+
this.ensureNodeBounds(true);
|
|
93354
93398
|
const node = this.container.selectAll(".node");
|
|
93355
93399
|
const mostSpecificTypeLabel = this.container.selectAll(".mostSpecificTypeLabel");
|
|
93356
93400
|
const label = this.container.selectAll(".label");
|
|
@@ -93406,8 +93450,53 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93406
93450
|
}).attr("y", function(d) {
|
|
93407
93451
|
return d.bounds.y + 12;
|
|
93408
93452
|
}).attr("text-anchor", "middle").raise();
|
|
93409
|
-
const linkGroups = this.container.selectAll(".
|
|
93410
|
-
linkGroups.select("
|
|
93453
|
+
const linkGroups = this.container.selectAll(".link-group");
|
|
93454
|
+
linkGroups.select("path").attr("d", (d) => {
|
|
93455
|
+
const sourceX = d.source?.x ?? d.source?.bounds?.cx() ?? 0;
|
|
93456
|
+
const sourceY = d.source?.y ?? d.source?.bounds?.cy() ?? 0;
|
|
93457
|
+
const targetX = d.target?.x ?? d.target?.bounds?.cx() ?? 0;
|
|
93458
|
+
const targetY = d.target?.y ?? d.target?.bounds?.cy() ?? 0;
|
|
93459
|
+
const dx = targetX - sourceX;
|
|
93460
|
+
const dy = targetY - sourceY;
|
|
93461
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
93462
|
+
const midX = sourceX + dx / 2;
|
|
93463
|
+
return this.gridLineFunction([
|
|
93464
|
+
{ x: sourceX, y: sourceY },
|
|
93465
|
+
{ x: midX, y: sourceY },
|
|
93466
|
+
{ x: midX, y: targetY },
|
|
93467
|
+
{ x: targetX, y: targetY }
|
|
93468
|
+
]);
|
|
93469
|
+
} else {
|
|
93470
|
+
const midY = sourceY + dy / 2;
|
|
93471
|
+
return this.gridLineFunction([
|
|
93472
|
+
{ x: sourceX, y: sourceY },
|
|
93473
|
+
{ x: sourceX, y: midY },
|
|
93474
|
+
{ x: targetX, y: midY },
|
|
93475
|
+
{ x: targetX, y: targetY }
|
|
93476
|
+
]);
|
|
93477
|
+
}
|
|
93478
|
+
});
|
|
93479
|
+
linkGroups.select("text.linklabel").attr("x", (d) => {
|
|
93480
|
+
const pathElement = this.shadowRoot?.querySelector(`path[data-link-id="${d.id}"]`);
|
|
93481
|
+
if (pathElement) {
|
|
93482
|
+
const pathLength = pathElement.getTotalLength();
|
|
93483
|
+
const midpoint = pathElement.getPointAtLength(pathLength / 2);
|
|
93484
|
+
return midpoint.x;
|
|
93485
|
+
}
|
|
93486
|
+
const sourceX = d.source?.x ?? d.source?.bounds?.cx() ?? 0;
|
|
93487
|
+
const targetX = d.target?.x ?? d.target?.bounds?.cx() ?? 0;
|
|
93488
|
+
return (sourceX + targetX) / 2;
|
|
93489
|
+
}).attr("y", (d) => {
|
|
93490
|
+
const pathElement = this.shadowRoot?.querySelector(`path[data-link-id="${d.id}"]`);
|
|
93491
|
+
if (pathElement) {
|
|
93492
|
+
const pathLength = pathElement.getTotalLength();
|
|
93493
|
+
const midpoint = pathElement.getPointAtLength(pathLength / 2);
|
|
93494
|
+
return midpoint.y;
|
|
93495
|
+
}
|
|
93496
|
+
const sourceY = d.source?.y ?? d.source?.bounds?.cy() ?? 0;
|
|
93497
|
+
const targetY = d.target?.y ?? d.target?.bounds?.cy() ?? 0;
|
|
93498
|
+
return (sourceY + targetY) / 2;
|
|
93499
|
+
}).raise();
|
|
93411
93500
|
}
|
|
93412
93501
|
/**
|
|
93413
93502
|
* Advanced edge routing with curvature calculation and overlap handling.
|
|
@@ -93438,12 +93527,19 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93438
93527
|
* When using prior positions with minimal iterations, WebCola may not
|
|
93439
93528
|
* have computed bounds for nodes. This method manually creates Rectangle
|
|
93440
93529
|
* bounds based on node x, y, width, height properties.
|
|
93530
|
+
*
|
|
93531
|
+
* @param forceRecompute - If true, always recompute bounds even if they exist
|
|
93441
93532
|
*/
|
|
93442
|
-
ensureNodeBounds() {
|
|
93533
|
+
ensureNodeBounds(forceRecompute = false) {
|
|
93443
93534
|
if (!this.currentLayout?.nodes || !cola?.Rectangle) return;
|
|
93444
93535
|
for (const node of this.currentLayout.nodes) {
|
|
93445
|
-
if (node.bounds && typeof node.bounds.rayIntersection === "function") {
|
|
93446
|
-
|
|
93536
|
+
if (!forceRecompute && node.bounds && typeof node.bounds.rayIntersection === "function") {
|
|
93537
|
+
const currentCx = node.bounds.cx();
|
|
93538
|
+
const currentCy = node.bounds.cy();
|
|
93539
|
+
const tolerance = 1;
|
|
93540
|
+
if (Math.abs(currentCx - (node.x || 0)) < tolerance && Math.abs(currentCy - (node.y || 0)) < tolerance) {
|
|
93541
|
+
continue;
|
|
93542
|
+
}
|
|
93447
93543
|
}
|
|
93448
93544
|
const halfWidth = (node.width || 50) / 2;
|
|
93449
93545
|
const halfHeight = (node.height || 30) / 2;
|
|
@@ -93489,23 +93585,30 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93489
93585
|
getNodePairKey(sourceId, targetId) {
|
|
93490
93586
|
return sourceId < targetId ? `${sourceId}:${targetId}` : `${targetId}:${sourceId}`;
|
|
93491
93587
|
}
|
|
93492
|
-
route(nodes, groups, margin, groupMargin) {
|
|
93588
|
+
route(nodes = [], groups = [], margin, groupMargin) {
|
|
93493
93589
|
nodes.forEach((d) => {
|
|
93590
|
+
const bounds = d.bounds || d.innerBounds || this.createFallbackBounds(d);
|
|
93494
93591
|
d.routerNode = {
|
|
93495
93592
|
name: d.name,
|
|
93496
|
-
bounds
|
|
93593
|
+
bounds
|
|
93497
93594
|
};
|
|
93498
93595
|
});
|
|
93499
93596
|
groups.forEach((d) => {
|
|
93597
|
+
if (!d.bounds) {
|
|
93598
|
+
console.warn("Grid routing group missing bounds; routing may be degraded.", d);
|
|
93599
|
+
}
|
|
93500
93600
|
d.routerNode = {
|
|
93501
|
-
bounds: d.bounds
|
|
93601
|
+
bounds: d.bounds?.inflate(-groupMargin) ?? d.bounds,
|
|
93502
93602
|
children: (typeof d.groups !== "undefined" ? d.groups.map((c) => nodes.length + c.id) : []).concat(typeof d.leaves !== "undefined" ? d.leaves.map((c) => c.index) : [])
|
|
93503
93603
|
};
|
|
93504
93604
|
});
|
|
93505
93605
|
let gridRouterNodes = nodes.concat(groups).map((d, i) => {
|
|
93606
|
+
if (!d.routerNode) {
|
|
93607
|
+
return null;
|
|
93608
|
+
}
|
|
93506
93609
|
d.routerNode.id = i;
|
|
93507
93610
|
return d.routerNode;
|
|
93508
|
-
});
|
|
93611
|
+
}).filter(Boolean);
|
|
93509
93612
|
return new cola.GridRouter(gridRouterNodes, {
|
|
93510
93613
|
getChildren: (v) => v.children,
|
|
93511
93614
|
getBounds: (v) => v.bounds
|
|
@@ -93513,57 +93616,127 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93513
93616
|
}
|
|
93514
93617
|
gridify(nudgeGap, margin, groupMargin) {
|
|
93515
93618
|
try {
|
|
93516
|
-
const
|
|
93517
|
-
|
|
93518
|
-
const edges = this.currentLayout?.links
|
|
93519
|
-
if (
|
|
93619
|
+
const nodes = this.currentLayout?.nodes ?? [];
|
|
93620
|
+
const groups = this.currentLayout?.groups ?? [];
|
|
93621
|
+
const edges = this.currentLayout?.links ?? [];
|
|
93622
|
+
if (nodes.length === 0) {
|
|
93623
|
+
console.warn("No nodes available for GridRouter; skipping gridify.");
|
|
93624
|
+
return;
|
|
93625
|
+
}
|
|
93626
|
+
if (edges.length === 0) {
|
|
93520
93627
|
console.warn("No edges to route in GridRouter");
|
|
93521
93628
|
return;
|
|
93522
93629
|
}
|
|
93523
|
-
|
|
93524
|
-
|
|
93525
|
-
|
|
93526
|
-
|
|
93630
|
+
console.log("[gridify] Node positions BEFORE ensureNodeBounds:");
|
|
93631
|
+
nodes.slice(0, 3).forEach((n) => {
|
|
93632
|
+
console.log(` ${n.id}: x=${n.x?.toFixed(2)}, y=${n.y?.toFixed(2)}, bounds.cx=${n.bounds?.cx?.()?.toFixed(2)}, bounds.x=${n.bounds?.x?.toFixed(2)}`);
|
|
93633
|
+
});
|
|
93634
|
+
this.ensureNodeBounds(true);
|
|
93635
|
+
console.log("[gridify] Node positions AFTER ensureNodeBounds:");
|
|
93636
|
+
nodes.slice(0, 3).forEach((n) => {
|
|
93637
|
+
console.log(` ${n.id}: x=${n.x?.toFixed(2)}, y=${n.y?.toFixed(2)}, bounds.cx=${n.bounds?.cx?.()?.toFixed(2)}, bounds.x=${n.bounds?.x?.toFixed(2)}`);
|
|
93527
93638
|
});
|
|
93528
|
-
this.
|
|
93529
|
-
routes
|
|
93639
|
+
const gridrouter = this.route(nodes, groups, margin, groupMargin);
|
|
93640
|
+
let routes = [];
|
|
93641
|
+
const routableEdges = edges.filter((edge) => edge?.source?.routerNode && edge?.target?.routerNode);
|
|
93642
|
+
console.log("[gridify] Total edges:", edges.length, "Routable:", routableEdges.length);
|
|
93643
|
+
if (routableEdges.length !== edges.length) {
|
|
93644
|
+
const unroutableEdges = edges.filter((edge) => !edge?.source?.routerNode || !edge?.target?.routerNode);
|
|
93645
|
+
unroutableEdges.forEach((e) => {
|
|
93646
|
+
console.warn(
|
|
93647
|
+
"[gridify] Unroutable edge:",
|
|
93648
|
+
e.id,
|
|
93649
|
+
"source routerNode:",
|
|
93650
|
+
!!e?.source?.routerNode,
|
|
93651
|
+
"target routerNode:",
|
|
93652
|
+
!!e?.target?.routerNode,
|
|
93653
|
+
"source:",
|
|
93654
|
+
e?.source?.id,
|
|
93655
|
+
"x:",
|
|
93656
|
+
e?.source?.x,
|
|
93657
|
+
"y:",
|
|
93658
|
+
e?.source?.y,
|
|
93659
|
+
"target:",
|
|
93660
|
+
e?.target?.id,
|
|
93661
|
+
"x:",
|
|
93662
|
+
e?.target?.x,
|
|
93663
|
+
"y:",
|
|
93664
|
+
e?.target?.y
|
|
93665
|
+
);
|
|
93666
|
+
});
|
|
93667
|
+
}
|
|
93668
|
+
routes = gridrouter.routeEdges(
|
|
93669
|
+
routableEdges,
|
|
93670
|
+
nudgeGap,
|
|
93671
|
+
function(e) {
|
|
93672
|
+
return e.source.routerNode.id;
|
|
93673
|
+
},
|
|
93674
|
+
function(e) {
|
|
93675
|
+
return e.target.routerNode.id;
|
|
93676
|
+
}
|
|
93677
|
+
);
|
|
93678
|
+
const routesByEdgeId = /* @__PURE__ */ new Map();
|
|
93679
|
+
routableEdges.forEach((edge, index) => {
|
|
93680
|
+
const route = routes[index];
|
|
93681
|
+
if (edge?.id && route) {
|
|
93682
|
+
routesByEdgeId.set(edge.id, this.adjustGridRouteForEdge(edge, route));
|
|
93683
|
+
}
|
|
93684
|
+
});
|
|
93685
|
+
console.log("[gridify] Routes generated:", routesByEdgeId.size, "out of", routableEdges.length);
|
|
93686
|
+
const linkGroups = this.container.selectAll(".link-group").data(edges, (d) => d.id ?? d);
|
|
93687
|
+
linkGroups.select("path").attr("d", (edgeData) => {
|
|
93688
|
+
const route = routesByEdgeId.get(edgeData.id);
|
|
93689
|
+
if (!route) {
|
|
93690
|
+
const sourceX = edgeData.source?.x ?? edgeData.source?.bounds?.cx() ?? 0;
|
|
93691
|
+
const sourceY = edgeData.source?.y ?? edgeData.source?.bounds?.cy() ?? 0;
|
|
93692
|
+
const targetX = edgeData.target?.x ?? edgeData.target?.bounds?.cx() ?? 0;
|
|
93693
|
+
const targetY = edgeData.target?.y ?? edgeData.target?.bounds?.cy() ?? 0;
|
|
93694
|
+
console.log(
|
|
93695
|
+
"[gridify] Fallback path for edge:",
|
|
93696
|
+
edgeData.id,
|
|
93697
|
+
"from",
|
|
93698
|
+
edgeData.source?.id,
|
|
93699
|
+
"(",
|
|
93700
|
+
sourceX,
|
|
93701
|
+
",",
|
|
93702
|
+
sourceY,
|
|
93703
|
+
")",
|
|
93704
|
+
"to",
|
|
93705
|
+
edgeData.target?.id,
|
|
93706
|
+
"(",
|
|
93707
|
+
targetX,
|
|
93708
|
+
",",
|
|
93709
|
+
targetY,
|
|
93710
|
+
")"
|
|
93711
|
+
);
|
|
93712
|
+
const dx = targetX - sourceX;
|
|
93713
|
+
const dy = targetY - sourceY;
|
|
93714
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
93715
|
+
const midX = sourceX + dx / 2;
|
|
93716
|
+
return this.gridLineFunction([
|
|
93717
|
+
{ x: sourceX, y: sourceY },
|
|
93718
|
+
{ x: midX, y: sourceY },
|
|
93719
|
+
{ x: midX, y: targetY },
|
|
93720
|
+
{ x: targetX, y: targetY }
|
|
93721
|
+
]);
|
|
93722
|
+
} else {
|
|
93723
|
+
const midY = sourceY + dy / 2;
|
|
93724
|
+
return this.gridLineFunction([
|
|
93725
|
+
{ x: sourceX, y: sourceY },
|
|
93726
|
+
{ x: sourceX, y: midY },
|
|
93727
|
+
{ x: targetX, y: midY },
|
|
93728
|
+
{ x: targetX, y: targetY }
|
|
93729
|
+
]);
|
|
93730
|
+
}
|
|
93731
|
+
}
|
|
93530
93732
|
const cornerradius = 5;
|
|
93531
93733
|
const arrowwidth = 3;
|
|
93532
93734
|
const arrowheight = 7;
|
|
93533
|
-
const edgeData = edges[index];
|
|
93534
93735
|
const p = cola.GridRouter.getRoutePath(route, cornerradius, arrowwidth, arrowheight);
|
|
93535
|
-
const
|
|
93536
|
-
|
|
93537
|
-
if (this.isAlignmentEdge(edgeData)) return "alignmentLink";
|
|
93538
|
-
if (this.isInferredEdge(edgeData)) return "inferredLink";
|
|
93539
|
-
return "link";
|
|
93540
|
-
}).attr("data-link-id", edgeData.id).attr("stroke", (d) => d.color).attr("d", p.routepath).lower();
|
|
93541
|
-
linkGroup.filter((d) => !this.isAlignmentEdge(d)).append("text").attr("class", "linklabel").text((d) => d.label);
|
|
93736
|
+
const adjustedPath = this.adjustGridRouteForArrowPositioning(edgeData, p.routepath, route);
|
|
93737
|
+
return adjustedPath || p.routepath;
|
|
93542
93738
|
});
|
|
93543
|
-
this.
|
|
93544
|
-
return d.bounds.x;
|
|
93545
|
-
}).attr("y", function(d) {
|
|
93546
|
-
return d.bounds.y;
|
|
93547
|
-
}).attr("width", function(d) {
|
|
93548
|
-
return d.bounds.width();
|
|
93549
|
-
}).attr("height", function(d) {
|
|
93550
|
-
return d.bounds.height();
|
|
93551
|
-
});
|
|
93552
|
-
this.container.selectAll(".group").transition().attr("x", function(d) {
|
|
93553
|
-
return d.bounds.x;
|
|
93554
|
-
}).attr("y", function(d) {
|
|
93555
|
-
return d.bounds.y;
|
|
93556
|
-
}).attr("width", function(d) {
|
|
93557
|
-
return d.bounds.width();
|
|
93558
|
-
}).attr("height", function(d) {
|
|
93559
|
-
return d.bounds.height();
|
|
93560
|
-
});
|
|
93561
|
-
this.container.selectAll(".label").transition().attr("x", function(d) {
|
|
93562
|
-
return d.bounds.cx();
|
|
93563
|
-
}).attr("y", function(d) {
|
|
93564
|
-
return d.bounds.cy();
|
|
93565
|
-
});
|
|
93566
|
-
this.gridUpdateLinkLabels(routes, edges);
|
|
93739
|
+
this.gridUpdateLinkLabels(edges, routesByEdgeId);
|
|
93567
93740
|
this.fitViewportToContent();
|
|
93568
93741
|
this.dispatchEvent(new Event("relationsAvailable"));
|
|
93569
93742
|
} catch (e) {
|
|
@@ -93584,23 +93757,224 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93584
93757
|
return;
|
|
93585
93758
|
}
|
|
93586
93759
|
}
|
|
93587
|
-
gridUpdateLinkLabels(
|
|
93588
|
-
|
|
93589
|
-
|
|
93590
|
-
|
|
93591
|
-
|
|
93592
|
-
|
|
93593
|
-
|
|
93594
|
-
|
|
93595
|
-
|
|
93596
|
-
|
|
93597
|
-
|
|
93760
|
+
gridUpdateLinkLabels(edges, routesByEdgeId) {
|
|
93761
|
+
const linkGroups = this.container.selectAll(".link-group");
|
|
93762
|
+
linkGroups.filter((d) => !this.isAlignmentEdge(d)).select("text.linklabel").attr("x", (edgeData) => {
|
|
93763
|
+
const pathElement = this.shadowRoot?.querySelector(`path[data-link-id="${edgeData.id}"]`);
|
|
93764
|
+
if (pathElement) {
|
|
93765
|
+
try {
|
|
93766
|
+
const pathLength = pathElement.getTotalLength();
|
|
93767
|
+
const midpoint2 = pathElement.getPointAtLength(pathLength / 2);
|
|
93768
|
+
return midpoint2.x;
|
|
93769
|
+
} catch (e) {
|
|
93770
|
+
}
|
|
93771
|
+
}
|
|
93772
|
+
const midpoint = this.getGridRouteMidpoint(edgeData, routesByEdgeId);
|
|
93773
|
+
return midpoint?.x ?? (edgeData.source?.x ?? edgeData.source?.bounds?.cx() ?? 0);
|
|
93774
|
+
}).attr("y", (edgeData) => {
|
|
93775
|
+
const pathElement = this.shadowRoot?.querySelector(`path[data-link-id="${edgeData.id}"]`);
|
|
93776
|
+
if (pathElement) {
|
|
93777
|
+
try {
|
|
93778
|
+
const pathLength = pathElement.getTotalLength();
|
|
93779
|
+
const midpoint2 = pathElement.getPointAtLength(pathLength / 2);
|
|
93780
|
+
return midpoint2.y;
|
|
93781
|
+
} catch (e) {
|
|
93782
|
+
}
|
|
93783
|
+
}
|
|
93784
|
+
const midpoint = this.getGridRouteMidpoint(edgeData, routesByEdgeId);
|
|
93785
|
+
return midpoint?.y ?? (edgeData.source?.y ?? edgeData.source?.bounds?.cy() ?? 0);
|
|
93786
|
+
}).attr("text-anchor", "middle").attr("dominant-baseline", "middle");
|
|
93787
|
+
}
|
|
93788
|
+
getGridRouteMidpoint(edgeData, routesByEdgeId) {
|
|
93789
|
+
const route = routesByEdgeId.get(edgeData.id);
|
|
93790
|
+
if (!route) {
|
|
93791
|
+
const sourceX = edgeData.source?.x ?? edgeData.source?.bounds?.cx() ?? 0;
|
|
93792
|
+
const sourceY = edgeData.source?.y ?? edgeData.source?.bounds?.cy() ?? 0;
|
|
93793
|
+
const targetX = edgeData.target?.x ?? edgeData.target?.bounds?.cx() ?? 0;
|
|
93794
|
+
const targetY = edgeData.target?.y ?? edgeData.target?.bounds?.cy() ?? 0;
|
|
93795
|
+
return {
|
|
93796
|
+
x: (sourceX + targetX) / 2,
|
|
93797
|
+
y: (sourceY + targetY) / 2
|
|
93598
93798
|
};
|
|
93599
|
-
|
|
93600
|
-
|
|
93601
|
-
|
|
93602
|
-
|
|
93799
|
+
}
|
|
93800
|
+
const points = [];
|
|
93801
|
+
route.forEach((segment) => {
|
|
93802
|
+
if (points.length === 0 && segment.length > 0) {
|
|
93803
|
+
points.push(segment[0]);
|
|
93804
|
+
}
|
|
93805
|
+
if (segment.length > 1) {
|
|
93806
|
+
points.push(segment[1]);
|
|
93807
|
+
}
|
|
93808
|
+
});
|
|
93809
|
+
if (points.length < 2) {
|
|
93810
|
+
return null;
|
|
93811
|
+
}
|
|
93812
|
+
let totalLength = 0;
|
|
93813
|
+
const segmentLengths = [];
|
|
93814
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
93815
|
+
const dx = points[i + 1].x - points[i].x;
|
|
93816
|
+
const dy = points[i + 1].y - points[i].y;
|
|
93817
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
93818
|
+
segmentLengths.push(length);
|
|
93819
|
+
totalLength += length;
|
|
93820
|
+
}
|
|
93821
|
+
const targetLength = totalLength / 2;
|
|
93822
|
+
let accumulatedLength = 0;
|
|
93823
|
+
for (let i = 0; i < segmentLengths.length; i++) {
|
|
93824
|
+
const segmentLength = segmentLengths[i];
|
|
93825
|
+
if (accumulatedLength + segmentLength >= targetLength) {
|
|
93826
|
+
const remainingLength = targetLength - accumulatedLength;
|
|
93827
|
+
const t = segmentLength > 0 ? remainingLength / segmentLength : 0;
|
|
93828
|
+
return {
|
|
93829
|
+
x: points[i].x + t * (points[i + 1].x - points[i].x),
|
|
93830
|
+
y: points[i].y + t * (points[i + 1].y - points[i].y)
|
|
93831
|
+
};
|
|
93832
|
+
}
|
|
93833
|
+
accumulatedLength += segmentLength;
|
|
93834
|
+
}
|
|
93835
|
+
const midIndex = Math.floor(points.length / 2);
|
|
93836
|
+
return points[midIndex];
|
|
93837
|
+
}
|
|
93838
|
+
adjustGridRouteForEdge(edgeData, route) {
|
|
93839
|
+
if (!edgeData?.id?.startsWith("_g_")) {
|
|
93840
|
+
return route;
|
|
93841
|
+
}
|
|
93842
|
+
const points = this.gridRouteToPoints(route);
|
|
93843
|
+
if (points.length < 2) {
|
|
93844
|
+
return route;
|
|
93845
|
+
}
|
|
93846
|
+
const adjustedPoints = this.routeGroupEdge(edgeData, points);
|
|
93847
|
+
return this.pointsToGridRoute(adjustedPoints);
|
|
93848
|
+
}
|
|
93849
|
+
/**
|
|
93850
|
+
* Adjust grid route path for proper arrow positioning at node boundaries.
|
|
93851
|
+
* Ensures the path terminates at the node boundary rather than center.
|
|
93852
|
+
*/
|
|
93853
|
+
adjustGridRouteForArrowPositioning(edgeData, routePath, route) {
|
|
93854
|
+
if (!routePath || !edgeData.source || !edgeData.target) {
|
|
93855
|
+
return null;
|
|
93856
|
+
}
|
|
93857
|
+
try {
|
|
93858
|
+
const points = this.gridRouteToPoints(route);
|
|
93859
|
+
if (points.length < 2) {
|
|
93860
|
+
return null;
|
|
93861
|
+
}
|
|
93862
|
+
const source = edgeData.source;
|
|
93863
|
+
const target = edgeData.target;
|
|
93864
|
+
const sourceBounds = source.bounds || {
|
|
93865
|
+
x: source.x - (source.width || 0) / 2,
|
|
93866
|
+
y: source.y - (source.height || 0) / 2,
|
|
93867
|
+
width: () => source.width || 0,
|
|
93868
|
+
height: () => source.height || 0
|
|
93869
|
+
};
|
|
93870
|
+
const targetBounds = target.bounds || {
|
|
93871
|
+
x: target.x - (target.width || 0) / 2,
|
|
93872
|
+
y: target.y - (target.height || 0) / 2,
|
|
93873
|
+
width: () => target.width || 0,
|
|
93874
|
+
height: () => target.height || 0
|
|
93875
|
+
};
|
|
93876
|
+
const firstPoint = points[0];
|
|
93877
|
+
const secondPoint = points.length > 1 ? points[1] : points[0];
|
|
93878
|
+
const sourceIntersection = this.getRectangleIntersection(
|
|
93879
|
+
sourceBounds.x + sourceBounds.width() / 2,
|
|
93880
|
+
sourceBounds.y + sourceBounds.height() / 2,
|
|
93881
|
+
secondPoint.x,
|
|
93882
|
+
secondPoint.y,
|
|
93883
|
+
sourceBounds
|
|
93884
|
+
);
|
|
93885
|
+
if (sourceIntersection) {
|
|
93886
|
+
points[0] = sourceIntersection;
|
|
93887
|
+
}
|
|
93888
|
+
const lastPoint = points[points.length - 1];
|
|
93889
|
+
const secondLastPoint = points.length > 1 ? points[points.length - 2] : lastPoint;
|
|
93890
|
+
const targetIntersection = this.getRectangleIntersection(
|
|
93891
|
+
targetBounds.x + targetBounds.width() / 2,
|
|
93892
|
+
targetBounds.y + targetBounds.height() / 2,
|
|
93893
|
+
secondLastPoint.x,
|
|
93894
|
+
secondLastPoint.y,
|
|
93895
|
+
targetBounds
|
|
93896
|
+
);
|
|
93897
|
+
if (targetIntersection) {
|
|
93898
|
+
points[points.length - 1] = targetIntersection;
|
|
93899
|
+
}
|
|
93900
|
+
return this.gridLineFunction(points);
|
|
93901
|
+
} catch (error) {
|
|
93902
|
+
console.warn("Error adjusting grid route for arrow positioning:", error);
|
|
93903
|
+
return null;
|
|
93904
|
+
}
|
|
93905
|
+
}
|
|
93906
|
+
gridRouteToPoints(route) {
|
|
93907
|
+
const points = [];
|
|
93908
|
+
route.forEach((segment, index) => {
|
|
93909
|
+
if (index === 0) {
|
|
93910
|
+
points.push({ x: segment[0].x, y: segment[0].y });
|
|
93911
|
+
}
|
|
93912
|
+
points.push({ x: segment[1].x, y: segment[1].y });
|
|
93603
93913
|
});
|
|
93914
|
+
return points;
|
|
93915
|
+
}
|
|
93916
|
+
pointsToGridRoute(points) {
|
|
93917
|
+
const segments = [];
|
|
93918
|
+
for (let i = 0; i < points.length - 1; i += 1) {
|
|
93919
|
+
segments.push([points[i], points[i + 1]]);
|
|
93920
|
+
}
|
|
93921
|
+
return segments;
|
|
93922
|
+
}
|
|
93923
|
+
createFallbackBounds(node) {
|
|
93924
|
+
if (!cola?.Rectangle) {
|
|
93925
|
+
return null;
|
|
93926
|
+
}
|
|
93927
|
+
const halfWidth = (node.width || 50) / 2;
|
|
93928
|
+
const halfHeight = (node.height || 30) / 2;
|
|
93929
|
+
const x = (node.x || 0) - halfWidth;
|
|
93930
|
+
const X = (node.x || 0) + halfWidth;
|
|
93931
|
+
const y = (node.y || 0) - halfHeight;
|
|
93932
|
+
const Y = (node.y || 0) + halfHeight;
|
|
93933
|
+
return new cola.Rectangle(x, X, y, Y);
|
|
93934
|
+
}
|
|
93935
|
+
/**
|
|
93936
|
+
* Calculate the intersection point between a line and a rectangle.
|
|
93937
|
+
* Used for positioning arrow heads at node boundaries in grid mode.
|
|
93938
|
+
*
|
|
93939
|
+
* @param x1 - Start x coordinate (usually center of node)
|
|
93940
|
+
* @param y1 - Start y coordinate (usually center of node)
|
|
93941
|
+
* @param x2 - End x coordinate (next point in path)
|
|
93942
|
+
* @param y2 - End y coordinate (next point in path)
|
|
93943
|
+
* @param bounds - Rectangle bounds with x, y, width(), height()
|
|
93944
|
+
* @returns Intersection point or null if no intersection
|
|
93945
|
+
*/
|
|
93946
|
+
getRectangleIntersection(x1, y1, x2, y2, bounds) {
|
|
93947
|
+
const rectLeft = bounds.x;
|
|
93948
|
+
const rectRight = bounds.x + bounds.width();
|
|
93949
|
+
const rectTop = bounds.y;
|
|
93950
|
+
const rectBottom = bounds.y + bounds.height();
|
|
93951
|
+
const dx = x2 - x1;
|
|
93952
|
+
const dy = y2 - y1;
|
|
93953
|
+
if (dx === 0 && dy === 0) {
|
|
93954
|
+
return { x: x1, y: y1 };
|
|
93955
|
+
}
|
|
93956
|
+
let tMin = 0;
|
|
93957
|
+
let tMax = 1;
|
|
93958
|
+
if (dx !== 0) {
|
|
93959
|
+
const t1 = (rectLeft - x1) / dx;
|
|
93960
|
+
const t2 = (rectRight - x1) / dx;
|
|
93961
|
+
tMin = Math.max(tMin, Math.min(t1, t2));
|
|
93962
|
+
tMax = Math.min(tMax, Math.max(t1, t2));
|
|
93963
|
+
}
|
|
93964
|
+
if (dy !== 0) {
|
|
93965
|
+
const t1 = (rectTop - y1) / dy;
|
|
93966
|
+
const t2 = (rectBottom - y1) / dy;
|
|
93967
|
+
tMin = Math.max(tMin, Math.min(t1, t2));
|
|
93968
|
+
tMax = Math.min(tMax, Math.max(t1, t2));
|
|
93969
|
+
}
|
|
93970
|
+
if (tMin > tMax) {
|
|
93971
|
+
return null;
|
|
93972
|
+
}
|
|
93973
|
+
const t = tMin > 0 ? tMin : tMax;
|
|
93974
|
+
return {
|
|
93975
|
+
x: x1 + t * dx,
|
|
93976
|
+
y: y1 + t * dy
|
|
93977
|
+
};
|
|
93604
93978
|
}
|
|
93605
93979
|
/**
|
|
93606
93980
|
* Routes all link paths with advanced curvature and collision handling.
|
|
@@ -94702,6 +95076,45 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
94702
95076
|
transform: none;
|
|
94703
95077
|
}
|
|
94704
95078
|
|
|
95079
|
+
/* Routing control styling */
|
|
95080
|
+
#routing-control {
|
|
95081
|
+
display: flex;
|
|
95082
|
+
align-items: center;
|
|
95083
|
+
gap: 6px;
|
|
95084
|
+
margin-left: 16px;
|
|
95085
|
+
padding-left: 16px;
|
|
95086
|
+
border-left: 1px solid #e5e7eb;
|
|
95087
|
+
}
|
|
95088
|
+
|
|
95089
|
+
#routing-control label {
|
|
95090
|
+
font-size: 12px;
|
|
95091
|
+
font-weight: 500;
|
|
95092
|
+
color: #6b7280;
|
|
95093
|
+
user-select: none;
|
|
95094
|
+
}
|
|
95095
|
+
|
|
95096
|
+
#routing-mode {
|
|
95097
|
+
padding: 4px 8px;
|
|
95098
|
+
border: 1px solid #d1d5db;
|
|
95099
|
+
background: #f9fafb;
|
|
95100
|
+
color: #374151;
|
|
95101
|
+
border-radius: 4px;
|
|
95102
|
+
font-size: 12px;
|
|
95103
|
+
cursor: pointer;
|
|
95104
|
+
transition: all 0.15s ease;
|
|
95105
|
+
outline: none;
|
|
95106
|
+
}
|
|
95107
|
+
|
|
95108
|
+
#routing-mode:hover {
|
|
95109
|
+
background: #f3f4f6;
|
|
95110
|
+
border-color: #9ca3af;
|
|
95111
|
+
}
|
|
95112
|
+
|
|
95113
|
+
#routing-mode:focus {
|
|
95114
|
+
border-color: #3b82f6;
|
|
95115
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
|
95116
|
+
}
|
|
95117
|
+
|
|
94705
95118
|
/* Modal Overlay and Dialog */
|
|
94706
95119
|
.modal-overlay {
|
|
94707
95120
|
position: fixed;
|