mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-19 15:30:03 +02:00
made draw commit more readable, included more helper functions and interfaces, added in-source test suite to renderer
This commit is contained in:
@@ -9,6 +9,9 @@ import type { GitGraphDiagramConfig } from '../../config.type.js';
|
|||||||
|
|
||||||
let allCommitsDict = new Map();
|
let allCommitsDict = new Map();
|
||||||
|
|
||||||
|
const LAYOUT_OFFSET = 10;
|
||||||
|
const COMMIT_STEP = 40;
|
||||||
|
|
||||||
const commitType: CommitType = {
|
const commitType: CommitType = {
|
||||||
NORMAL: 0,
|
NORMAL: 0,
|
||||||
REVERSE: 1,
|
REVERSE: 1,
|
||||||
@@ -29,6 +32,10 @@ interface CommitPosition {
|
|||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CommitPositionOffset extends CommitPosition {
|
||||||
|
posWithOffset: number;
|
||||||
|
}
|
||||||
|
|
||||||
const branchPos = new Map<string, BranchPosition>();
|
const branchPos = new Map<string, BranchPosition>();
|
||||||
const commitPos = new Map<string, CommitPosition>();
|
const commitPos = new Map<string, CommitPosition>();
|
||||||
let lanes: number[] = [];
|
let lanes: number[] = [];
|
||||||
@@ -93,9 +100,7 @@ const findClosestParent = (parents: string[], useBTLogic = false): string | unde
|
|||||||
const setParallelBTPos = (
|
const setParallelBTPos = (
|
||||||
sortedKeys: string[],
|
sortedKeys: string[],
|
||||||
commits: Map<string, Commit>,
|
commits: Map<string, Commit>,
|
||||||
defaultPos: number,
|
defaultPos: number
|
||||||
commitStep: number,
|
|
||||||
layoutOffset: number
|
|
||||||
) => {
|
) => {
|
||||||
let curPos = defaultPos;
|
let curPos = defaultPos;
|
||||||
let maxPosition = defaultPos;
|
let maxPosition = defaultPos;
|
||||||
@@ -108,12 +113,12 @@ const setParallelBTPos = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasParents(commit)) {
|
if (hasParents(commit)) {
|
||||||
curPos = calculateCommitPosition(commit, commitStep, maxPosition);
|
curPos = calculateCommitPosition(commit);
|
||||||
maxPosition = Math.max(curPos, maxPosition);
|
maxPosition = Math.max(curPos, maxPosition);
|
||||||
} else {
|
} else {
|
||||||
roots.push(commit);
|
roots.push(commit);
|
||||||
}
|
}
|
||||||
setCommitPosition(commit, curPos, layoutOffset);
|
setCommitPosition(commit, curPos);
|
||||||
});
|
});
|
||||||
|
|
||||||
curPos = maxPosition;
|
curPos = maxPosition;
|
||||||
@@ -125,7 +130,7 @@ const setParallelBTPos = (
|
|||||||
const hasParents = (commit: Commit): boolean => commit.parents?.length > 0;
|
const hasParents = (commit: Commit): boolean => commit.parents?.length > 0;
|
||||||
|
|
||||||
const findClosestParentPos = (commit: Commit): number => {
|
const findClosestParentPos = (commit: Commit): number => {
|
||||||
const closestParent = findClosestParent(commit.parents.filter((p) => p !== null) as string[]);
|
const closestParent = findClosestParent(commit.parents.filter((p) => p !== null));
|
||||||
if (!closestParent) {
|
if (!closestParent) {
|
||||||
throw new Error(`Closest parent not found for commit ${commit.id}`);
|
throw new Error(`Closest parent not found for commit ${commit.id}`);
|
||||||
}
|
}
|
||||||
@@ -137,19 +142,19 @@ const findClosestParentPos = (commit: Commit): number => {
|
|||||||
return closestParentPos;
|
return closestParentPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateCommitPosition = (commit: Commit, commitStep: number): number => {
|
const calculateCommitPosition = (commit: Commit): number => {
|
||||||
const closestParentPos = findClosestParentPos(commit);
|
const closestParentPos = findClosestParentPos(commit);
|
||||||
return closestParentPos + commitStep;
|
return closestParentPos + COMMIT_STEP;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCommitPosition = (commit: Commit, curPos: number, layoutOffset: number) => {
|
const setCommitPosition = (commit: Commit, curPos: number) => {
|
||||||
const branch = branchPos.get(commit.branch);
|
const branch = branchPos.get(commit.branch);
|
||||||
if (!branch) {
|
if (!branch) {
|
||||||
throw new Error(`Branch not found for commit ${commit.id}`);
|
throw new Error(`Branch not found for commit ${commit.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = branch.pos;
|
const x = branch.pos;
|
||||||
const y = curPos + layoutOffset;
|
const y = curPos + LAYOUT_OFFSET;
|
||||||
commitPos.set(commit.id, { x, y });
|
commitPos.set(commit.id, { x, y });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -167,8 +172,7 @@ const setRootPosition = (commit: Commit, curPos: number, defaultPos: number) =>
|
|||||||
const drawCommitBullet = (
|
const drawCommitBullet = (
|
||||||
gBullets: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
gBullets: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
||||||
commit: Commit,
|
commit: Commit,
|
||||||
x: number,
|
commitPosition: CommitPositionOffset,
|
||||||
y: number,
|
|
||||||
typeClass: string,
|
typeClass: string,
|
||||||
branchIndex: number,
|
branchIndex: number,
|
||||||
commitSymbolType: number
|
commitSymbolType: number
|
||||||
@@ -176,8 +180,8 @@ const drawCommitBullet = (
|
|||||||
if (commitSymbolType === commitType.HIGHLIGHT) {
|
if (commitSymbolType === commitType.HIGHLIGHT) {
|
||||||
gBullets
|
gBullets
|
||||||
.append('rect')
|
.append('rect')
|
||||||
.attr('x', x - 10)
|
.attr('x', commitPosition.x - 10)
|
||||||
.attr('y', y - 10)
|
.attr('y', commitPosition.y - 10)
|
||||||
.attr('width', 20)
|
.attr('width', 20)
|
||||||
.attr('height', 20)
|
.attr('height', 20)
|
||||||
.attr(
|
.attr(
|
||||||
@@ -186,8 +190,8 @@ const drawCommitBullet = (
|
|||||||
);
|
);
|
||||||
gBullets
|
gBullets
|
||||||
.append('rect')
|
.append('rect')
|
||||||
.attr('x', x - 6)
|
.attr('x', commitPosition.x - 6)
|
||||||
.attr('y', y - 6)
|
.attr('y', commitPosition.y - 6)
|
||||||
.attr('width', 12)
|
.attr('width', 12)
|
||||||
.attr('height', 12)
|
.attr('height', 12)
|
||||||
.attr(
|
.attr(
|
||||||
@@ -197,50 +201,50 @@ const drawCommitBullet = (
|
|||||||
} else if (commitSymbolType === commitType.CHERRY_PICK) {
|
} else if (commitSymbolType === commitType.CHERRY_PICK) {
|
||||||
gBullets
|
gBullets
|
||||||
.append('circle')
|
.append('circle')
|
||||||
.attr('cx', x)
|
.attr('cx', commitPosition.x)
|
||||||
.attr('cy', y)
|
.attr('cy', commitPosition.y)
|
||||||
.attr('r', 10)
|
.attr('r', 10)
|
||||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||||
gBullets
|
gBullets
|
||||||
.append('circle')
|
.append('circle')
|
||||||
.attr('cx', x - 3)
|
.attr('cx', commitPosition.x - 3)
|
||||||
.attr('cy', y + 2)
|
.attr('cy', commitPosition.y + 2)
|
||||||
.attr('r', 2.75)
|
.attr('r', 2.75)
|
||||||
.attr('fill', '#fff')
|
.attr('fill', '#fff')
|
||||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||||
gBullets
|
gBullets
|
||||||
.append('circle')
|
.append('circle')
|
||||||
.attr('cx', x + 3)
|
.attr('cx', commitPosition.x + 3)
|
||||||
.attr('cy', y + 2)
|
.attr('cy', commitPosition.y + 2)
|
||||||
.attr('r', 2.75)
|
.attr('r', 2.75)
|
||||||
.attr('fill', '#fff')
|
.attr('fill', '#fff')
|
||||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||||
gBullets
|
gBullets
|
||||||
.append('line')
|
.append('line')
|
||||||
.attr('x1', x + 3)
|
.attr('x1', commitPosition.x + 3)
|
||||||
.attr('y1', y + 1)
|
.attr('y1', commitPosition.y + 1)
|
||||||
.attr('x2', x)
|
.attr('x2', commitPosition.x)
|
||||||
.attr('y2', y - 5)
|
.attr('y2', commitPosition.y - 5)
|
||||||
.attr('stroke', '#fff')
|
.attr('stroke', '#fff')
|
||||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||||
gBullets
|
gBullets
|
||||||
.append('line')
|
.append('line')
|
||||||
.attr('x1', x - 3)
|
.attr('x1', commitPosition.x - 3)
|
||||||
.attr('y1', y + 1)
|
.attr('y1', commitPosition.y + 1)
|
||||||
.attr('x2', x)
|
.attr('x2', commitPosition.x)
|
||||||
.attr('y2', y - 5)
|
.attr('y2', commitPosition.y - 5)
|
||||||
.attr('stroke', '#fff')
|
.attr('stroke', '#fff')
|
||||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||||
} else {
|
} else {
|
||||||
const circle = gBullets.append('circle');
|
const circle = gBullets.append('circle');
|
||||||
circle.attr('cx', x);
|
circle.attr('cx', commitPosition.x);
|
||||||
circle.attr('cy', y);
|
circle.attr('cy', commitPosition.y);
|
||||||
circle.attr('r', commit.type === commitType.MERGE ? 9 : 10);
|
circle.attr('r', commit.type === commitType.MERGE ? 9 : 10);
|
||||||
circle.attr('class', `commit ${commit.id} commit${branchIndex % THEME_COLOR_LIMIT}`);
|
circle.attr('class', `commit ${commit.id} commit${branchIndex % THEME_COLOR_LIMIT}`);
|
||||||
if (commit.type === commitType.MERGE) {
|
if (commit.type === commitType.MERGE) {
|
||||||
const circle2 = gBullets.append('circle');
|
const circle2 = gBullets.append('circle');
|
||||||
circle2.attr('cx', x);
|
circle2.attr('cx', commitPosition.x);
|
||||||
circle2.attr('cy', y);
|
circle2.attr('cy', commitPosition.y);
|
||||||
circle2.attr('r', 6);
|
circle2.attr('r', 6);
|
||||||
circle2.attr(
|
circle2.attr(
|
||||||
'class',
|
'class',
|
||||||
@@ -250,7 +254,10 @@ const drawCommitBullet = (
|
|||||||
if (commitSymbolType === commitType.REVERSE) {
|
if (commitSymbolType === commitType.REVERSE) {
|
||||||
const cross = gBullets.append('path');
|
const cross = gBullets.append('path');
|
||||||
cross
|
cross
|
||||||
.attr('d', `M ${x - 5},${y - 5}L${x + 5},${y + 5}M${x - 5},${y + 5}L${x + 5},${y - 5}`)
|
.attr(
|
||||||
|
'd',
|
||||||
|
`M ${commitPosition.x - 5},${commitPosition.y - 5}L${commitPosition.x + 5},${commitPosition.y + 5}M${commitPosition.x - 5},${commitPosition.y + 5}L${commitPosition.x + 5},${commitPosition.y - 5}`
|
||||||
|
)
|
||||||
.attr('class', `commit ${typeClass} ${commit.id} commit${branchIndex % THEME_COLOR_LIMIT}`);
|
.attr('class', `commit ${typeClass} ${commit.id} commit${branchIndex % THEME_COLOR_LIMIT}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,10 +266,8 @@ const drawCommitBullet = (
|
|||||||
const drawCommitLabel = (
|
const drawCommitLabel = (
|
||||||
gLabels: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
gLabels: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
||||||
commit: Commit,
|
commit: Commit,
|
||||||
x: number,
|
commitPosition: CommitPositionOffset,
|
||||||
y: number,
|
|
||||||
pos: number,
|
pos: number,
|
||||||
posWithOffset: number,
|
|
||||||
gitGraphConfig: GitGraphDiagramConfig
|
gitGraphConfig: GitGraphDiagramConfig
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
@@ -274,34 +279,52 @@ const drawCommitLabel = (
|
|||||||
const labelBkg = wrapper.insert('rect').attr('class', 'commit-label-bkg');
|
const labelBkg = wrapper.insert('rect').attr('class', 'commit-label-bkg');
|
||||||
const text = wrapper
|
const text = wrapper
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('x', x)
|
.attr('x', commitPosition.x)
|
||||||
.attr('y', y + 25)
|
.attr('y', commitPosition.y + 25)
|
||||||
.attr('class', 'commit-label')
|
.attr('class', 'commit-label')
|
||||||
.text(commit.id);
|
.text(commit.id);
|
||||||
const bbox = text.node()?.getBBox();
|
const bbox = text.node()?.getBBox();
|
||||||
|
|
||||||
if (bbox) {
|
if (bbox) {
|
||||||
labelBkg
|
labelBkg
|
||||||
.attr('x', posWithOffset - bbox.width / 2 - 2)
|
.attr('x', commitPosition.posWithOffset - bbox.width / 2 - 2)
|
||||||
.attr('y', y + 13.5)
|
.attr('y', commitPosition.y + 13.5)
|
||||||
.attr('width', bbox.width + 4)
|
.attr('width', bbox.width + 4)
|
||||||
.attr('height', bbox.height + 4);
|
.attr('height', bbox.height + 4);
|
||||||
|
|
||||||
if (dir === 'TB' || dir === 'BT') {
|
if (dir === 'TB' || dir === 'BT') {
|
||||||
labelBkg.attr('x', x - (bbox.width + 4)).attr('y', y - 12);
|
labelBkg.attr('x', commitPosition.x - (bbox.width + 4)).attr('y', commitPosition.y - 12);
|
||||||
text.attr('x', x - (bbox.width + 2)).attr('y', y + bbox.height - 12);
|
text
|
||||||
|
.attr('x', commitPosition.x - (bbox.width + 2))
|
||||||
|
.attr('y', commitPosition.y + bbox.height - 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gitGraphConfig.rotateCommitLabel) {
|
if (gitGraphConfig.rotateCommitLabel) {
|
||||||
if (dir === 'TB' || dir === 'BT') {
|
if (dir === 'TB' || dir === 'BT') {
|
||||||
text.attr('transform', 'rotate(' + -45 + ', ' + x + ', ' + y + ')');
|
text.attr(
|
||||||
labelBkg.attr('transform', 'rotate(' + -45 + ', ' + x + ', ' + y + ')');
|
'transform',
|
||||||
|
'rotate(' + -45 + ', ' + commitPosition.x + ', ' + commitPosition.y + ')'
|
||||||
|
);
|
||||||
|
labelBkg.attr(
|
||||||
|
'transform',
|
||||||
|
'rotate(' + -45 + ', ' + commitPosition.x + ', ' + commitPosition.y + ')'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const r_x = -7.5 - ((bbox.width + 10) / 25) * 9.5;
|
const r_x = -7.5 - ((bbox.width + 10) / 25) * 9.5;
|
||||||
const r_y = 10 + (bbox.width / 25) * 8.5;
|
const r_y = 10 + (bbox.width / 25) * 8.5;
|
||||||
wrapper.attr(
|
wrapper.attr(
|
||||||
'transform',
|
'transform',
|
||||||
'translate(' + r_x + ', ' + r_y + ') rotate(' + -45 + ', ' + pos + ', ' + y + ')'
|
'translate(' +
|
||||||
|
r_x +
|
||||||
|
', ' +
|
||||||
|
r_y +
|
||||||
|
') rotate(' +
|
||||||
|
-45 +
|
||||||
|
', ' +
|
||||||
|
pos +
|
||||||
|
', ' +
|
||||||
|
commitPosition.y +
|
||||||
|
')'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,11 +335,8 @@ const drawCommitLabel = (
|
|||||||
const drawCommitTags = (
|
const drawCommitTags = (
|
||||||
gLabels: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
gLabels: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
||||||
commit: Commit,
|
commit: Commit,
|
||||||
x: number,
|
commitPosition: CommitPositionOffset,
|
||||||
y: number,
|
pos: number
|
||||||
pos: number,
|
|
||||||
posWithOffset: number,
|
|
||||||
layoutOffset: number
|
|
||||||
) => {
|
) => {
|
||||||
if (commit.tags.length > 0) {
|
if (commit.tags.length > 0) {
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
@@ -329,7 +349,7 @@ const drawCommitTags = (
|
|||||||
const hole = gLabels.append('circle');
|
const hole = gLabels.append('circle');
|
||||||
const tag = gLabels
|
const tag = gLabels
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('y', y - 16 - yOffset)
|
.attr('y', commitPosition.y - 16 - yOffset)
|
||||||
.attr('class', 'tag-label')
|
.attr('class', 'tag-label')
|
||||||
.text(tagValue);
|
.text(tagValue);
|
||||||
const tagBbox = tag.node()?.getBBox();
|
const tagBbox = tag.node()?.getBBox();
|
||||||
@@ -339,7 +359,7 @@ const drawCommitTags = (
|
|||||||
maxTagBboxWidth = Math.max(maxTagBboxWidth, tagBbox.width);
|
maxTagBboxWidth = Math.max(maxTagBboxWidth, tagBbox.width);
|
||||||
maxTagBboxHeight = Math.max(maxTagBboxHeight, tagBbox.height);
|
maxTagBboxHeight = Math.max(maxTagBboxHeight, tagBbox.height);
|
||||||
|
|
||||||
tag.attr('x', posWithOffset - tagBbox.width / 2);
|
tag.attr('x', commitPosition.posWithOffset - tagBbox.width / 2);
|
||||||
|
|
||||||
tagElements.push({
|
tagElements.push({
|
||||||
tag,
|
tag,
|
||||||
@@ -353,16 +373,16 @@ const drawCommitTags = (
|
|||||||
|
|
||||||
for (const { tag, hole, rect, yOffset } of tagElements) {
|
for (const { tag, hole, rect, yOffset } of tagElements) {
|
||||||
const h2 = maxTagBboxHeight / 2;
|
const h2 = maxTagBboxHeight / 2;
|
||||||
const ly = y - 19.2 - yOffset;
|
const ly = commitPosition.y - 19.2 - yOffset;
|
||||||
rect.attr('class', 'tag-label-bkg').attr(
|
rect.attr('class', 'tag-label-bkg').attr(
|
||||||
'points',
|
'points',
|
||||||
`
|
`
|
||||||
${pos - maxTagBboxWidth / 2 - 2},${ly + 2}
|
${pos - maxTagBboxWidth / 2 - 2},${ly + 2}
|
||||||
${pos - maxTagBboxWidth / 2 - 2},${ly - 2}
|
${pos - maxTagBboxWidth / 2 - 2},${ly - 2}
|
||||||
${posWithOffset - maxTagBboxWidth / 2 - 4},${ly - h2 - 2}
|
${commitPosition.posWithOffset - maxTagBboxWidth / 2 - 4},${ly - h2 - 2}
|
||||||
${posWithOffset + maxTagBboxWidth / 2 + 4},${ly - h2 - 2}
|
${commitPosition.posWithOffset + maxTagBboxWidth / 2 + 4},${ly - h2 - 2}
|
||||||
${posWithOffset + maxTagBboxWidth / 2 + 4},${ly + h2 + 2}
|
${commitPosition.posWithOffset + maxTagBboxWidth / 2 + 4},${ly + h2 + 2}
|
||||||
${posWithOffset - maxTagBboxWidth / 2 - 4},${ly + h2 + 2}`
|
${commitPosition.posWithOffset - maxTagBboxWidth / 2 - 4},${ly + h2 + 2}`
|
||||||
);
|
);
|
||||||
|
|
||||||
hole
|
hole
|
||||||
@@ -379,22 +399,22 @@ const drawCommitTags = (
|
|||||||
.attr(
|
.attr(
|
||||||
'points',
|
'points',
|
||||||
`
|
`
|
||||||
${x},${yOrigin + 2}
|
${commitPosition.x},${yOrigin + 2}
|
||||||
${x},${yOrigin - 2}
|
${commitPosition.x},${yOrigin - 2}
|
||||||
${x + layoutOffset},${yOrigin - h2 - 2}
|
${commitPosition.x + LAYOUT_OFFSET},${yOrigin - h2 - 2}
|
||||||
${x + layoutOffset + maxTagBboxWidth + 4},${yOrigin - h2 - 2}
|
${commitPosition.x + LAYOUT_OFFSET + maxTagBboxWidth + 4},${yOrigin - h2 - 2}
|
||||||
${x + layoutOffset + maxTagBboxWidth + 4},${yOrigin + h2 + 2}
|
${commitPosition.x + LAYOUT_OFFSET + maxTagBboxWidth + 4},${yOrigin + h2 + 2}
|
||||||
${x + layoutOffset},${yOrigin + h2 + 2}`
|
${commitPosition.x + LAYOUT_OFFSET},${yOrigin + h2 + 2}`
|
||||||
)
|
)
|
||||||
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
.attr('transform', 'translate(12,12) rotate(45, ' + commitPosition.x + ',' + pos + ')');
|
||||||
hole
|
hole
|
||||||
.attr('cx', x + 2)
|
.attr('cx', commitPosition.x + 2)
|
||||||
.attr('cy', yOrigin)
|
.attr('cy', yOrigin)
|
||||||
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
.attr('transform', 'translate(12,12) rotate(45, ' + commitPosition.x + ',' + pos + ')');
|
||||||
tag
|
tag
|
||||||
.attr('x', x + 5)
|
.attr('x', commitPosition.x + 5)
|
||||||
.attr('y', yOrigin + 3)
|
.attr('y', yOrigin + 3)
|
||||||
.attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')');
|
.attr('transform', 'translate(14,14) rotate(45, ' + commitPosition.x + ',' + pos + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,30 +441,23 @@ const getCommitClassType = (commit: Commit): string => {
|
|||||||
const calculatePosition = (
|
const calculatePosition = (
|
||||||
commit: Commit,
|
commit: Commit,
|
||||||
dir: string,
|
dir: string,
|
||||||
isParallelCommits: boolean,
|
|
||||||
pos: number,
|
pos: number,
|
||||||
commitStep: number,
|
|
||||||
layoutOffset: number,
|
|
||||||
commitPos: Map<string, CommitPosition>
|
commitPos: Map<string, CommitPosition>
|
||||||
): number => {
|
): number => {
|
||||||
const defaultCommitPosition = { x: 0, y: defaultPos }; // Default position if commit is not found
|
const defaultCommitPosition = { x: 0, y: 0 }; // Default position if commit is not found
|
||||||
|
|
||||||
if (isParallelCommits) {
|
|
||||||
if (commit.parents.length > 0) {
|
if (commit.parents.length > 0) {
|
||||||
const closestParent =
|
const closestParent = findClosestParent(commit.parents);
|
||||||
dir === 'BT' ? findClosestParent(commit.parents) : findClosestParent(commit.parents);
|
|
||||||
|
|
||||||
// Check if closestParent is defined
|
|
||||||
if (closestParent) {
|
if (closestParent) {
|
||||||
const parentPosition = commitPos.get(closestParent) ?? defaultCommitPosition;
|
const parentPosition = commitPos.get(closestParent) ?? defaultCommitPosition;
|
||||||
|
|
||||||
if (dir === 'TB') {
|
if (dir === 'TB') {
|
||||||
return parentPosition.y + commitStep;
|
return parentPosition.y + COMMIT_STEP;
|
||||||
} else if (dir === 'BT') {
|
} else if (dir === 'BT') {
|
||||||
const currentPosition = commitPos.get(commit.id) ?? defaultCommitPosition;
|
const currentPosition = commitPos.get(commit.id) ?? defaultCommitPosition;
|
||||||
return currentPosition.y - commitStep;
|
return currentPosition.y - COMMIT_STEP;
|
||||||
} else {
|
} else {
|
||||||
return parentPosition.x + commitStep;
|
return parentPosition.x + COMMIT_STEP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -452,14 +465,26 @@ const calculatePosition = (
|
|||||||
return defaultPos;
|
return defaultPos;
|
||||||
} else if (dir === 'BT') {
|
} else if (dir === 'BT') {
|
||||||
const currentPosition = commitPos.get(commit.id) ?? defaultCommitPosition;
|
const currentPosition = commitPos.get(commit.id) ?? defaultCommitPosition;
|
||||||
return currentPosition.y - commitStep;
|
return currentPosition.y - COMMIT_STEP;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
return dir === 'TB' && isParallelCommits ? pos : pos + layoutOffset;
|
const getCommitPosition = (
|
||||||
|
commit: Commit,
|
||||||
|
pos: number,
|
||||||
|
isParallelCommits: boolean
|
||||||
|
): CommitPositionOffset => {
|
||||||
|
const posWithOffset = dir === 'BT' && isParallelCommits ? pos : pos + LAYOUT_OFFSET;
|
||||||
|
const y = dir === 'TB' || dir === 'BT' ? posWithOffset : branchPos.get(commit.branch)?.pos;
|
||||||
|
const x = dir === 'TB' || dir === 'BT' ? branchPos.get(commit.branch)?.pos : posWithOffset;
|
||||||
|
if (x === undefined || y === undefined) {
|
||||||
|
throw new Error(`Position were undefined for commit ${commit.id}`);
|
||||||
|
}
|
||||||
|
return { x, y, posWithOffset };
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawCommits = (
|
const drawCommits = (
|
||||||
@@ -476,8 +501,6 @@ const drawCommits = (
|
|||||||
let pos = dir === 'TB' || dir === 'BT' ? defaultPos : 0;
|
let pos = dir === 'TB' || dir === 'BT' ? defaultPos : 0;
|
||||||
const keys = [...commits.keys()];
|
const keys = [...commits.keys()];
|
||||||
const isParallelCommits = gitGraphConfig?.parallelCommits ?? false;
|
const isParallelCommits = gitGraphConfig?.parallelCommits ?? false;
|
||||||
const layoutOffset = 10;
|
|
||||||
const commitStep = 40;
|
|
||||||
|
|
||||||
const sortKeys = (a: string, b: string) => {
|
const sortKeys = (a: string, b: string) => {
|
||||||
const seqA = commits.get(a)?.seq;
|
const seqA = commits.get(a)?.seq;
|
||||||
@@ -487,12 +510,10 @@ const drawCommits = (
|
|||||||
|
|
||||||
let sortedKeys = keys.sort(sortKeys);
|
let sortedKeys = keys.sort(sortKeys);
|
||||||
|
|
||||||
if (dir === 'BT' && !isParallelCommits) {
|
if (dir === 'BT') {
|
||||||
sortedKeys = sortedKeys.reverse();
|
if (isParallelCommits) {
|
||||||
|
setParallelBTPos(sortedKeys, commits, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir === 'BT' && isParallelCommits) {
|
|
||||||
setParallelBTPos(sortedKeys, commits, pos, commitStep, layoutOffset);
|
|
||||||
sortedKeys = sortedKeys.reverse();
|
sortedKeys = sortedKeys.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,41 +521,30 @@ const drawCommits = (
|
|||||||
const commit = commits.get(key);
|
const commit = commits.get(key);
|
||||||
if (!commit) {
|
if (!commit) {
|
||||||
throw new Error(`Commit not found for key ${key}`);
|
throw new Error(`Commit not found for key ${key}`);
|
||||||
} else {
|
|
||||||
pos = calculatePosition(
|
|
||||||
commit,
|
|
||||||
dir,
|
|
||||||
isParallelCommits,
|
|
||||||
pos,
|
|
||||||
commitStep,
|
|
||||||
layoutOffset,
|
|
||||||
commitPos
|
|
||||||
);
|
|
||||||
const posWithOffset = dir === 'BT' && isParallelCommits ? pos : pos + layoutOffset;
|
|
||||||
const y = dir === 'TB' || dir === 'BT' ? posWithOffset : branchPos.get(commit.branch)?.pos;
|
|
||||||
const x = dir === 'TB' || dir === 'BT' ? branchPos.get(commit.branch)?.pos : posWithOffset;
|
|
||||||
if (x === undefined || y === undefined) {
|
|
||||||
throw new Error(`Position were undefined for commit ${commit.id}`);
|
|
||||||
}
|
}
|
||||||
|
if (isParallelCommits) {
|
||||||
|
pos = calculatePosition(commit, dir, pos, commitPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitPosition = getCommitPosition(commit, pos, isParallelCommits);
|
||||||
// Don't draw the commits now but calculate the positioning which is used by the branch lines etc.
|
// Don't draw the commits now but calculate the positioning which is used by the branch lines etc.
|
||||||
if (modifyGraph) {
|
if (modifyGraph) {
|
||||||
const typeClass = getCommitClassType(commit);
|
const typeClass = getCommitClassType(commit);
|
||||||
const commitSymbolType = commit.customType ?? commit.type;
|
const commitSymbolType = commit.customType ?? commit.type;
|
||||||
const branchIndex = branchPos.get(commit.branch)?.index ?? 0;
|
const branchIndex = branchPos.get(commit.branch)?.index ?? 0;
|
||||||
drawCommitBullet(gBullets, commit, x, y, typeClass, branchIndex, commitSymbolType);
|
drawCommitBullet(gBullets, commit, commitPosition, typeClass, branchIndex, commitSymbolType);
|
||||||
drawCommitLabel(gLabels, commit, x, y, pos, posWithOffset, gitGraphConfig);
|
drawCommitLabel(gLabels, commit, commitPosition, pos, gitGraphConfig);
|
||||||
drawCommitTags(gLabels, commit, x, y, pos, posWithOffset, layoutOffset);
|
drawCommitTags(gLabels, commit, commitPosition, pos);
|
||||||
}
|
}
|
||||||
if (dir === 'TB' || dir === 'BT') {
|
if (dir === 'TB' || dir === 'BT') {
|
||||||
commitPos.set(commit.id, { x: x, y: posWithOffset });
|
commitPos.set(commit.id, { x: commitPosition.x, y: commitPosition.posWithOffset });
|
||||||
} else {
|
} else {
|
||||||
commitPos.set(commit.id, { x: posWithOffset, y: y });
|
commitPos.set(commit.id, { x: commitPosition.posWithOffset, y: commitPosition.y });
|
||||||
}
|
}
|
||||||
pos = dir === 'BT' && isParallelCommits ? pos + commitStep : pos + commitStep + layoutOffset;
|
pos = dir === 'BT' && isParallelCommits ? pos + COMMIT_STEP : pos + COMMIT_STEP + LAYOUT_OFFSET;
|
||||||
if (pos > maxPos) {
|
if (pos > maxPos) {
|
||||||
maxPos = pos;
|
maxPos = pos;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -904,65 +914,269 @@ const drawBranches = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setBranchPosition = function (
|
||||||
|
name: string,
|
||||||
|
pos: number,
|
||||||
|
index: number,
|
||||||
|
bbox: DOMRect,
|
||||||
|
rotateCommitLabel: boolean
|
||||||
|
): number {
|
||||||
|
branchPos.set(name, { pos, index });
|
||||||
|
pos += 50 + (rotateCommitLabel ? 40 : 0) + (dir === 'TB' || dir === 'BT' ? bbox.width / 2 : 0);
|
||||||
|
return pos;
|
||||||
|
};
|
||||||
|
|
||||||
export const draw: DrawDefinition = function (txt, id, ver, diagObj) {
|
export const draw: DrawDefinition = function (txt, id, ver, diagObj) {
|
||||||
clear();
|
clear();
|
||||||
const conf = getConfig();
|
const conf = getConfig();
|
||||||
const gitGraphConfig = conf.gitGraph;
|
const gitGraphConfig = conf.gitGraph;
|
||||||
// try {
|
|
||||||
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
|
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
|
||||||
|
if (!gitGraphConfig) {
|
||||||
|
throw new Error('GitGraph config not found');
|
||||||
|
}
|
||||||
|
const rotateCommitLabel = gitGraphConfig.rotateCommitLabel ?? false;
|
||||||
const db = diagObj.db as GitGraphDB;
|
const db = diagObj.db as GitGraphDB;
|
||||||
allCommitsDict = db.getCommits();
|
allCommitsDict = db.getCommits();
|
||||||
const branches = db.getBranchesAsObjArray();
|
const branches = db.getBranchesAsObjArray();
|
||||||
dir = db.getDirection();
|
dir = db.getDirection();
|
||||||
const diagram = select(`[id="${id}"]`);
|
const diagram = select(`[id="${id}"]`);
|
||||||
// Position branches
|
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
|
|
||||||
branches.forEach((branch, index) => {
|
branches.forEach((branch, index) => {
|
||||||
const labelElement = drawText(branch.name);
|
const labelElement = drawText(branch.name);
|
||||||
const g = diagram.append('g');
|
const g = diagram.append('g');
|
||||||
const branchLabel = g.insert('g').attr('class', 'branchLabel');
|
const branchLabel = g.insert('g').attr('class', 'branchLabel');
|
||||||
const label = branchLabel.insert('g').attr('class', 'label branch-label');
|
const label = branchLabel.insert('g').attr('class', 'label branch-label');
|
||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
label.node().appendChild(labelElement);
|
|
||||||
const bbox = labelElement.getBBox();
|
const bbox = labelElement.getBBox();
|
||||||
|
pos = setBranchPosition(branch.name, pos, index, bbox, rotateCommitLabel);
|
||||||
branchPos.set(branch.name, { pos, index });
|
|
||||||
pos +=
|
|
||||||
50 +
|
|
||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
(gitGraphConfig.rotateCommitLabel ? 40 : 0) +
|
|
||||||
(dir === 'TB' || dir === 'BT' ? bbox.width / 2 : 0);
|
|
||||||
label.remove();
|
label.remove();
|
||||||
branchLabel.remove();
|
branchLabel.remove();
|
||||||
g.remove();
|
g.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
drawCommits(diagram, allCommitsDict, false);
|
drawCommits(diagram, allCommitsDict, false);
|
||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
if (gitGraphConfig.showBranches) {
|
if (gitGraphConfig.showBranches) {
|
||||||
drawBranches(diagram, branches);
|
drawBranches(diagram, branches);
|
||||||
}
|
}
|
||||||
drawArrows(diagram, allCommitsDict);
|
drawArrows(diagram, allCommitsDict);
|
||||||
drawCommits(diagram, allCommitsDict, true);
|
drawCommits(diagram, allCommitsDict, true);
|
||||||
|
|
||||||
utils.insertTitle(
|
utils.insertTitle(
|
||||||
diagram,
|
diagram,
|
||||||
'gitTitleText',
|
'gitTitleText',
|
||||||
// @ts-ignore: TODO Fix ts errors
|
gitGraphConfig.titleTopMargin ?? 0,
|
||||||
gitGraphConfig.titleTopMargin,
|
|
||||||
db.getDiagramTitle()
|
db.getDiagramTitle()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Setup the view box and size of the svg element
|
// Setup the view box and size of the svg element
|
||||||
setupGraphViewbox(
|
setupGraphViewbox(undefined, diagram, gitGraphConfig.diagramPadding, gitGraphConfig.useMaxWidth);
|
||||||
undefined,
|
|
||||||
diagram,
|
|
||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
gitGraphConfig.diagramPadding,
|
|
||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
gitGraphConfig.useMaxWidth ?? conf.useMaxWidth
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
draw,
|
draw,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (import.meta.vitest) {
|
||||||
|
const { it, expect, describe } = import.meta.vitest;
|
||||||
|
|
||||||
|
describe('drawText', () => {
|
||||||
|
it('should drawText', () => {
|
||||||
|
const svgLabel = drawText('main');
|
||||||
|
expect(svgLabel).toBeDefined();
|
||||||
|
expect(svgLabel.children[0].innerHTML).toBe('main');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('drawBranchPositions', () => {
|
||||||
|
const bbox: DOMRect = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
toJSON: () => '',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should setBranchPositions LR with two branches', () => {
|
||||||
|
dir = 'LR';
|
||||||
|
|
||||||
|
const pos = setBranchPosition('main', 0, 0, bbox, true);
|
||||||
|
expect(pos).toBe(90);
|
||||||
|
expect(branchPos.get('main')).toEqual({ pos: 0, index: 0 });
|
||||||
|
const posNext = setBranchPosition('develop', pos, 1, bbox, true);
|
||||||
|
expect(posNext).toBe(180);
|
||||||
|
expect(branchPos.get('develop')).toEqual({ pos: pos, index: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setBranchPositions TB with two branches', () => {
|
||||||
|
dir = 'TB';
|
||||||
|
bbox.width = 34.9921875;
|
||||||
|
|
||||||
|
const pos = setBranchPosition('main', 0, 0, bbox, true);
|
||||||
|
expect(pos).toBe(107.49609375);
|
||||||
|
expect(branchPos.get('main')).toEqual({ pos: 0, index: 0 });
|
||||||
|
|
||||||
|
bbox.width = 56.421875;
|
||||||
|
const posNext = setBranchPosition('develop', pos, 1, bbox, true);
|
||||||
|
expect(posNext).toBe(225.70703125);
|
||||||
|
expect(branchPos.get('develop')).toEqual({ pos: pos, index: 1 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
describe('drawCommits', () => {
|
||||||
|
dir = 'TB';
|
||||||
|
const commits = new Map<string, Commit>([
|
||||||
|
[
|
||||||
|
'commitZero',
|
||||||
|
{
|
||||||
|
id: 'ZERO',
|
||||||
|
message: '',
|
||||||
|
seq: 0,
|
||||||
|
type: commitType.NORMAL,
|
||||||
|
tags: [],
|
||||||
|
parents: [],
|
||||||
|
branch: 'main',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commitA',
|
||||||
|
{
|
||||||
|
id: 'A',
|
||||||
|
message: '',
|
||||||
|
seq: 1,
|
||||||
|
type: commitType.NORMAL,
|
||||||
|
tags: [],
|
||||||
|
parents: ['ZERO'],
|
||||||
|
branch: 'feature',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commitB',
|
||||||
|
{
|
||||||
|
id: 'B',
|
||||||
|
message: '',
|
||||||
|
seq: 2,
|
||||||
|
type: commitType.NORMAL,
|
||||||
|
tags: [],
|
||||||
|
parents: ['A'],
|
||||||
|
branch: 'feature',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commitM',
|
||||||
|
{
|
||||||
|
id: 'M',
|
||||||
|
message: 'merged branch feature into main',
|
||||||
|
seq: 3,
|
||||||
|
type: commitType.MERGE,
|
||||||
|
tags: [],
|
||||||
|
parents: ['ZERO', 'B'],
|
||||||
|
branch: 'main',
|
||||||
|
customId: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commitC',
|
||||||
|
{
|
||||||
|
id: 'C',
|
||||||
|
message: '',
|
||||||
|
seq: 4,
|
||||||
|
type: commitType.NORMAL,
|
||||||
|
tags: [],
|
||||||
|
parents: ['ZERO'],
|
||||||
|
branch: 'release',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commit5_8928ea0',
|
||||||
|
{
|
||||||
|
id: '5-8928ea0',
|
||||||
|
message: 'cherry-picked [object Object] into release',
|
||||||
|
seq: 5,
|
||||||
|
type: commitType.CHERRY_PICK,
|
||||||
|
tags: [],
|
||||||
|
parents: ['C', 'M'],
|
||||||
|
branch: 'release',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commitD',
|
||||||
|
{
|
||||||
|
id: 'D',
|
||||||
|
message: '',
|
||||||
|
seq: 6,
|
||||||
|
type: commitType.NORMAL,
|
||||||
|
tags: [],
|
||||||
|
parents: ['5-8928ea0'],
|
||||||
|
branch: 'release',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'commit7_ed848ba',
|
||||||
|
{
|
||||||
|
id: '7-ed848ba',
|
||||||
|
message: 'cherry-picked [object Object] into release',
|
||||||
|
seq: 7,
|
||||||
|
type: commitType.CHERRY_PICK,
|
||||||
|
tags: [],
|
||||||
|
parents: ['D', 'M'],
|
||||||
|
branch: 'release',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
branchPos.set('main', { pos: 0, index: 0 });
|
||||||
|
branchPos.set('feature', { pos: 107.49609375, index: 1 });
|
||||||
|
branchPos.set('release', { pos: 224.03515625, index: 2 });
|
||||||
|
|
||||||
|
commits.forEach((commit) => {
|
||||||
|
it(`should draw commit ${commit.id}`, () => {
|
||||||
|
const commitPosition = getCommitPosition(commit, 0, false);
|
||||||
|
expect(commitPosition).toBeDefined();
|
||||||
|
});
|
||||||
|
it(`should draw commit ${commit.id} with position`, () => {
|
||||||
|
const commitPosition = getCommitPosition(commit, 0, false);
|
||||||
|
expect(commitPosition.x).toBeDefined();
|
||||||
|
expect(commitPosition.y).toBeDefined();
|
||||||
|
expect(commitPosition.posWithOffset).toBeDefined();
|
||||||
|
}
|
||||||
|
it(`should draw commit ${commit.id} bullet`, () => {
|
||||||
|
const gBullets = svg.append('g').attr('class', 'commit-bullets');
|
||||||
|
const typeClass = getCommitClassType(commit);
|
||||||
|
const branchIndex = branchPos.get(commit.branch)?.index ?? 0;
|
||||||
|
drawCommitBullet(gBullets, commit, commitPosition, typeClass, branchIndex, commit.type);
|
||||||
|
}
|
||||||
|
it(`should draw commit ${commit.id} label`, () => {
|
||||||
|
const gLabels = svg.append('g').attr('class', 'commit-labels');
|
||||||
|
drawCommitLabel(gLabels, commit, commitPosition, 0, gitGraphConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
describe('drawBranches', () => {
|
||||||
|
it('should drawBranches', () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('drawArrows', () => {
|
||||||
|
it('should drawArrows', () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add', () => {
|
||||||
|
commitPos.set('parent1', { x: 1, y: 1 });
|
||||||
|
commitPos.set('parent2', { x: 2, y: 2 });
|
||||||
|
commitPos.set('parent3', { x: 3, y: 3 });
|
||||||
|
dir = 'LR';
|
||||||
|
const parents = ['parent1', 'parent2', 'parent3'];
|
||||||
|
const closestParent = findClosestParent(parents);
|
||||||
|
|
||||||
|
expect(closestParent).toBe('parent3');
|
||||||
|
commitPos.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user