mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-04 04:44:08 +01:00 
			
		
		
		
	Fix for edge calculation to subgraphs
This commit is contained in:
		@@ -105,6 +105,97 @@
 | 
			
		||||
  </head>
 | 
			
		||||
 | 
			
		||||
  <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">
 | 
			
		||||
      ---
 | 
			
		||||
      config:
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user