diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index f89b4422b..a47d5f474 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -5,6 +5,7 @@ import { line, curveBasis, select } from 'd3'; import { getConfig } from '../config.js'; import utils from '../utils.js'; import { evaluate } from '../diagrams/common/common.js'; +import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js'; let edgeLabels = {}; let terminalLabels = {}; @@ -368,20 +369,6 @@ const cutPathAtIntersect = (_points, boundryNode) => { return points; }; -/** - * Calculate the deltas and angle between two points - * @param {{x: number, y:number}} point1 - * @param {{x: number, y:number}} point2 - * @returns {{angle: number, deltaX: number, deltaY: number}} - */ -function calculateDeltaAndAngle(point1, point2) { - const [x1, y1] = [point1.x, point1.y]; - const [x2, y2] = [point2.x, point2.y]; - const deltaX = x2 - x1; - const deltaY = y2 - y1; - return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY }; -} - export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) { let points = edge.points; let pointsHasChanged = false; @@ -456,56 +443,8 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph curve = edge.curve; } - // We need to draw the lines a bit shorter to avoid drawing - // under any transparent markers. - // The offsets are calculated from the markers' dimensions. - const markerOffsets = { - aggregation: 18, - extension: 18, - composition: 18, - dependency: 6, - lollipop: 13.5, - arrow_point: 5.3, - }; - - const lineFunction = line() - .x(function (d, i, data) { - let offset = 0; - if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { - // Handle first point - // Calculate the angle and delta between the first two points - const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]); - // Calculate the offset based on the angle and the marker's dimensions - offset = markerOffsets[edge.arrowTypeStart] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0; - } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { - // Handle last point - // Calculate the angle and delta between the last two points - const { angle, deltaX } = calculateDeltaAndAngle( - data[data.length - 1], - data[data.length - 2] - ); - offset = markerOffsets[edge.arrowTypeEnd] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0; - } - return d.x + offset; - }) - .y(function (d, i, data) { - // Same handling as X above - let offset = 0; - if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { - const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]); - offset = - markerOffsets[edge.arrowTypeStart] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1); - } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { - const { angle, deltaY } = calculateDeltaAndAngle( - data[data.length - 1], - data[data.length - 2] - ); - offset = - markerOffsets[edge.arrowTypeEnd] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1); - } - return d.y + offset; - }) - .curve(curve); + const { x, y } = getLineFunctionsWithOffset(edge); + const lineFunction = line().x(x).y(y).curve(curve); // Construct stroke classes based on properties let strokeClasses; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js index c7bfdf524..2f576025f 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js @@ -4,13 +4,14 @@ import insertMarkers from '../../../dagre-wrapper/markers.js'; import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; import { findCommonAncestor } from './render-utils.js'; import { labelHelper } from '../../../dagre-wrapper/shapes/util.js'; -import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { getConfig } from '../../../config.js'; import { log } from '../../../logger.js'; import { setupGraphViewbox } from '../../../setupGraphViewbox.js'; -import common, { evaluate } from '../../common/common.js'; +import common from '../../common/common.js'; import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; import ELK from 'elkjs/lib/elk.bundled.js'; +import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js'; + const elk = new ELK(); let portPos = {}; @@ -705,8 +706,8 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) { [dest.x + offset.x, dest.y + offset.y], ]; - // const curve = line().curve(curveBasis); - const curve = line().curve(curveLinear); + const { x, y } = getLineFunctionsWithOffset(edge.edgeData); + const curve = line().x(x).y(y).curve(curveLinear); const edgePath = edgesEl .insert('path') .attr('d', curve(points)) diff --git a/packages/mermaid/src/utils/lineWithOffset.ts b/packages/mermaid/src/utils/lineWithOffset.ts new file mode 100644 index 000000000..9b3176783 --- /dev/null +++ b/packages/mermaid/src/utils/lineWithOffset.ts @@ -0,0 +1,90 @@ +import type { EdgeData, Point } from '../types.js'; + +// We need to draw the lines a bit shorter to avoid drawing +// under any transparent markers. +// The offsets are calculated from the markers' dimensions. +const markerOffsets = { + aggregation: 18, + extension: 18, + composition: 18, + dependency: 6, + lollipop: 13.5, + arrow_point: 5.3, +} as const; + +/** + * Calculate the deltas and angle between two points + * @param point1 - First point + * @param point2 - Second point + * @returns The angle, deltaX and deltaY + */ +function calculateDeltaAndAngle( + point1: Point | [number, number], + point2: Point | [number, number] +): { angle: number; deltaX: number; deltaY: number } { + point1 = pointTransformer(point1); + point2 = pointTransformer(point2); + const [x1, y1] = [point1.x, point1.y]; + const [x2, y2] = [point2.x, point2.y]; + const deltaX = x2 - x1; + const deltaY = y2 - y1; + return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY }; +} + +const pointTransformer = (data: Point | [number, number]) => { + if (Array.isArray(data)) { + return { x: data[0], y: data[1] }; + } + return data; +}; + +export const getLineFunctionsWithOffset = (edge: EdgeData) => { + return { + x: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) { + let offset = 0; + if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { + // Handle first point + // Calculate the angle and delta between the first two points + const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]); + // Calculate the offset based on the angle and the marker's dimensions + offset = + markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] * + Math.cos(angle) * + (deltaX >= 0 ? 1 : -1); + } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { + // Handle last point + // Calculate the angle and delta between the last two points + const { angle, deltaX } = calculateDeltaAndAngle( + data[data.length - 1], + data[data.length - 2] + ); + offset = + markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] * + Math.cos(angle) * + (deltaX >= 0 ? 1 : -1); + } + return pointTransformer(d).x + offset; + }, + y: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) { + // Same handling as X above + let offset = 0; + if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { + const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]); + offset = + markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] * + Math.abs(Math.sin(angle)) * + (deltaY >= 0 ? 1 : -1); + } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { + const { angle, deltaY } = calculateDeltaAndAngle( + data[data.length - 1], + data[data.length - 2] + ); + offset = + markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] * + Math.abs(Math.sin(angle)) * + (deltaY >= 0 ? 1 : -1); + } + return pointTransformer(d).y + offset; + }, + }; +};