made draw commit more readable, included more helper functions and interfaces, added in-source test suite to renderer

This commit is contained in:
Austin Fulbright
2024-08-10 06:08:57 -04:00
parent 2218929416
commit a93b8324ad

View File

@@ -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,45 +441,50 @@ 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 = findClosestParent(commit.parents);
const closestParent = if (closestParent) {
dir === 'BT' ? findClosestParent(commit.parents) : findClosestParent(commit.parents); const parentPosition = commitPos.get(closestParent) ?? defaultCommitPosition;
// Check if closestParent is defined
if (closestParent) {
const parentPosition = commitPos.get(closestParent) ?? defaultCommitPosition;
if (dir === 'TB') {
return parentPosition.y + commitStep;
} else if (dir === 'BT') {
const currentPosition = commitPos.get(commit.id) ?? defaultCommitPosition;
return currentPosition.y - commitStep;
} else {
return parentPosition.x + commitStep;
}
}
} else {
if (dir === 'TB') { if (dir === 'TB') {
return defaultPos; 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 0; return parentPosition.x + COMMIT_STEP;
} }
} }
} else {
if (dir === 'TB') {
return defaultPos;
} else if (dir === 'BT') {
const currentPosition = commitPos.get(commit.id) ?? defaultCommitPosition;
return currentPosition.y - COMMIT_STEP;
} else {
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,40 +521,29 @@ 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}`);
}
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.
if (modifyGraph) {
const typeClass = getCommitClassType(commit);
const commitSymbolType = commit.customType ?? commit.type;
const branchIndex = branchPos.get(commit.branch)?.index ?? 0;
drawCommitBullet(gBullets, commit, commitPosition, typeClass, branchIndex, commitSymbolType);
drawCommitLabel(gLabels, commit, commitPosition, pos, gitGraphConfig);
drawCommitTags(gLabels, commit, commitPosition, pos);
}
if (dir === 'TB' || dir === 'BT') {
commitPos.set(commit.id, { x: commitPosition.x, y: commitPosition.posWithOffset });
} else { } else {
pos = calculatePosition( commitPos.set(commit.id, { x: commitPosition.posWithOffset, y: commitPosition.y });
commit, }
dir, pos = dir === 'BT' && isParallelCommits ? pos + COMMIT_STEP : pos + COMMIT_STEP + LAYOUT_OFFSET;
isParallelCommits, if (pos > maxPos) {
pos, maxPos = 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}`);
}
// Don't draw the commits now but calculate the positioning which is used by the branch lines etc.
if (modifyGraph) {
const typeClass = getCommitClassType(commit);
const commitSymbolType = commit.customType ?? commit.type;
const branchIndex = branchPos.get(commit.branch)?.index ?? 0;
drawCommitBullet(gBullets, commit, x, y, typeClass, branchIndex, commitSymbolType);
drawCommitLabel(gLabels, commit, x, y, pos, posWithOffset, gitGraphConfig);
drawCommitTags(gLabels, commit, x, y, pos, posWithOffset, layoutOffset);
}
if (dir === 'TB' || dir === 'BT') {
commitPos.set(commit.id, { x: x, y: posWithOffset });
} else {
commitPos.set(commit.id, { x: posWithOffset, y: y });
}
pos = dir === 'BT' && isParallelCommits ? pos + commitStep : pos + commitStep + layoutOffset;
if (pos > maxPos) {
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();
});
}