diff --git a/cypress/platform/shape-tester.html b/cypress/platform/shape-tester.html new file mode 100644 index 000000000..21ab9ad85 --- /dev/null +++ b/cypress/platform/shape-tester.html @@ -0,0 +1,102 @@ + +
+ + + + + + + + + + + + + + + + + + + diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index 8f49a2476..b63bf9528 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -592,9 +592,10 @@ export const render = async ( setIncludeChildrenPolicy(target, ancestorId); } }); - + // const copy = JSON.parse(JSON.stringify({ ...elkGraph })); + // console.log('APA13 layout before', copy); const g = await elk.layout(elkGraph); - + // console.log('APA13 layout', JSON.parse(JSON.stringify(g))); // debugger; await drawNodes(0, 0, g.children, svg, subGraphsEl, 0); g.edges?.map( @@ -683,6 +684,18 @@ export const render = async ( } if (startNode.calcIntersect) { + // console.log( + // 'APA13 calculating start intersection start node', + // startNode.id, + // startNode.x, + // startNode.y, + // 'w:', + // startNode.width, + // 'h:', + // startNode.height, + // '\nPos', + // edge.points[0] + // ); const intersection = startNode.calcIntersect( { x: startNode.offset.posX + startNode.width / 2, @@ -707,9 +720,18 @@ export const render = async ( }, edge.points[edge.points.length - 1] ); + // if (edge.id === 'L_n4_C_10_0') { + // console.log('APA14 lineData', edge.points, 'intersection:', intersection); + // console.log( + // 'APA14! calculating end intersection\ndistance:', + // distance(intersection, edge.points[edge.points.length - 1]) + // ); + // } if (distance(intersection, edge.points[edge.points.length - 1]) > epsilon) { + // console.log('APA13! distance ok\nintersection:', intersection); edge.points.push(intersection); + // console.log('APA13! distance ok\npoints:', edge.points); } } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js index 22fa6c1bb..7cbce9347 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js @@ -5,7 +5,8 @@ import { createText } from '../createText.js'; import utils from '../../utils.js'; import { getLineFunctionsWithOffset } from '../../utils/lineWithOffset.js'; import { getSubGraphTitleMargins } from '../../utils/subGraphTitleMargins.js'; -import { curveBasis, line, select } from 'd3'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { curveBasis, curveLinear, line, select } from 'd3'; import rough from 'roughjs'; import createLabel from './createLabel.js'; import { addEdgeMarkers } from './edgeMarker.ts'; @@ -335,7 +336,7 @@ const cutPathAtIntersect = (_points, boundaryNode) => { return points; }; -const adjustForArrowHeads = function (lineData, size = 5) { +const adjustForArrowHeads = function (lineData, size = 5, shouldLog = false) { const newLineData = [...lineData]; const lastPoint = lineData[lineData.length - 1]; const secondLastPoint = lineData[lineData.length - 2]; @@ -344,6 +345,9 @@ const adjustForArrowHeads = function (lineData, size = 5) { (lastPoint.x - secondLastPoint.x) ** 2 + (lastPoint.y - secondLastPoint.y) ** 2 ); + if (shouldLog) { + log.debug('APA14 distanceBetweenLastPoints', distanceBetweenLastPoints); + } if (distanceBetweenLastPoints < size) { // Calculate the direction vector from the last point to the second last point const directionX = secondLastPoint.x - lastPoint.x; @@ -366,6 +370,195 @@ const adjustForArrowHeads = function (lineData, size = 5) { return newLineData; }; +function extractCornerPoints(points) { + const cornerPoints = []; + const cornerPointPositions = []; + for (let i = 1; i < points.length - 1; i++) { + const prev = points[i - 1]; + const curr = points[i]; + const next = points[i + 1]; + if ( + prev.x === curr.x && + curr.y === next.y && + Math.abs(curr.x - next.x) > 5 && + Math.abs(curr.y - prev.y) > 5 + ) { + cornerPoints.push(curr); + cornerPointPositions.push(i); + } else if ( + prev.y === curr.y && + curr.x === next.x && + Math.abs(curr.x - prev.x) > 5 && + Math.abs(curr.y - next.y) > 5 + ) { + cornerPoints.push(curr); + cornerPointPositions.push(i); + } + } + return { cornerPoints, cornerPointPositions }; +} + +const findAdjacentPoint = function (pointA, pointB, distance) { + const xDiff = pointB.x - pointA.x; + const yDiff = pointB.y - pointA.y; + const length = Math.sqrt(xDiff * xDiff + yDiff * yDiff); + const ratio = distance / length; + return { x: pointB.x - ratio * xDiff, y: pointB.y - ratio * yDiff }; +}; + +const fixCorners = function (lineData) { + const { cornerPointPositions } = extractCornerPoints(lineData); + const newLineData = []; + for (let i = 0; i < lineData.length; i++) { + if (cornerPointPositions.includes(i)) { + const prevPoint = lineData[i - 1]; + const nextPoint = lineData[i + 1]; + const cornerPoint = lineData[i]; + + const newPrevPoint = findAdjacentPoint(prevPoint, cornerPoint, 5); + const newNextPoint = findAdjacentPoint(nextPoint, cornerPoint, 5); + + const xDiff = newNextPoint.x - newPrevPoint.x; + const yDiff = newNextPoint.y - newPrevPoint.y; + newLineData.push(newPrevPoint); + + const a = Math.sqrt(2) * 2; + let newCornerPoint = { x: cornerPoint.x, y: cornerPoint.y }; + if (Math.abs(nextPoint.x - prevPoint.x) > 10 && Math.abs(nextPoint.y - prevPoint.y) >= 10) { + log.debug( + 'Corner point fixing', + Math.abs(nextPoint.x - prevPoint.x), + Math.abs(nextPoint.y - prevPoint.y) + ); + const r = 5; + if (cornerPoint.x === newPrevPoint.x) { + newCornerPoint = { + x: xDiff < 0 ? newPrevPoint.x - r + a : newPrevPoint.x + r - a, + y: yDiff < 0 ? newPrevPoint.y - a : newPrevPoint.y + a, + }; + } else { + newCornerPoint = { + x: xDiff < 0 ? newPrevPoint.x - a : newPrevPoint.x + a, + y: yDiff < 0 ? newPrevPoint.y - r + a : newPrevPoint.y + r - a, + }; + } + } else { + log.debug( + 'Corner point skipping fixing', + Math.abs(nextPoint.x - prevPoint.x), + Math.abs(nextPoint.y - prevPoint.y) + ); + } + newLineData.push(newCornerPoint, newNextPoint); + } else { + newLineData.push(lineData[i]); + } + } + return newLineData; +}; + +export const generateRoundedPath = (points, radius, endPosition) => { + if (points.length < 2) { + return ''; + } + + // console.trace('here', points); + const path = []; + const startPoint = points[0]; + + path.push(`M ${startPoint.x},${startPoint.y}`); + + for (let i = 1; i < points.length - 1; i++) { + const currPoint = points[i]; + const nextPoint = points[i + 1]; + const prevPoint = points[i - 1]; + + // Calculate vectors + const v1 = { x: currPoint.x - prevPoint.x, y: currPoint.y - prevPoint.y }; + const v2 = { x: nextPoint.x - currPoint.x, y: nextPoint.y - currPoint.y }; + + // Normalize vectors + const v1Length = Math.hypot(v1.x, v1.y); + const v2Length = Math.hypot(v2.x, v2.y); + const v1Normalized = { x: v1.x / v1Length, y: v1.y / v1Length }; + const v2Normalized = { x: v2.x / v2Length, y: v2.y / v2Length }; + + // Calculate tangent points + const tangentLength = Math.min(radius, v1Length / 2, v2Length / 2); + const tangent1 = { + x: currPoint.x - v1Normalized.x * tangentLength, + y: currPoint.y - v1Normalized.y * tangentLength, + }; + const tangent2 = { + x: currPoint.x + v2Normalized.x * tangentLength, + y: currPoint.y + v2Normalized.y * tangentLength, + }; + + if (endPosition) { + const { bottomY, leftX, rightX, topY } = endPosition; + if (startPoint.pos === 'b' && tangent1.y > topY) { + tangent1.y = topY; + tangent2.y = topY; + currPoint.y = topY; + } + if (startPoint.pos === 't' && tangent1.y < bottomY) { + tangent1.y = bottomY; + tangent2.y = bottomY; + currPoint.y = bottomY; + } + if (startPoint.pos === 'l' && tangent1.x < rightX) { + tangent1.x = rightX; + tangent2.x = rightX; + currPoint.x = rightX; + } + if (startPoint.pos === 'r' && tangent1.x > leftX) { + tangent1.x = leftX; + tangent2.x = leftX; + currPoint.x = leftX; + } + if (tangent2.x && tangent2.y && tangent1.x && tangent1.y) { + path.push( + `L ${tangent1.x},${tangent1.y}`, + `Q ${currPoint.x},${currPoint.y} ${tangent2.x},${tangent2.y}` + ); + } + } else { + if (tangent2.x && tangent2.y && tangent1.x && tangent1.y) { + path.push( + `L ${tangent1.x},${tangent1.y}`, + `Q ${currPoint.x},${currPoint.y} ${tangent2.x},${tangent2.y}` + ); + } + } + } + // Last point + const lastPoint = points[points.length - 1]; + if (endPosition) { + if (startPoint.pos === 'b') { + if (endPosition?.topY && points[1].y > endPosition?.topY && points[2].y > endPosition?.topY) { + points[1].y = endPosition?.topY; + points[2].y = endPosition?.topY; + } + path.push(`L ${lastPoint.x},${endPosition.topY}`); + } + if (startPoint.pos === 't') { + if (points[1].y < endPosition.bottomY) { + points[1].y = endPosition.bottomY; + points[2].y = endPosition.bottomY; + } + path.push(`L ${lastPoint.x},${endPosition.bottomY}`); + } + if (startPoint.pos === 'l') { + path.push(`L ${endPosition.rightX},${lastPoint.y}`); + } + if (startPoint.pos === 'r') { + path.push(`L ${endPosition.leftX},${lastPoint.y}`); + } + } else { + path.push(`L ${lastPoint.x},${lastPoint.y}`); + } + return path.join(' '); +}; export const insertEdge = function (elem, edge, clusterDb, diagramType, startNode, endNode, id) { const { handDrawnSeed } = getConfig(); @@ -407,8 +600,12 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod } let lineData = points.filter((p) => !Number.isNaN(p.y)); - lineData = adjustForArrowHeads(lineData); - // lineData = fixCorners(lineData); + lineData = adjustForArrowHeads(lineData, 4, edge.id === 'L_n4_C_10_0'); + lineData = fixCorners(lineData); + // if (edge.id === 'L_n4_C_10_0') { + // console.log('APA14 lineData', lineData); + // } + // lineData = adjustForArrowHeads(lineData); let curve = curveBasis; // let curve = curveLinear; if (edge.curve) { @@ -417,6 +614,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod const { x, y } = getLineFunctionsWithOffset(edge); const lineFunction = line().x(x).y(y).curve(curve); + // const lineFunction = line().curve(curve); let strokeClasses; switch (edge.thickness) { @@ -447,6 +645,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod } let svgPath; let linePath = lineFunction(lineData); + // let linePath = generateRoundedPath(lineData, 5); const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style]; if (edge.look === 'handDrawn') { const rc = rough.svg(elem); @@ -478,16 +677,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod // DEBUG code, DO NOT REMOVE // adds a red circle at each edge coordinate - // cornerPoints.forEach((point) => { - // elem - // .append('circle') - // .style('stroke', 'blue') - // .style('fill', 'blue') - // .attr('r', 3) - // .attr('cx', point.x) - // .attr('cy', point.y); - // }); - // lineData.forEach((point) => { + // points.forEach((point) => { // elem // .append('circle') // .style('stroke', 'red') diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js index b12f8688e..24ff6c791 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js @@ -57,7 +57,7 @@ function intersectLine(p1, p2, q1, q2) { num = a2 * c1 - a1 * c2; const y = num < 0 ? (num - offset) / denom : (num + offset) / denom; // console.log( - // 'APA30 intersectLine intersection', + // 'APA13 intersectLine intersection', // '\np1: (', // p1.x, // p1.y, diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js index 2e48ba8d0..32a9efaa5 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js @@ -7,7 +7,7 @@ import intersectLine from './intersect-line.js'; function intersectPolygon(node, polyPoints, point) { let x1 = node.x; let y1 = node.y; - + // console.trace('APA14 intersectPolygon', x1, y1, polyPoints, point); let intersections = []; let minX = Number.POSITIVE_INFINITY; @@ -24,7 +24,7 @@ function intersectPolygon(node, polyPoints, point) { let left = x1 - node.width / 2 - minX; let top = y1 - node.height / 2 - minY; - + // console.log('APA13 intersectPolygon2 ', left, y1); for (let i = 0; i < polyPoints.length; i++) { let p1 = polyPoints[i]; let p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0]; @@ -34,7 +34,9 @@ function intersectPolygon(node, polyPoints, point) { { x: left + p1.x, y: top + p1.y }, { x: left + p2.x, y: top + p2.y } ); + // console.log('APA13 intersectPolygon3 ', intersect); if (intersect) { + // console.log('APA13 intersectPolygon4 ', intersect); intersections.push(intersect); } } @@ -42,6 +44,7 @@ function intersectPolygon(node, polyPoints, point) { if (!intersections.length) { return node; } + // console.log('APA12 intersectPolygon5 '); if (intersections.length > 1) { // More intersections, find the one nearest to edge end point @@ -54,6 +57,8 @@ function intersectPolygon(node, polyPoints, point) { let qdy = q.y - point.y; let distq = Math.sqrt(qdx * qdx + qdy * qdy); + // console.log('APA12 intersectPolygon6 '); + return distp < distq ? -1 : distp === distq ? 0 : 1; }); } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts index d75b5102d..d1ba0a0c1 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts @@ -66,7 +66,9 @@ export async function card