#1252 Adding confuration options and some cleanup, swicth of graphType from git to gitGraph

This commit is contained in:
Knut Sveidqvist 2022-04-07 18:22:06 +02:00
parent 57ac111d05
commit 87b56bdd9a
11 changed files with 254 additions and 444 deletions

View File

@ -9,7 +9,7 @@
<style> <style>
body { body {
/* background: rgb(221, 208, 208); */ /* background: rgb(221, 208, 208); */
background:#111; /* background:#111; */
/* background:#333; */ /* background:#333; */
font-family: 'Arial'; font-family: 'Arial';
} }
@ -43,6 +43,7 @@
</div> </div>
<div class="mermaid" style="width: 100%; height: 20%;"> <div class="mermaid" style="width: 100%; height: 20%;">
%%{init: { "gitGraph": { "showBranches": false, "mainBranchName": "APA" }}}%%
gitGraph gitGraph
commit commit
branch hotfix branch hotfix
@ -101,7 +102,7 @@
// console.error('Mermaid error: ', err); // console.error('Mermaid error: ', err);
}; };
mermaid.initialize({ mermaid.initialize({
theme: 'dark', theme: 'default',
themeVariables: { themeVariables: {
// primaryColor: '#9400D3', // primaryColor: '#9400D3',
// darkMode: false, // darkMode: false,
@ -119,6 +120,7 @@
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}', // themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 1, logLevel: 1,
flowchart: { curve: 'linear', htmlLabels: true }, flowchart: { curve: 'linear', htmlLabels: true },
// gitGraph: { showCommitLabel: false },
// gantt: { axisFormat: '%m/%d/%Y' }, // gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50, showSequenceNumbers: true }, sequence: { actorMargin: 50, showSequenceNumbers: true },
// sequenceDiagram: { actorMargin: 300 } // deprecated // sequenceDiagram: { actorMargin: 300 } // deprecated

View File

@ -756,19 +756,6 @@ available space if not the absolute space required is used.
Default value: true Default value: true
## useMaxWidth
| Parameter | Description | Type | Required | Values |
| ----------- | ----------- | ------- | -------- | ----------- |
| useMaxWidth | See notes | boolean | 4 | true, false |
**Notes:**
When this flag is set the height and width is set to 100% and is then scaling with the
available space if not the absolute space required is used.
Default value: true
## defaultRenderer ## defaultRenderer
| Parameter | Description | Type | Required | Values | | Parameter | Description | Type | Required | Values |

View File

@ -49,7 +49,7 @@ class Diagram {
this.type = utils.detectType(txt, cnf); this.type = utils.detectType(txt, cnf);
log.debug('Type ' + this.type); log.debug('Type ' + this.type);
switch (this.type) { switch (this.type) {
case 'git': case 'gitGraph':
this.parser = gitGraphParser; this.parser = gitGraphParser;
this.parser.parser.yy = gitGraphAst; this.parser.parser.yy = gitGraphAst;
this.db = gitGraphAst; this.db = gitGraphAst;

View File

@ -1,162 +0,0 @@
import { assignWithDepth } from './utils';
import { log } from './logger'; // eslint-disable-line
import theme from './themes';
import config from './defaultConfig';
const handleThemeVariables = (value) => {
return theme[value] ? theme[value].getThemeVariables() : theme.default.getThemeVariables();
};
const manipulators = {
themeVariables: handleThemeVariables,
};
// debugger;
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
config.git.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
export const defaultConfig = Object.freeze(config);
const siteConfig = assignWithDepth({}, defaultConfig);
const currentConfig = assignWithDepth({}, defaultConfig);
/**
* ## setSiteConfig
*
* | Function | Description | Type | Values |
* | ------------- | ------------------------------------- | ----------- | --------------------------------------- |
* | setSiteConfig | Sets the siteConfig to desired values | Put Request | Any Values, except ones in secure array |
*
* **Notes:** Sets the siteConfig. The siteConfig is a protected configuration for repeat use. Calls
* to reset() will reset the currentConfig to siteConfig. Calls to reset(configApi.defaultConfig)
* will reset siteConfig and currentConfig to the defaultConfig Note: currentConfig is set in this
* function Default value: At default, will mirror Global Config
*
* @param {any} conf - The base currentConfig to use as siteConfig
* @returns {any} - The siteConfig
*/
export const setSiteConfig = (conf) => {
console.log('setSiteConfig');
Object.keys(conf).forEach((key) => {
const manipulator = manipulators[key];
conf[key] = manipulator ? manipulator(conf[key]) : conf[key];
});
assignWithDepth(currentConfig, conf, { clobber: true });
// Set theme variables if user has set the theme option
assignWithDepth(siteConfig, conf);
return getSiteConfig();
};
/**
* ## getSiteConfig
*
* | Function | Description | Type | Values |
* | ------------- | ------------------------------------------------- | ----------- | -------------------------------- |
* | setSiteConfig | Returns the current siteConfig base configuration | Get Request | Returns Any Values in siteConfig |
*
* **Notes**: Returns **any** values in siteConfig.
*
* @returns {any}
*/
export const getSiteConfig = () => {
return assignWithDepth({}, siteConfig);
};
/**
* ## setConfig
*
* | Function | Description | Type | Values |
* | ------------- | ------------------------------------- | ----------- | --------------------------------------- |
* | setSiteConfig | Sets the siteConfig to desired values | Put Request | Any Values, except ones in secure array |
*
* **Notes**: Sets the currentConfig. The parameter conf is sanitized based on the siteConfig.secure
* keys. Any values found in conf with key found in siteConfig.secure will be replaced with the
* corresponding siteConfig value.
*
* @param {any} conf - The potential currentConfig
* @returns {any} - The currentConfig merged with the sanitized conf
*/
export const setConfig = (conf) => {
console.log('setConfig');
sanitize(conf);
Object.keys(conf).forEach((key) => {
const manipulator = manipulators[key];
conf[key] = manipulator ? manipulator(conf[key]) : conf[key];
});
assignWithDepth(currentConfig, conf);
return getConfig();
};
/**
* ## getConfig
*
* | Function | Description | Type | Return Values |
* | --------- | ------------------------- | ----------- | ----------------------------- |
* | getConfig | Obtains the currentConfig | Get Request | Any Values from currentConfig |
*
* **Notes**: Returns **any** the currentConfig
*
* @returns {any} - The currentConfig
*/
export const getConfig = () => {
return assignWithDepth({}, currentConfig);
};
/**
* ## sanitize
*
* | Function | Description | Type | Values |
* | -------- | -------------------------------------- | ----------- | ------ |
* | sanitize | Sets the siteConfig to desired values. | Put Request | None |
*
* Ensures options parameter does not attempt to override siteConfig secure keys **Notes**: modifies
* options in-place
*
* @param {any} options - The potential setConfig parameter
*/
export const sanitize = (options) => {
Object.keys(siteConfig.secure).forEach((key) => {
if (typeof options[siteConfig.secure[key]] !== 'undefined') {
// DO NOT attempt to print options[siteConfig.secure[key]] within `${}` as a malicious script
// can exploit the logger's attempt to stringify the value and execute arbitrary code
log.trace(
`Denied attempt to modify a secure key ${siteConfig.secure[key]}`,
options[siteConfig.secure[key]]
);
delete options[siteConfig.secure[key]];
}
});
};
/**
* ## reset
*
* | Function | Description | Type | Required | Values |
* | -------- | ---------------------------- | ----------- | -------- | ------ |
* | reset | Resets currentConfig to conf | Put Request | Required | None |
*
* | Parameter | Description |Type | Required | Values|
*
* | --- | --- | --- | --- | --- |
* | conf| base set of values, which currentConfig coul be **reset** to.| Dictionary | Required | Any Values, with respect to the secure Array|
*
* **Notes**: (default: current siteConfig ) (optional, default `getSiteConfig()`)
*
* @param {any} conf - The base currentConfig to reset to (default: current siteConfig )
*/
export const reset = (conf = getSiteConfig()) => {
console.warn('reset');
Object.keys(siteConfig).forEach((key) => delete siteConfig[key]);
Object.keys(currentConfig).forEach((key) => delete currentConfig[key]);
assignWithDepth(siteConfig, conf, { clobber: true });
assignWithDepth(currentConfig, conf, { clobber: true });
};
const configApi = Object.freeze({
sanitize,
setSiteConfig,
getSiteConfig,
setConfig,
getConfig,
reset,
defaultConfig,
});
export default configApi;

View File

@ -844,25 +844,6 @@ const config = {
*/ */
defaultRenderer: 'dagre-wrapper', defaultRenderer: 'dagre-wrapper',
}, },
git: {
arrowMarkerAbsolute: false,
useWidth: undefined,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
* | useMaxWidth | See notes | boolean | 4 | true, false |
*
* **Notes:**
*
* When this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used.
*
* Default value: true
*/
useMaxWidth: true,
},
state: { state: {
dividerMargin: 10, dividerMargin: 10,
sizeUnit: 5, sizeUnit: 5,
@ -1066,27 +1047,20 @@ const config = {
}, },
gitGraph: { gitGraph: {
diagramPadding: 8, diagramPadding: 8,
nodeSpacing: 150,
nodeFillColor: 'yellow',
nodeStrokeWidth: 2,
nodeStrokeColor: 'grey',
lineStrokeWidth: 4,
branchOffset: 50,
lineColor: 'grey',
leftMargin: 50,
branchColors: ['#442f74', '#983351', '#609732', '#AA9A39'],
nodeRadius: 10,
nodeLabel: { nodeLabel: {
width: 75, width: 75,
height: 100, height: 100,
x: -25, x: -25,
y: 0, y: 0,
}, },
mainBranchName: 'main',
showCommitLabel: true,
showBranches: true,
}, },
}; };
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute; config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
config.git.arrowMarkerAbsolute = config.arrowMarkerAbsolute; config.gitGraph.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
const keyify = (obj, prefix = '') => const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => { Object.keys(obj).reduce((res, el) => {

View File

@ -220,7 +220,6 @@ export const merge = function (otherBranch) {
export const checkout = function (branch) { export const checkout = function (branch) {
branch = common.sanitizeText(branch, configApi.getConfig()); branch = common.sanitizeText(branch, configApi.getConfig());
console.info(branches);
if (typeof branches[branch] === 'undefined') { if (typeof branches[branch] === 'undefined') {
let error = new Error( let error = new Error(
'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")' 'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")'
@ -238,9 +237,6 @@ export const checkout = function (branch) {
} else { } else {
curBranch = branch; curBranch = branch;
const id = branches[curBranch]; const id = branches[curBranch];
console.log(id);
console.log('hi');
console.log(commits);
head = commits[id]; head = commits[id];
} }
}; };

View File

@ -1,18 +1,12 @@
/* eslint-disable */
import { curveBasis, line, select } from 'd3'; import { curveBasis, line, select } from 'd3';
import { interpolateToCurve, getStylesFromArray, configureSvgSize } from '../../utils'; import { interpolateToCurve, getStylesFromArray, configureSvgSize } from '../../utils';
import db from './gitGraphAst'; import db from './gitGraphAst';
//import * as db from './mockDb';
import gitGraphParser from './parser/gitGraph'; import gitGraphParser from './parser/gitGraph';
import { log } from '../../logger'; import { log } from '../../logger';
/* eslint-disable */
import { getConfig } from '../../config'; import { getConfig } from '../../config';
//import * as configApi from '../../config';
let allCommitsDict = {}; let allCommitsDict = {};
let branchNum; let branchNum;
//let conf = configApi.getConfig();
//const commitType = db.commitType;
const commitType = { const commitType = {
NORMAL: 0, NORMAL: 0,
REVERSE: 1, REVERSE: 1,
@ -29,89 +23,55 @@ const clear = () => {
commitPos = {}; commitPos = {};
allCommitsDict = {}; allCommitsDict = {};
maxPos = 0; maxPos = 0;
lanes = [] lanes = [];
}; };
// let apiConfig = {};
// export const setConf = function(c) {
// apiConfig = c;
// };
/** @param svg */
function svgCreateDefs(svg) {
const config = getConfig().gitGraph;
svg
.append('defs')
.append('g')
.attr('id', 'def-commit')
.append('circle')
.attr('r', config.nodeRadius)
.attr('cx', 0)
.attr('cy', 0);
svg
.select('#def-commit')
.append('foreignObject')
.attr('width', config.nodeLabel.width)
.attr('height', config.nodeLabel.height)
.attr('x', config.nodeLabel.x)
.attr('y', config.nodeLabel.y)
.attr('class', 'node-label')
.attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility')
.append('p')
.html('');
}
/** /**
* @param svg * Draws a text, used for labels of the branches
* @param points
* @param colorIdx
* @param interpolate
*/
/**
// Pass in the element and its pre-transform coords
* *
* @param element * @param {string} txt The text
* @param coords * @returns {SVGElement}
*/ */
/**
* @param svg
* @param fromId
* @param toId
* @param direction
* @param color
*/
const drawText = (txt) => { const drawText = (txt) => {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
// svgLabel.setAttribute('style', style.replace('color:', 'fill:')); let rows = [];
let rows = [];
if (typeof txt === 'string') { // Handling of new lines in the label
rows = txt.split(/\\n|\n|<br\s*\/?>/gi); if (typeof txt === 'string') {
} else if (Array.isArray(txt)) { rows = txt.split(/\\n|\n|<br\s*\/?>/gi);
rows = txt; } else if (Array.isArray(txt)) {
} else { rows = txt;
rows = []; } else {
} rows = [];
for (let j = 0; j < rows.length; j++) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '0');
tspan.setAttribute('class', 'row');
tspan.textContent = rows[j].trim();
svgLabel.appendChild(tspan);
} }
/**
* @param svg for (let j = 0; j < rows.length; j++) {
* @param selector const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
*/ tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '0');
tspan.setAttribute('class', 'row');
tspan.textContent = rows[j].trim();
svgLabel.appendChild(tspan);
}
/**
* @param svg
* @param selector
*/
return svgLabel; return svgLabel;
} };
/**
* Draws the commits with its symbol and labels. The function has tywo modes, one which only
* calculates the positions and one that does the actual drawing. This for a simple way getting the
* vertical leyering rcorrect in the graph.
*
* @param {any} svg
* @param {any} commits
* @param {any} modifyGraph
*/
const drawCommits = (svg, commits, modifyGraph) => { const drawCommits = (svg, commits, modifyGraph) => {
const gitGraphConfig = getConfig().gitGraph;
const gBullets = svg.append('g').attr('class', 'commit-bullets'); const gBullets = svg.append('g').attr('class', 'commit-bullets');
const gLabels = svg.append('g').attr('class', 'commit-labels'); const gLabels = svg.append('g').attr('class', 'commit-labels');
let pos = 0; let pos = 0;
@ -119,20 +79,20 @@ const drawCommits = (svg, commits, modifyGraph) => {
const keys = Object.keys(commits); const keys = Object.keys(commits);
const sortedKeys = keys.sort((a, b) => { const sortedKeys = keys.sort((a, b) => {
return commits[a].seq - commits[b].seq; return commits[a].seq - commits[b].seq;
}) });
sortedKeys.forEach((key, index) => { sortedKeys.forEach((key, index) => {
const commit = commits[key]; const commit = commits[key];
const y = branchPos[commit.branch].pos; const y = branchPos[commit.branch].pos;
const x = pos + 10; const x = pos + 10;
// Don't draw the commits now but calculate the positioning which is used by the branmch lines etc. // Don't draw the commits now but calculate the positioning which is used by the branch lines etc.
if (modifyGraph) { if (modifyGraph) {
let typeClass; let typeClass;
switch(commit.type) { switch (commit.type) {
case commitType.NORMAL: case commitType.NORMAL:
typeClass = 'commit-normal'; typeClass = 'commit-normal';
break; break;
case commitType.REVERSE: case commitType.REVERSE:
typeClass = 'commit-reverse'; typeClass = 'commit-reverse';
break; break;
case commitType.HIGHLIGHT: case commitType.HIGHLIGHT:
@ -147,51 +107,77 @@ const drawCommits = (svg, commits, modifyGraph) => {
if (commit.type === commitType.HIGHLIGHT) { if (commit.type === commitType.HIGHLIGHT) {
const circle = gBullets.append('rect'); const circle = gBullets.append('rect');
circle.attr('x', x-10); circle.attr('x', x - 10);
circle.attr('y', y-10); circle.attr('y', y - 10);
circle.attr('height', 20); circle.attr('height', 20);
circle.attr('width', 20); circle.attr('width', 20);
circle.attr('class', 'commit ' + commit.id + ' commit-highlight' + branchPos[commit.branch].index + ' ' + typeClass+'-outer'); circle.attr(
gBullets.append('rect') 'class',
.attr('x', x-6) 'commit ' +
.attr('y', y-6) commit.id +
.attr('height', 12) ' commit-highlight' +
.attr('width', 12) branchPos[commit.branch].index +
.attr('class', 'commit ' + commit.id + ' commit' + branchPos[commit.branch].index + ' ' + typeClass+'-inner'); ' ' +
typeClass +
'-outer'
);
gBullets
.append('rect')
.attr('x', x - 6)
.attr('y', y - 6)
.attr('height', 12)
.attr('width', 12)
.attr(
'class',
'commit ' +
commit.id +
' commit' +
branchPos[commit.branch].index +
' ' +
typeClass +
'-inner'
);
} else { } else {
const circle = gBullets.append('circle'); const circle = gBullets.append('circle');
circle.attr('cx', x); circle.attr('cx', x);
circle.attr('cy', y); circle.attr('cy', 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' + branchPos[commit.branch].index); circle.attr('class', 'commit ' + commit.id + ' commit' + branchPos[commit.branch].index);
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', x);
circle2.attr('cy', y); circle2.attr('cy', y);
circle2.attr('r', 6); circle2.attr('r', 6);
circle2.attr('class', 'commit '+typeClass + ' ' + commit.id + ' commit' + branchPos[commit.branch].index); circle2.attr(
'class',
'commit ' + typeClass + ' ' + commit.id + ' commit' + branchPos[commit.branch].index
);
} }
if(commit.type === commitType.REVERSE) { if (commit.type === 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 ${x - 5},${y - 5}L${x + 5},${y + 5}M${x - 5},${y + 5}L${x + 5},${y - 5}`)
.attr('class', 'commit '+typeClass + ' ' + commit.id + ' commit' + branchPos[commit.branch].index); .attr(
'class',
'commit ' + typeClass + ' ' + commit.id + ' commit' + branchPos[commit.branch].index
);
} }
} }
} }
commitPos[commit.id] = {x: pos + 10, y: y}; commitPos[commit.id] = { x: pos + 10, y: y };
// The first iteration over the commits are for positioning purposes, this // The first iteration over the commits are for positioning purposes, this
// is required for drawing the lines. The circles and labels is drawn after the labels // is required for drawing the lines. The circles and labels is drawn after the labels
// placing them on top of the lines. // placing them on top of the lines.
if (modifyGraph) { if (modifyGraph) {
const px=4; const px = 4;
const py=2; const py = 2;
if(commit.type !== commitType.MERGE) { // Draw the commit label
const labelBkg = gLabels.insert('rect') if (commit.type !== commitType.MERGE && gitGraphConfig.showCommitLabel) {
.attr('class', 'commit-label-bkg'); const labelBkg = gLabels.insert('rect').attr('class', 'commit-label-bkg');
const text = gLabels.append('text') const text = gLabels
.append('text')
.attr('x', pos) .attr('x', pos)
.attr('y', y + 25) .attr('y', y + 25)
.attr('class', 'commit-label') .attr('class', 'commit-label')
@ -206,48 +192,52 @@ const drawCommits = (svg, commits, modifyGraph) => {
.attr('height', bbox.height + 2 * py); .attr('height', bbox.height + 2 * py);
text.attr('x', pos + 10 - bbox.width / 2); text.attr('x', pos + 10 - bbox.width / 2);
} }
if(commit.tag) { if (commit.tag) {
const rect = gLabels.insert('polygon'); const rect = gLabels.insert('polygon');
const hole = gLabels.append('circle'); const hole = gLabels.append('circle');
const tag = gLabels.append('text') const tag = gLabels
// Note that we are delaying setting the x position until we know the width of the text .append('text')
.attr('y', y - 16) // Note that we are delaying setting the x position until we know the width of the text
.attr('class', 'tag-label') .attr('y', y - 16)
.text(commit.tag); .attr('class', 'tag-label')
.text(commit.tag);
let tagBbox = tag.node().getBBox(); let tagBbox = tag.node().getBBox();
tag.attr('x', pos + 10 - tagBbox.width / 2); tag.attr('x', pos + 10 - tagBbox.width / 2);
const h2 = tagBbox.height/2 const h2 = tagBbox.height / 2;
const ly = y - 19.2 ; const ly = y - 19.2;
rect rect.attr('class', 'tag-label-bkg').attr(
.attr('class', 'tag-label-bkg') 'points',
.attr('points', ` `
${pos - tagBbox.width / 2 - px/2},${ly + py} ${pos - tagBbox.width / 2 - px / 2},${ly + py}
${pos - tagBbox.width / 2 - px/2},${ly - py} ${pos - tagBbox.width / 2 - px / 2},${ly - py}
${pos + 10 - tagBbox.width / 2 - px},${ly - h2 - py} ${pos + 10 - tagBbox.width / 2 - px},${ly - h2 - py}
${pos + 10 + tagBbox.width / 2 + px},${ly - h2 - py} ${pos + 10 + tagBbox.width / 2 + px},${ly - h2 - py}
${pos + 10 + tagBbox.width / 2 + px},${ly + h2 + py } ${pos + 10 + tagBbox.width / 2 + px},${ly + h2 + py}
${pos + 10 - tagBbox.width / 2 - px},${ly + h2 + py}`); ${pos + 10 - tagBbox.width / 2 - px},${ly + h2 + py}`
);
hole hole
.attr('cx', pos - tagBbox.width / 2 + px/2) .attr('cx', pos - tagBbox.width / 2 + px / 2)
.attr('cy', ly) .attr('cy', ly)
.attr('r', 1.5) .attr('r', 1.5)
.attr('class', 'tag-hole'); .attr('class', 'tag-hole');
} }
} }
pos +=50; pos += 50;
if(pos>maxPos) { if (pos > maxPos) {
maxPos = pos; maxPos = pos;
} }
}); });
} };
/** /**
* Detect if there are other commits between commit1s x-position and commit2s x-position on the same branch as commit2. * Detect if there are other commits between commit1s x-position and commit2s x-position on the same
* @param {*} commit1 * branch as commit2.
* @param {*} commit2 *
* @param {any} commit1
* @param {any} commit2
* @param allCommits
* @returns * @returns
*/ */
const hasOverlappingCommits = (commit1, commit2, allCommits) => { const hasOverlappingCommits = (commit1, commit2, allCommits) => {
@ -257,43 +247,62 @@ const hasOverlappingCommits = (commit1, commit2, allCommits) => {
// Find commits on the same branch as commit2 // Find commits on the same branch as commit2
const keys = Object.keys(allCommits); const keys = Object.keys(allCommits);
const overlappingComits = keys.filter((key) => { const overlappingComits = keys.filter((key) => {
return allCommits[key].branch === commit2.branch && allCommits[key].seq > commit1.seq && allCommits[key].seq < commit2.seq return (
allCommits[key].branch === commit2.branch &&
allCommits[key].seq > commit1.seq &&
allCommits[key].seq < commit2.seq
);
}); });
return overlappingComits.length > 0; return overlappingComits.length > 0;
} };
/** /**
* This function find a lane in the y-axis that is not overlapping with any other lanes. This is
* used for drawing the lines between commits.
* *
* @param {any} y1
* @param {any} y2
* @param {any} _depth
* @returns
*/ */
const findLane = (y1, y2, _depth) => { const findLane = (y1, y2, _depth) => {
const depth = _depth || 0; const depth = _depth || 0;
const candidate = y1 + Math.abs(y1 - y2) / 2; const candidate = y1 + Math.abs(y1 - y2) / 2;
if(depth > 5) { if (depth > 5) {
return candidate; return candidate;
} }
let ok = true; let ok = true;
for(let i = 0; i < lanes.length; i++) { for (let i = 0; i < lanes.length; i++) {
if(Math.abs(lanes[i] - candidate) < 10) { if (Math.abs(lanes[i] - candidate) < 10) {
ok = false; ok = false;
} }
} }
if(ok) { if (ok) {
lanes.push(candidate); lanes.push(candidate);
return candidate; return candidate;
} }
const diff = Math.abs(y1 - y2); const diff = Math.abs(y1 - y2);
return findLane(y1, y2-(diff/5), depth); return findLane(y1, y2 - diff / 5, depth);
} };
/**
* This function draw trhe lines between the commits. They were arrows initially.
*
* @param {any} svg
* @param {any} commit1
* @param {any} commit2
* @param {any} allCommits
*/
const drawArrow = (svg, commit1, commit2, allCommits) => { const drawArrow = (svg, commit1, commit2, allCommits) => {
const conf = getConfig(); const conf = getConfig();
const p1 = commitPos[commit1.id]; const p1 = commitPos[commit1.id];
const p2 = commitPos[commit2.id]; const p2 = commitPos[commit2.id];
const overlappingCommits = hasOverlappingCommits(commit1, commit2, allCommits); const overlappingCommits = hasOverlappingCommits(commit1, commit2, allCommits);
log.debug('drawArrow', p1, p2, overlappingCommits, commit1.id, commit2.id); // log.debug('drawArrow', p1, p2, overlappingCommits, commit1.id, commit2.id);
let url = ''; let url = '';
if (conf.arrowMarkerAbsolute) { if (conf.arrowMarkerAbsolute) {
@ -310,28 +319,30 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
let arc = ''; let arc = '';
let arc2 = ''; let arc2 = '';
let radius = 0; let radius = 0;
let offset = 0 let offset = 0;
let colorClassNum = branchPos[commit2.branch].index let colorClassNum = branchPos[commit2.branch].index;
let lineDef; let lineDef;
if(overlappingCommits) { if (overlappingCommits) {
arc = 'A 10 10, 0, 0, 0,'; arc = 'A 10 10, 0, 0, 0,';
arc2 = 'A 10 10, 0, 0, 1,'; arc2 = 'A 10 10, 0, 0, 1,';
radius = 10; radius = 10;
offset = 10; offset = 10;
// Figure out the color of the arrow,arrows going down take the color from the destination branch // Figure out the color of the arrow,arrows going down take the color from the destination branch
colorClassNum = branchPos[commit2.branch].index; colorClassNum = branchPos[commit2.branch].index;
const lineY = p1.y < p2.y ? findLane(p1.y, p2.y):findLane(p2.y, p1.y); const lineY = p1.y < p2.y ? findLane(p1.y, p2.y) : findLane(p2.y, p1.y);
if(p1.y < p2.y) {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY-radius} ${arc} ${p1.x + offset} ${lineY} L ${p2.x-radius} ${lineY} ${arc2} ${p2.x} ${lineY+offset} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY+radius} ${arc2} ${p1.x + offset} ${lineY} L ${p2.x-radius} ${lineY} ${arc} ${p2.x} ${lineY-offset} L ${p2.x} ${p2.y}`;
}
if (p1.y < p2.y) {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY - radius} ${arc} ${p1.x + offset} ${lineY} L ${
p2.x - radius
} ${lineY} ${arc2} ${p2.x} ${lineY + offset} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY + radius} ${arc2} ${
p1.x + offset
} ${lineY} L ${p2.x - radius} ${lineY} ${arc} ${p2.x} ${lineY - offset} L ${p2.x} ${p2.y}`;
}
} else { } else {
if (p1.y < p2.y) {
if(p1.y < p2.y) {
arc = 'A 20 20, 0, 0, 0,'; arc = 'A 20 20, 0, 0, 0,';
radius = 20; radius = 20;
offset = 20; offset = 20;
@ -339,26 +350,34 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
// Figure out the color of the arrow,arrows going down take the color from the destination branch // Figure out the color of the arrow,arrows going down take the color from the destination branch
colorClassNum = branchPos[commit2.branch].index; colorClassNum = branchPos[commit2.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y-radius} ${arc} ${p1.x + offset} ${p2.y} L ${p2.x} ${p2.y}`; lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${
p2.x
} ${p2.y}`;
} }
if(p1.y > p2.y) { if (p1.y > p2.y) {
arc = 'A 20 20, 0, 0, 0,'; arc = 'A 20 20, 0, 0, 0,';
radius = 20; radius = 20;
offset = 20; offset = 20;
// Arrows going up take the color from the source branch // Arrows going up take the color from the source branch
colorClassNum = branchPos[commit1.branch].index; colorClassNum = branchPos[commit1.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p2.x-radius} ${p1.y} ${arc} ${p2.x} ${p1.y-offset} L ${p2.x} ${p2.y}`; lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${p1.y - offset} L ${
p2.x
} ${p2.y}`;
} }
if(p1.y === p2.y) { if (p1.y === p2.y) {
colorClassNum = branchPos[commit1.branch].index colorClassNum = branchPos[commit1.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y-radius} ${arc} ${p1.x + offset} ${p2.y} L ${p2.x} ${p2.y}`; lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${
p2.x
} ${p2.y}`;
} }
} }
const arrow = svg.append('path').attr('d', lineDef) const arrow = svg
.attr('class', 'arrow arrow' + colorClassNum) .append('path')
} .attr('d', lineDef)
.attr('class', 'arrow arrow' + colorClassNum);
};
const drawArrows = (svg, commits) => { const drawArrows = (svg, commits) => {
const gArrows = svg.append('g').attr('class', 'commit-arrows'); const gArrows = svg.append('g').attr('class', 'commit-arrows');
@ -367,123 +386,114 @@ const drawArrows = (svg, commits) => {
const k = Object.keys(commits); const k = Object.keys(commits);
k.forEach((key, index) => { k.forEach((key, index) => {
const commit = commits[key]; const commit = commits[key];
if(commit.parents && commit.parents.length>0) { if (commit.parents && commit.parents.length > 0) {
commit.parents.forEach((parent) => { commit.parents.forEach((parent) => {
drawArrow(gArrows, commits[parent], commit, commits); drawArrow(gArrows, commits[parent], commit, commits);
}); });
} }
}); });
} };
/** /**
* This function adds the branches and the branches' labels to the svg.
*
* @param svg * @param svg
* @param commitid * @param commitid
* @param branches * @param branches
* @param direction * @param direction
*/ */
const drawBranches = (svg, branches) => { const drawBranches = (svg, branches) => {
const g = svg.append('g') const gitGraphConfig = getConfig().gitGraph;
branches.forEach((branch, index) => { const g = svg.append('g');
const pos = branchPos[branch.name].pos; branches.forEach((branch, index) => {
const line = g.append('line'); const pos = branchPos[branch.name].pos;
line.attr('x1', 0); const line = g.append('line');
line.attr('y1', pos); line.attr('x1', 0);
line.attr('x2', maxPos); line.attr('y1', pos);
line.attr('y2', pos); line.attr('x2', maxPos);
line.attr('class', 'branch branch'+index) line.attr('y2', pos);
line.attr('class', 'branch branch' + index);
lanes.push(pos); lanes.push(pos);
// Create the actual text element let name = index === 0 ? gitGraphConfig.mainBranchName : branch.name;
const labelElement = drawText(branch.name);
// Create outer g, edgeLabel, this will be positioned after graph layout
const bkg = g.insert('rect');
const branchLabel = g.insert('g').attr('class', 'branchLabel');
// Create inner g, label, this will be positioned now for centering the text // Create the actual text element
const label = branchLabel.insert('g').attr('class', 'label branch-label'+index); const labelElement = drawText(name);
label.node().appendChild(labelElement); // Create outer g, edgeLabel, this will be positioned after graph layout
let bbox = labelElement.getBBox(); const bkg = g.insert('rect');
bkg.attr('class', 'branchLabelBkg label' + index) const branchLabel = g.insert('g').attr('class', 'branchLabel');
.attr('rx', 4)
.attr('ry', 4)
.attr('x', -bbox.width -4)
.attr('y', -bbox.height / 2 +8 )
.attr('width', bbox.width + 18)
.attr('height', bbox.height + 4);
label.attr('transform', 'translate(' + (-bbox.width -14) + ', ' + (pos - bbox.height/2-1) + ')'); // Create inner g, label, this will be positioned now for centering the text
bkg.attr('transform', 'translate(' + -19 + ', ' + (pos - bbox.height/2) + ')'); const label = branchLabel.insert('g').attr('class', 'label branch-label' + index);
}) label.node().appendChild(labelElement);
let bbox = labelElement.getBBox();
bkg
.attr('class', 'branchLabelBkg label' + index)
.attr('rx', 4)
.attr('ry', 4)
.attr('x', -bbox.width - 4)
.attr('y', -bbox.height / 2 + 8)
.attr('width', bbox.width + 18)
.attr('height', bbox.height + 4);
} label.attr(
'transform',
'translate(' + (-bbox.width - 14) + ', ' + (pos - bbox.height / 2 - 1) + ')'
);
bkg.attr('transform', 'translate(' + -19 + ', ' + (pos - bbox.height / 2) + ')');
});
};
/** /**
* @param svg * @param svg
* @param commit * @param commit
* @param direction * @param direction
* @param branchColor * @param branchColor
* @param txt
* @param id
* @param ver
*/ */
export const draw = function (txt, id, ver) { export const draw = function (txt, id, ver) {
clear(); clear();
const conf = getConfig(); const conf = getConfig();
const config = conf.gitGraph; const gitGraphConfig = getConfig().gitGraph;
// try { // try {
const parser = gitGraphParser.parser; const parser = gitGraphParser.parser;
parser.yy = db; parser.yy = db;
parser.yy.clear(); parser.yy.clear();
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver); log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
// // Parse the graph definition // // Parse the graph definition
parser.parse(txt + '\n'); parser.parse(txt + '\n');
// config = Object.assign(config, apiConfig, db.getOptions());
const direction = db.getDirection(); const direction = db.getDirection();
allCommitsDict = db.getCommits(); allCommitsDict = db.getCommits();
const branches = db.getBranchesAsObjArray(); const branches = db.getBranchesAsObjArray();
// Position branches vertically // Position branches vertically
let pos=0; let pos = 0;
branches.forEach((branch, index) => { branches.forEach((branch, index) => {
branchPos[branch.name] = {pos, index}; branchPos[branch.name] = { pos, index };
pos+=50; pos += 50;
}); });
log.debug('brach pos ', branchPos);
log.debug('effective options', config, branches);
log.debug('commits', allCommitsDict);
const diagram = select(`[id="${id}"]`); const diagram = select(`[id="${id}"]`);
svgCreateDefs(diagram);
diagram
.append('defs')
.append('marker')
.attr('id', 'arrowhead')
.attr('refX',24)
.attr('refY', 10)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 24)
.attr('markerHeight', 24)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 20 10 L 0 20 z'); // this is actual shape for arrowhead
drawCommits(diagram, allCommitsDict, false); drawCommits(diagram, allCommitsDict, false);
drawBranches(diagram, branches); if (gitGraphConfig.showBranches) {
drawBranches(diagram, branches);
}
drawArrows(diagram, allCommitsDict); drawArrows(diagram, allCommitsDict);
drawCommits(diagram, allCommitsDict, true); drawCommits(diagram, allCommitsDict, true);
const padding = config.diagramPadding; const padding = gitGraphConfig.diagramPadding;
const svgBounds = diagram.node().getBBox(); const svgBounds = diagram.node().getBBox();
const width = svgBounds.width + padding * 2; const width = svgBounds.width + padding * 2;
const height = svgBounds.height + padding * 2; const height = svgBounds.height + padding * 2;
configureSvgSize(diagram, height, width, conf.useMaxWidth); configureSvgSize(diagram, height, width, conf.useMaxWidth);
const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`;
// logger.debug(`viewBox ${vBox}`);
diagram.attr('viewBox', vBox); diagram.attr('viewBox', vBox);
}; };

View File

@ -72,14 +72,14 @@ function parse(text) {
const graphInit = utils.detectInit(text, cnf); const graphInit = utils.detectInit(text, cnf);
if (graphInit) { if (graphInit) {
reinitialize(graphInit); reinitialize(graphInit);
log.debug('reinit ', graphInit); log.info('reinit ', graphInit);
} }
const graphType = utils.detectType(text, cnf); const graphType = utils.detectType(text, cnf);
let parser; let parser;
log.debug('Type ' + graphType); log.debug('Type ' + graphType);
switch (graphType) { switch (graphType) {
case 'git': case 'gitGraph':
parser = gitGraphParser; parser = gitGraphParser;
parser.parser.yy = gitGraphAst; parser.parser.yy = gitGraphAst;
break; break;
@ -229,6 +229,9 @@ const render = function (id, _txt, cb, container) {
configApi.addDirective(graphInit); configApi.addDirective(graphInit);
} }
let cnf = configApi.getConfig(); let cnf = configApi.getConfig();
log.debug(cnf);
// Check the maximum allowed text size // Check the maximum allowed text size
if (_txt.length > cnf.maxTextSize) { if (_txt.length > cnf.maxTextSize) {
txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa'; txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
@ -422,7 +425,7 @@ const render = function (id, _txt, cb, container) {
try { try {
switch (graphType) { switch (graphType) {
case 'git': case 'gitGraph':
// cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; // cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
//gitGraphRenderer.setConf(cnf.git); //gitGraphRenderer.setConf(cnf.git);
gitGraphRenderer.draw(txt, id, false); gitGraphRenderer.draw(txt, id, false);

View File

@ -2,7 +2,7 @@ import classDiagram from './diagrams/class/styles';
import er from './diagrams/er/styles'; import er from './diagrams/er/styles';
import flowchart from './diagrams/flowchart/styles'; import flowchart from './diagrams/flowchart/styles';
import gantt from './diagrams/gantt/styles'; import gantt from './diagrams/gantt/styles';
import git from './diagrams/git/styles'; import gitGraph from './diagrams/git/styles';
import info from './diagrams/info/styles'; import info from './diagrams/info/styles';
import pie from './diagrams/pie/styles'; import pie from './diagrams/pie/styles';
import requirement from './diagrams/requirement/styles'; import requirement from './diagrams/requirement/styles';
@ -20,7 +20,7 @@ const themes = {
class: classDiagram, class: classDiagram,
stateDiagram, stateDiagram,
state: stateDiagram, state: stateDiagram,
git, gitGraph,
info, info,
pie, pie,
er, er,

View File

@ -210,7 +210,7 @@ export const detectType = function (text, cnf) {
} }
if (text.match(/^\s*gitGraph/)) { if (text.match(/^\s*gitGraph/)) {
return 'git'; return 'gitGraph';
} }
if (text.match(/^\s*flowchart/)) { if (text.match(/^\s*flowchart/)) {
return 'flowchart-v2'; return 'flowchart-v2';

View File

@ -214,7 +214,7 @@ Alice->Bob: hi`;
it('should handle a graph definition for gitGraph', function () { it('should handle a graph definition for gitGraph', function () {
const str = ' \n gitGraph TB:\nbfs1:queue'; const str = ' \n gitGraph TB:\nbfs1:queue';
const type = utils.detectType(str); const type = utils.detectType(str);
expect(type).toBe('git'); expect(type).toBe('gitGraph');
}); });
}); });
describe('when finding substring in array ', function () { describe('when finding substring in array ', function () {