diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index f3356f531..59601533e 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -105,6 +105,97 @@
++ --- + 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 + ++
+ --- + config: + layout: elk + --- + flowchart TB + subgraph container_Beta + process_C + end + + process_B-->|via_AWSBatch|container_Beta + + ++
+ --- + 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!" ++
+ --- + 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 +
--- config: diff --git a/packages/mermaid-layout-elk/src/geometry.ts b/packages/mermaid-layout-elk/src/geometry.ts index 3255b2e8b..5cda865de 100644 --- a/packages/mermaid-layout-elk/src/geometry.ts +++ b/packages/mermaid-layout-elk/src/geometry.ts @@ -20,6 +20,21 @@ export interface NodeLike { export const EPS = 1; 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 * segment from insidePoint -\> outsidePoint. Returns the point on the rectangle border. diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index aeaa865e2..24a740433 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -10,6 +10,7 @@ import { outsideNode, computeNodeIntersection, replaceEndpoint, + onBorder, } from './geometry.js'; type Node = LayoutData['nodes'][number]; @@ -527,8 +528,8 @@ export const render = async ( const endCenter = points[points.length - 1]; // Minimal, structured logging for diagnostics - log.debug('UIO cutter2: bounds', { startBounds, endBounds }); - log.debug('UIO cutter2: original points', _points); + log.debug('PPP cutter2: bounds', { startBounds, endBounds }); + log.debug('PPP cutter2: original points', _points); let firstOutsideStartIndex = -1; @@ -867,13 +868,19 @@ export const render = async ( startNode.y = startNode.offset.posY + startNode.height / 2; endNode.x = endNode.offset.posX + endNode.width / 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({ x: startNode.x, y: startNode.y, }); } - if (endNode.shape !== 'rect33') { + + if (shouldAddEndCenter) { edge.points.push({ x: endNode.x, y: endNode.y, @@ -882,13 +889,64 @@ export const render = async ( // Debug and sanitize points around cutter2 const prevPoints = Array.isArray(edge.points) ? [...edge.points] : []; - log.debug('UIO cutter2: Points before cutter2:', prevPoints); - edge.points = cutter2(startNode, endNode, prevPoints); + const endBounds = boundsFor(endNode); + 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 }[]) => pts?.some((p) => !Number.isFinite(p?.x) || !Number.isFinite(p?.y)); if (!Array.isArray(edge.points) || edge.points.length < 2 || hasNaN(edge.points)) { log.warn( - 'UIO cutter2: Invalid points from cutter2, falling back to prevPoints', + 'POI cutter2: Invalid points from cutter2, falling back to prevPoints', edge.points ); // Fallback to previous points and strip any invalid ones just in case