mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 23:09:49 +02:00
Fix for edge calculation to subgraphs
This commit is contained in:
@@ -105,6 +105,97 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
flowchart TB
|
||||||
|
subgraph container_Beta
|
||||||
|
process_C
|
||||||
|
end
|
||||||
|
subgraph container_Alpha
|
||||||
|
subgraph process_B
|
||||||
|
pppB
|
||||||
|
end
|
||||||
|
subgraph process_A
|
||||||
|
pppA
|
||||||
|
end
|
||||||
|
process_B-->|via_AWSBatch|container_Beta
|
||||||
|
process_A-->|messages|container_Beta
|
||||||
|
end
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
flowchart TB
|
||||||
|
subgraph container_Beta
|
||||||
|
process_C
|
||||||
|
end
|
||||||
|
|
||||||
|
process_B-->|via_AWSBatch|container_Beta
|
||||||
|
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
note "I love this diagram!\nDo you love it?"
|
||||||
|
Class01 <|-- AveryLongClass : Cool
|
||||||
|
<<interface>> Class01
|
||||||
|
Class03 "1" *-- "*" Class04
|
||||||
|
Class05 "1" o-- "many" Class06
|
||||||
|
Class07 "1" .. "*" Class08
|
||||||
|
Class09 "1" --> "*" C2 : Where am i?
|
||||||
|
Class09 "*" --* "*" C3
|
||||||
|
Class09 "1" --|> "1" Class07
|
||||||
|
Class12 <|.. Class08
|
||||||
|
Class11 ..>Class12
|
||||||
|
Class07 : equals()
|
||||||
|
Class07 : Object[] elementData
|
||||||
|
Class01 : size()
|
||||||
|
Class01 : int chimp
|
||||||
|
Class01 : int gorilla
|
||||||
|
Class01 : -int privateChimp
|
||||||
|
Class01 : +int publicGorilla
|
||||||
|
Class01 : #int protectedMarmoset
|
||||||
|
Class08 <--> C2: Cool label
|
||||||
|
class Class10 {
|
||||||
|
<<service>>
|
||||||
|
int id
|
||||||
|
test()
|
||||||
|
}
|
||||||
|
note for Class10 "Cool class\nI said it's very cool class!"
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
requirementDiagram
|
||||||
|
requirement test_req {
|
||||||
|
id: 1
|
||||||
|
text: the test text.
|
||||||
|
risk: high
|
||||||
|
verifymethod: test
|
||||||
|
}
|
||||||
|
|
||||||
|
element test_entity {
|
||||||
|
type: simulation
|
||||||
|
}
|
||||||
|
|
||||||
|
test_entity - satisfies -> test_req
|
||||||
|
</pre
|
||||||
|
>
|
||||||
<pre id="diagram4" class="mermaid">
|
<pre id="diagram4" class="mermaid">
|
||||||
---
|
---
|
||||||
config:
|
config:
|
||||||
|
@@ -20,6 +20,21 @@ export interface NodeLike {
|
|||||||
export const EPS = 1;
|
export const EPS = 1;
|
||||||
export const PUSH_OUT = 10;
|
export const PUSH_OUT = 10;
|
||||||
|
|
||||||
|
export const onBorder = (bounds: RectLike, p: P, tol = 0.5): boolean => {
|
||||||
|
const halfW = bounds.width / 2;
|
||||||
|
const halfH = bounds.height / 2;
|
||||||
|
const left = bounds.x - halfW;
|
||||||
|
const right = bounds.x + halfW;
|
||||||
|
const top = bounds.y - halfH;
|
||||||
|
const bottom = bounds.y + halfH;
|
||||||
|
|
||||||
|
const onLeft = Math.abs(p.x - left) <= tol && p.y >= top - tol && p.y <= bottom + tol;
|
||||||
|
const onRight = Math.abs(p.x - right) <= tol && p.y >= top - tol && p.y <= bottom + tol;
|
||||||
|
const onTop = Math.abs(p.y - top) <= tol && p.x >= left - tol && p.x <= right + tol;
|
||||||
|
const onBottom = Math.abs(p.y - bottom) <= tol && p.x >= left - tol && p.x <= right + tol;
|
||||||
|
return onLeft || onRight || onTop || onBottom;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute intersection between a rectangle (center x/y, width/height) and the line
|
* Compute intersection between a rectangle (center x/y, width/height) and the line
|
||||||
* segment from insidePoint -\> outsidePoint. Returns the point on the rectangle border.
|
* segment from insidePoint -\> outsidePoint. Returns the point on the rectangle border.
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
outsideNode,
|
outsideNode,
|
||||||
computeNodeIntersection,
|
computeNodeIntersection,
|
||||||
replaceEndpoint,
|
replaceEndpoint,
|
||||||
|
onBorder,
|
||||||
} from './geometry.js';
|
} from './geometry.js';
|
||||||
|
|
||||||
type Node = LayoutData['nodes'][number];
|
type Node = LayoutData['nodes'][number];
|
||||||
@@ -527,8 +528,8 @@ export const render = async (
|
|||||||
const endCenter = points[points.length - 1];
|
const endCenter = points[points.length - 1];
|
||||||
|
|
||||||
// Minimal, structured logging for diagnostics
|
// Minimal, structured logging for diagnostics
|
||||||
log.debug('UIO cutter2: bounds', { startBounds, endBounds });
|
log.debug('PPP cutter2: bounds', { startBounds, endBounds });
|
||||||
log.debug('UIO cutter2: original points', _points);
|
log.debug('PPP cutter2: original points', _points);
|
||||||
|
|
||||||
let firstOutsideStartIndex = -1;
|
let firstOutsideStartIndex = -1;
|
||||||
|
|
||||||
@@ -867,13 +868,19 @@ export const render = async (
|
|||||||
startNode.y = startNode.offset.posY + startNode.height / 2;
|
startNode.y = startNode.offset.posY + startNode.height / 2;
|
||||||
endNode.x = endNode.offset.posX + endNode.width / 2;
|
endNode.x = endNode.offset.posX + endNode.width / 2;
|
||||||
endNode.y = endNode.offset.posY + endNode.height / 2;
|
endNode.y = endNode.offset.posY + endNode.height / 2;
|
||||||
if (startNode.shape !== 'rect33') {
|
|
||||||
|
// Only add center points for non-subgraph nodes or when the edge path doesn't already end near the target
|
||||||
|
const shouldAddStartCenter = startNode.shape !== 'rect33';
|
||||||
|
const shouldAddEndCenter = endNode.shape !== 'rect33';
|
||||||
|
|
||||||
|
if (shouldAddStartCenter) {
|
||||||
edge.points.unshift({
|
edge.points.unshift({
|
||||||
x: startNode.x,
|
x: startNode.x,
|
||||||
y: startNode.y,
|
y: startNode.y,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (endNode.shape !== 'rect33') {
|
|
||||||
|
if (shouldAddEndCenter) {
|
||||||
edge.points.push({
|
edge.points.push({
|
||||||
x: endNode.x,
|
x: endNode.x,
|
||||||
y: endNode.y,
|
y: endNode.y,
|
||||||
@@ -882,13 +889,64 @@ export const render = async (
|
|||||||
|
|
||||||
// Debug and sanitize points around cutter2
|
// Debug and sanitize points around cutter2
|
||||||
const prevPoints = Array.isArray(edge.points) ? [...edge.points] : [];
|
const prevPoints = Array.isArray(edge.points) ? [...edge.points] : [];
|
||||||
log.debug('UIO cutter2: Points before cutter2:', prevPoints);
|
const endBounds = boundsFor(endNode);
|
||||||
edge.points = cutter2(startNode, endNode, prevPoints);
|
log.debug(
|
||||||
|
'PPP cutter2: Points before cutter2:',
|
||||||
|
JSON.stringify(edge.points),
|
||||||
|
'endBounds:',
|
||||||
|
endBounds,
|
||||||
|
onBorder(endBounds, edge.points[edge.points.length - 1])
|
||||||
|
);
|
||||||
|
{
|
||||||
|
const endIsGroup = !!endNode?.isGroup;
|
||||||
|
const lastIdx = prevPoints.length - 1;
|
||||||
|
const lastPt = prevPoints[lastIdx];
|
||||||
|
const endCenterApprox =
|
||||||
|
Math.abs(lastPt.x - endNode.x) < 1e-6 && Math.abs(lastPt.y - endNode.y) < 1e-6;
|
||||||
|
const candidatePt =
|
||||||
|
endCenterApprox && prevPoints.length > 1 ? prevPoints[lastIdx - 1] : lastPt;
|
||||||
|
const lastOnEndBorder = onBorder(endBounds, candidatePt);
|
||||||
|
if (endIsGroup && lastOnEndBorder) {
|
||||||
|
if (endCenterApprox) {
|
||||||
|
// drop the appended end-center so the path truly ends at the border
|
||||||
|
prevPoints.pop();
|
||||||
|
}
|
||||||
|
// still compute start intersection to avoid starting at center
|
||||||
|
const startBounds = boundsFor(startNode);
|
||||||
|
let firstOutsideStartIndex = -1;
|
||||||
|
for (const [i, prevPoint] of prevPoints.entries()) {
|
||||||
|
if (outsideNode(startBounds, prevPoint)) {
|
||||||
|
firstOutsideStartIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstOutsideStartIndex !== -1) {
|
||||||
|
const outsidePointForStart = prevPoints[firstOutsideStartIndex];
|
||||||
|
const startCenter = prevPoints[0];
|
||||||
|
const startIntersection = computeNodeIntersection(
|
||||||
|
startNode,
|
||||||
|
startBounds,
|
||||||
|
outsidePointForStart,
|
||||||
|
startCenter
|
||||||
|
);
|
||||||
|
replaceEndpoint(prevPoints, 'start', startIntersection);
|
||||||
|
log.debug('UIO cutter2: start-only intersection applied', { startIntersection });
|
||||||
|
}
|
||||||
|
log.debug(
|
||||||
|
'PPP cutter2: skipping cutter2 because last point on end border and end is group',
|
||||||
|
{ endCenterApprox, candidatePt }
|
||||||
|
);
|
||||||
|
edge.points = prevPoints;
|
||||||
|
} else {
|
||||||
|
edge.points = cutter2(startNode, endNode, prevPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug('PPP cutter2: Points after cutter2:', JSON.stringify(edge.points));
|
||||||
const hasNaN = (pts: { x: number; y: number }[]) =>
|
const hasNaN = (pts: { x: number; y: number }[]) =>
|
||||||
pts?.some((p) => !Number.isFinite(p?.x) || !Number.isFinite(p?.y));
|
pts?.some((p) => !Number.isFinite(p?.x) || !Number.isFinite(p?.y));
|
||||||
if (!Array.isArray(edge.points) || edge.points.length < 2 || hasNaN(edge.points)) {
|
if (!Array.isArray(edge.points) || edge.points.length < 2 || hasNaN(edge.points)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
'UIO cutter2: Invalid points from cutter2, falling back to prevPoints',
|
'POI cutter2: Invalid points from cutter2, falling back to prevPoints',
|
||||||
edge.points
|
edge.points
|
||||||
);
|
);
|
||||||
// Fallback to previous points and strip any invalid ones just in case
|
// Fallback to previous points and strip any invalid ones just in case
|
||||||
|
Reference in New Issue
Block a user