Skip to content

Commit

Permalink
Merge pull request #4830 from mermaid-js/fix/flowchartElkArrow
Browse files Browse the repository at this point in the history
Fix: flowchartElk Arrow overlap
  • Loading branch information
knsv authored Sep 14, 2023
2 parents 6219aa5 + f208631 commit ed9159c
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 86 deletions.
67 changes: 3 additions & 64 deletions packages/mermaid/src/dagre-wrapper/edges.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/dagre-wrapper/markers.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ const point = (elem, type) => {
.attr('id', type + '-pointStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 0)
.attr('refX', 4.5)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
Expand Down
3 changes: 2 additions & 1 deletion packages/mermaid/src/diagrams/class/classRenderer-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import utils from '../../utils.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import common from '../common/common.js';
import type { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
import type { ClassRelation, ClassNote, ClassMap, NamespaceMap } from './classTypes.js';
import type { EdgeData } from '../../types.js';

const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());

Expand Down
18 changes: 0 additions & 18 deletions packages/mermaid/src/diagrams/class/classTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,6 @@ export interface ClassNote {
text: string;
}

export interface EdgeData {
arrowheadStyle?: string;
labelpos?: string;
labelType?: string;
label?: string;
classes: string;
pattern: string;
id: string;
arrowhead: string;
startLabelRight: string;
endLabelLeft: string;
arrowTypeStart: string;
arrowTypeEnd: string;
style: string;
labelStyle: string;
curve: any;
}

export type ClassRelation = {
id1: string;
id2: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { setupGraphViewbox } from '../../../setupGraphViewbox.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 = {};
Expand Down Expand Up @@ -704,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))
Expand Down
18 changes: 18 additions & 0 deletions packages/mermaid/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,21 @@ export interface TextDimensions {
height: number;
lineHeight?: number;
}

export interface EdgeData {
arrowheadStyle?: string;
labelpos?: string;
labelType?: string;
label?: string;
classes: string;
pattern: string;
id: string;
arrowhead: string;
startLabelRight: string;
endLabelLeft: string;
arrowTypeStart: string;
arrowTypeEnd: string;
style: string;
labelStyle: string;
curve: any;
}
90 changes: 90 additions & 0 deletions packages/mermaid/src/utils/lineWithOffset.ts
Original file line number Diff line number Diff line change
@@ -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;
},
};
};

0 comments on commit ed9159c

Please sign in to comment.