Merge branch 'develop' into next
* develop: (70 commits) build(deps-dev): bump vite from 4.4.9 to 4.4.12 Changes to .prettierignore 1. Added 'demos/dev/**' to be ignored by Prettier. 2. Added '!/demos/dev/example.html' so that Prettier ensures no one changes the example.html in a way that doesn't obey the Prettier code formatting rules. build: use `tsx` instead of `ts-node-esm` chore: Downgrade node to 18.18.2 fix: #5100 Add viewbox to sankey chore(deps): update all minor dependencies chore: Rename test test: Add unit test for generic classname and namespace fix: Check if parentCommit is provided Split type from generic class name Condition of Parent Id Without Merge Commit Added Referenced the PmWiki's Cookbook recipe enabling MermaidJs schematics in wiki pages test(e2e): fix pie chart E2E tests for PR #4288 Add dummy commit to trigger GH checks chore: Revert unnecessary export refactor: Remove unnecessary calculations chore: Fix computeWidth function chore: Cleanup setupGraphViewbox Update docs update mermaidAPI to cleanup the text before passing to getDiagramFromText ...
This commit is contained in:
commit
000b432bb2
|
@ -6,6 +6,6 @@ export default {
|
|||
// https://prettier.io/docs/en/cli.html#--cache
|
||||
'prettier --write',
|
||||
],
|
||||
'cSpell.json': ['ts-node-esm scripts/fixCSpell.ts'],
|
||||
'cSpell.json': ['tsx scripts/fixCSpell.ts'],
|
||||
'**/*.jison': ['pnpm -w run lint:jison'],
|
||||
};
|
||||
|
|
|
@ -10,6 +10,8 @@ stats
|
|||
.nyc_output
|
||||
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
||||
packages/mermaid/src/config.type.ts
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
# Ignore the files creates in /demos/dev except for example.html
|
||||
demos/dev/**
|
||||
!/demos/dev/example.html
|
||||
|
|
|
@ -74,6 +74,9 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||
output,
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'import.meta.vitest': 'undefined',
|
||||
},
|
||||
resolve: {
|
||||
extensions: [],
|
||||
},
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["scripts/docs.cli.mts"],
|
||||
"runtimeArgs": ["--loader", "ts-node/esm"],
|
||||
// we'll need to change this to --import in Node.JS v20.6.0 and up
|
||||
"runtimeArgs": ["--loader", "tsx/esm"],
|
||||
"cwd": "${workspaceRoot}/packages/mermaid",
|
||||
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
|
||||
"smartStep": true,
|
||||
|
|
|
@ -729,6 +729,18 @@ A ~~~ B
|
|||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('5064: Should render when subgraph child has links to outside node and subgraph', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart TB
|
||||
Out --> In
|
||||
subgraph Sub
|
||||
In
|
||||
end
|
||||
Sub --> In`
|
||||
);
|
||||
});
|
||||
|
||||
describe('Markdown strings flowchart (#4220)', () => {
|
||||
describe('html labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
|
@ -874,4 +886,93 @@ end
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('Subgraph title margins', () => {
|
||||
it('Should render subgraphs with title margins set (LR)', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart LR
|
||||
|
||||
subgraph TOP
|
||||
direction TB
|
||||
subgraph B1
|
||||
direction RL
|
||||
i1 -->f1
|
||||
end
|
||||
subgraph B2
|
||||
direction BT
|
||||
i2 -->f2
|
||||
end
|
||||
end
|
||||
A --> TOP --> B
|
||||
B1 --> B2
|
||||
`,
|
||||
{ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
|
||||
);
|
||||
});
|
||||
it('Should render subgraphs with title margins set (TD)', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart TD
|
||||
|
||||
subgraph TOP
|
||||
direction LR
|
||||
subgraph B1
|
||||
direction RL
|
||||
i1 -->f1
|
||||
end
|
||||
subgraph B2
|
||||
direction BT
|
||||
i2 -->f2
|
||||
end
|
||||
end
|
||||
A --> TOP --> B
|
||||
B1 --> B2
|
||||
`,
|
||||
{ flowchart: { subGraphTitleMargin: { top: 8, bottom: 16 } } }
|
||||
);
|
||||
});
|
||||
it('Should render subgraphs with title margins set (LR) and htmlLabels set to false', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart LR
|
||||
|
||||
subgraph TOP
|
||||
direction TB
|
||||
subgraph B1
|
||||
direction RL
|
||||
i1 -->f1
|
||||
end
|
||||
subgraph B2
|
||||
direction BT
|
||||
i2 -->f2
|
||||
end
|
||||
end
|
||||
A --> TOP --> B
|
||||
B1 --> B2
|
||||
`,
|
||||
{
|
||||
htmlLabels: false,
|
||||
flowchart: { htmlLabels: false, subGraphTitleMargin: { top: 10, bottom: 5 } },
|
||||
}
|
||||
);
|
||||
});
|
||||
it('Should render subgraphs with title margins and edge labels', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart LR
|
||||
|
||||
subgraph TOP
|
||||
direction TB
|
||||
subgraph B1
|
||||
direction RL
|
||||
i1 --lb1-->f1
|
||||
end
|
||||
subgraph B2
|
||||
direction BT
|
||||
i2 --lb2-->f2
|
||||
end
|
||||
end
|
||||
A --lb3--> TOP --lb4--> B
|
||||
B1 --lb5--> B2
|
||||
`,
|
||||
{ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -811,4 +811,19 @@ gitGraph TB:
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('40: should render a simple gitgraph with cherry pick merge commit', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
cherry-pick id: "M" parent:"B"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('pie chart', () => {
|
|||
const style = svg.attr('style');
|
||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
||||
expect(maxWidthValue).to.eq(984);
|
||||
expect(maxWidthValue).to.be.within(590, 600); // depends on installed fonts: 596.2 on my PC, 597.5 on CI
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -59,7 +59,7 @@ describe('pie chart', () => {
|
|||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
const width = parseFloat(svg.attr('width'));
|
||||
expect(width).to.eq(984);
|
||||
expect(width).to.be.within(590, 600); // depends on installed fonts: 596.2 on my PC, 597.5 on CI
|
||||
expect(svg).to.not.have.attr('style');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
---
|
||||
config:
|
||||
sankey:
|
||||
useMaxWidth: true
|
||||
showValues: false
|
||||
width: 1200
|
||||
height: 600
|
||||
|
|
|
@ -96,7 +96,7 @@ mermaid.initialize(config);
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:603](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L603)
|
||||
[mermaidAPI.ts:608](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L608)
|
||||
|
||||
## Functions
|
||||
|
||||
|
|
|
@ -111,6 +111,8 @@ Communication tools and platforms
|
|||
|
||||
### Wikis
|
||||
|
||||
- [PmWiki](https://www.pmwiki.org)
|
||||
- [MermaidJs Cookbook recipe](https://www.pmwiki.org/wiki/Cookbook/MermaidJs)
|
||||
- [MediaWiki](https://www.mediawiki.org)
|
||||
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
|
||||
- [Flex Diagrams Extension](https://www.mediawiki.org/wiki/Extension:Flex_Diagrams)
|
||||
|
|
|
@ -366,6 +366,8 @@ A few important rules to note here are:
|
|||
1. You need to provide the `id` for an existing commit to be cherry-picked. If given commit id does not exist it will result in an error. For this, make use of the `commit id:$value` format of declaring commits. See the examples from above.
|
||||
2. The given commit must not exist on the current branch. The cherry-picked commit must always be a different branch than the current branch.
|
||||
3. Current branch must have at least one commit, before you can cherry-pick, otherwise it will cause an error is throw.
|
||||
4. When cherry-picking a merge commit, providing a parent commit ID is mandatory. If the parent attribute is omitted or an invalid parent commit ID is provided, an error will be thrown.
|
||||
5. The specified parent commit must be an immediate parent of the merge commit being cherry-picked.
|
||||
|
||||
Let see an example:
|
||||
|
||||
|
@ -373,14 +375,17 @@ Let see an example:
|
|||
gitGraph
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
branch release
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
merge develop id:"MERGE"
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
checkout release
|
||||
cherry-pick id:"MERGE" parent:"B"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
|
@ -390,14 +395,17 @@ Let see an example:
|
|||
gitGraph
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
branch release
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
merge develop id:"MERGE"
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
checkout release
|
||||
cherry-pick id:"MERGE" parent:"B"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
|
|
22
package.json
22
package.json
|
@ -4,7 +4,7 @@
|
|||
"version": "10.2.4",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@8.10.5",
|
||||
"packageManager": "pnpm@8.11.0",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
|
@ -16,25 +16,25 @@
|
|||
],
|
||||
"scripts": {
|
||||
"build": "pnpm build:esbuild && pnpm build:types",
|
||||
"build:esbuild": "pnpm run -r clean && ts-node-esm --transpileOnly .esbuild/build.ts",
|
||||
"build:esbuild": "pnpm run -r clean && tsx .esbuild/build.ts",
|
||||
"build:mermaid": "pnpm build:esbuild --mermaid",
|
||||
"build:viz": "pnpm build:esbuild --visualize",
|
||||
"build:types": "ts-node-esm --transpileOnly .build/types.ts",
|
||||
"build:types": "tsx .build/types.ts",
|
||||
"build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch",
|
||||
"dev": "ts-node-esm --transpileOnly .esbuild/server.ts",
|
||||
"dev:vite": "ts-node-esm --transpileOnly .vite/server.ts",
|
||||
"dev": "tsx .esbuild/server.ts",
|
||||
"dev:vite": "tsx .vite/server.ts",
|
||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
||||
"release": "pnpm build",
|
||||
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
|
||||
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
|
||||
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
|
||||
"contributors": "ts-node-esm scripts/updateContributors.ts",
|
||||
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && tsx scripts/fixCSpell.ts",
|
||||
"lint:jison": "tsx ./scripts/jison/lint.mts",
|
||||
"contributors": "tsx scripts/updateContributors.ts",
|
||||
"cypress": "cypress run",
|
||||
"cypress:open": "cypress open",
|
||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||
"e2e:coverage": "start-server-and-test dev:coverage http://localhost:9000/ cypress",
|
||||
"coverage:cypress:clean": "rimraf .nyc_output coverage/cypress",
|
||||
"coverage:merge": "ts-node-esm scripts/coverage.ts",
|
||||
"coverage:merge": "tsx scripts/coverage.ts",
|
||||
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
|
||||
"ci": "vitest run",
|
||||
"test": "pnpm lint && vitest run",
|
||||
|
@ -118,9 +118,9 @@
|
|||
"rimraf": "^5.0.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite": "^4.4.12",
|
||||
"vite-plugin-istanbul": "^4.1.0",
|
||||
"vitest": "^0.34.0"
|
||||
},
|
||||
|
|
|
@ -11,6 +11,7 @@ import common from '../../mermaid/src/diagrams/common/common.js';
|
|||
import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils.js';
|
||||
import ELK from 'elkjs/lib/elk.bundled.js';
|
||||
import { getLineFunctionsWithOffset } from '../../mermaid/src/utils/lineWithOffset.js';
|
||||
import { addEdgeMarkers } from '../../mermaid/src/dagre-wrapper/edgeMarker.js';
|
||||
|
||||
const elk = new ELK();
|
||||
|
||||
|
@ -586,108 +587,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
|
|||
}
|
||||
|
||||
// look in edge data and decide which marker to use
|
||||
switch (edgeData.arrowTypeStart) {
|
||||
case 'arrow_cross':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'arrow_point':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'arrow_barb':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'arrow_circle':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'aggregation':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'extension':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'composition':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'dependency':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'lollipop':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
switch (edgeData.arrowTypeEnd) {
|
||||
case 'arrow_cross':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
|
||||
break;
|
||||
case 'arrow_point':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
|
||||
break;
|
||||
case 'arrow_barb':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
|
||||
break;
|
||||
case 'arrow_circle':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
|
||||
break;
|
||||
case 'aggregation':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'extension':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'composition':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'dependency':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'lollipop':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
addEdgeMarkers(svgPath, edgeData, url, id, diagramType);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,18 +26,18 @@
|
|||
"clean": "rimraf dist",
|
||||
"dev": "pnpm -w dev",
|
||||
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaidAPI.ts && prettier --write ./src/docs/config/setup",
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --verify",
|
||||
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts --verify",
|
||||
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && tsx scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"ts-node-esm scripts/docs.cli.mts --watch --vitepress\"",
|
||||
"docs:dev:docker": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev:docker\" \"ts-node-esm scripts/docs.cli.mts --watch --vitepress\"",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"tsx scripts/docs.cli.mts --watch --vitepress\"",
|
||||
"docs:dev:docker": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev:docker\" \"tsx scripts/docs.cli.mts --watch --vitepress\"",
|
||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||
"docs:release-version": "ts-node-esm scripts/update-release-version.mts",
|
||||
"docs:verify-version": "ts-node-esm scripts/update-release-version.mts --verify",
|
||||
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
|
||||
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
|
||||
"docs:release-version": "tsx scripts/update-release-version.mts",
|
||||
"docs:verify-version": "tsx scripts/update-release-version.mts --verify",
|
||||
"types:build-config": "tsx scripts/create-types-from-json-schema.mts",
|
||||
"types:verify-config": "tsx scripts/create-types-from-json-schema.mts --verify",
|
||||
"checkCircle": "npx madge --circular ./src",
|
||||
"release": "pnpm build",
|
||||
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm docs:release-version && pnpm -w run build"
|
||||
|
|
|
@ -1396,6 +1396,14 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
|||
* Margin top for the text over the diagram
|
||||
*/
|
||||
titleTopMargin?: number;
|
||||
/**
|
||||
* Defines a top/bottom margin for subgraph titles
|
||||
*
|
||||
*/
|
||||
subGraphTitleMargin?: {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
};
|
||||
arrowMarkerAbsolute?: boolean;
|
||||
/**
|
||||
* The amount of padding around the diagram as a whole so that embedded
|
||||
|
|
|
@ -5,9 +5,11 @@ import { createText } from '../rendering-util/createText.js';
|
|||
import { select } from 'd3';
|
||||
import { getConfig } from '../diagram-api/diagramAPI.js';
|
||||
import { evaluate } from '../diagrams/common/common.js';
|
||||
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
|
||||
|
||||
const rect = (parent, node) => {
|
||||
log.info('Creating subgraph rect for ', node.id, node);
|
||||
const siteConfig = getConfig();
|
||||
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
|
@ -18,7 +20,7 @@ const rect = (parent, node) => {
|
|||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
|
||||
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
|
||||
|
||||
// Create the label and insert it after the rect
|
||||
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
|
||||
|
@ -34,7 +36,7 @@ const rect = (parent, node) => {
|
|||
// Get the size of the label
|
||||
let bbox = text.getBBox();
|
||||
|
||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||
if (evaluate(siteConfig.flowchart.htmlLabels)) {
|
||||
const div = text.children[0];
|
||||
const dv = select(text);
|
||||
bbox = div.getBoundingClientRect();
|
||||
|
@ -63,17 +65,18 @@ const rect = (parent, node) => {
|
|||
.attr('width', width)
|
||||
.attr('height', node.height + padding);
|
||||
|
||||
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
|
||||
if (useHtmlLabels) {
|
||||
label.attr(
|
||||
'transform',
|
||||
// This puts the labal on top of the box instead of inside it
|
||||
'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')'
|
||||
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
|
||||
);
|
||||
} else {
|
||||
label.attr(
|
||||
'transform',
|
||||
// This puts the labal on top of the box instead of inside it
|
||||
'translate(' + node.x + ', ' + (node.y - node.height / 2) + ')'
|
||||
`translate(${node.x}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
|
||||
);
|
||||
}
|
||||
// Center the label
|
||||
|
@ -127,6 +130,8 @@ const noteGroup = (parent, node) => {
|
|||
return shapeSvg;
|
||||
};
|
||||
const roundedWithTitle = (parent, node) => {
|
||||
const siteConfig = getConfig();
|
||||
|
||||
// Add outer g element
|
||||
const shapeSvg = parent.insert('g').attr('class', node.classes).attr('id', node.id);
|
||||
|
||||
|
@ -143,7 +148,7 @@ const roundedWithTitle = (parent, node) => {
|
|||
|
||||
// Get the size of the label
|
||||
let bbox = text.getBBox();
|
||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||
if (evaluate(siteConfig.flowchart.htmlLabels)) {
|
||||
const div = text.children[0];
|
||||
const dv = select(text);
|
||||
bbox = div.getBoundingClientRect();
|
||||
|
@ -175,6 +180,7 @@ const roundedWithTitle = (parent, node) => {
|
|||
.attr('width', width + padding)
|
||||
.attr('height', node.height + padding - bbox.height - 3);
|
||||
|
||||
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
|
||||
// Center the label
|
||||
label.attr(
|
||||
'transform',
|
||||
|
@ -184,7 +190,8 @@ const roundedWithTitle = (parent, node) => {
|
|||
(node.y -
|
||||
node.height / 2 -
|
||||
node.padding / 3 +
|
||||
(evaluate(getConfig().flowchart.htmlLabels) ? 5 : 3)) +
|
||||
(evaluate(siteConfig.flowchart.htmlLabels) ? 5 : 3)) +
|
||||
subGraphTitleTopMargin +
|
||||
')'
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import type { Mocked } from 'vitest';
|
||||
import type { SVG } from '../diagram-api/types.js';
|
||||
import { addEdgeMarkers } from './edgeMarker.js';
|
||||
|
||||
describe('addEdgeMarker', () => {
|
||||
const svgPath = {
|
||||
attr: vitest.fn(),
|
||||
} as unknown as Mocked<SVG>;
|
||||
const url = 'http://example.com';
|
||||
const id = 'test';
|
||||
const diagramType = 'test';
|
||||
|
||||
beforeEach(() => {
|
||||
svgPath.attr.mockReset();
|
||||
});
|
||||
|
||||
it('should add markers for arrow_cross:arrow_point', () => {
|
||||
const arrowTypeStart = 'arrow_cross';
|
||||
const arrowTypeEnd = 'arrow_point';
|
||||
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-start',
|
||||
`url(${url}#${id}_${diagramType}-crossStart)`
|
||||
);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-end',
|
||||
`url(${url}#${id}_${diagramType}-pointEnd)`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add markers for aggregation:arrow_point', () => {
|
||||
const arrowTypeStart = 'aggregation';
|
||||
const arrowTypeEnd = 'arrow_point';
|
||||
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-start',
|
||||
`url(${url}#${id}_${diagramType}-aggregationStart)`
|
||||
);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-end',
|
||||
`url(${url}#${id}_${diagramType}-pointEnd)`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add markers for arrow_point:aggregation', () => {
|
||||
const arrowTypeStart = 'arrow_point';
|
||||
const arrowTypeEnd = 'aggregation';
|
||||
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-start',
|
||||
`url(${url}#${id}_${diagramType}-pointStart)`
|
||||
);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-end',
|
||||
`url(${url}#${id}_${diagramType}-aggregationEnd)`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add markers for aggregation:composition', () => {
|
||||
const arrowTypeStart = 'aggregation';
|
||||
const arrowTypeEnd = 'composition';
|
||||
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-start',
|
||||
`url(${url}#${id}_${diagramType}-aggregationStart)`
|
||||
);
|
||||
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||
'marker-end',
|
||||
`url(${url}#${id}_${diagramType}-compositionEnd)`
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add invalid markers', () => {
|
||||
const arrowTypeStart = 'this is an invalid marker';
|
||||
const arrowTypeEnd = ') url(https://my-malicious-site.example)';
|
||||
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||
expect(svgPath.attr).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
import type { SVG } from '../diagram-api/types.js';
|
||||
import { log } from '../logger.js';
|
||||
import type { EdgeData } from '../types.js';
|
||||
/**
|
||||
* Adds SVG markers to a path element based on the arrow types specified in the edge.
|
||||
*
|
||||
* @param svgPath - The SVG path element to add markers to.
|
||||
* @param edge - The edge data object containing the arrow types.
|
||||
* @param url - The URL of the SVG marker definitions.
|
||||
* @param id - The ID prefix for the SVG marker definitions.
|
||||
* @param diagramType - The type of diagram being rendered.
|
||||
*/
|
||||
export const addEdgeMarkers = (
|
||||
svgPath: SVG,
|
||||
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
|
||||
url: string,
|
||||
id: string,
|
||||
diagramType: string
|
||||
) => {
|
||||
if (edge.arrowTypeStart) {
|
||||
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
|
||||
}
|
||||
if (edge.arrowTypeEnd) {
|
||||
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
|
||||
}
|
||||
};
|
||||
|
||||
const arrowTypesMap = {
|
||||
arrow_cross: 'cross',
|
||||
arrow_point: 'point',
|
||||
arrow_barb: 'barb',
|
||||
arrow_circle: 'circle',
|
||||
aggregation: 'aggregation',
|
||||
extension: 'extension',
|
||||
composition: 'composition',
|
||||
dependency: 'dependency',
|
||||
lollipop: 'lollipop',
|
||||
} as const;
|
||||
|
||||
const addEdgeMarker = (
|
||||
svgPath: SVG,
|
||||
position: 'start' | 'end',
|
||||
arrowType: string,
|
||||
url: string,
|
||||
id: string,
|
||||
diagramType: string
|
||||
) => {
|
||||
const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
|
||||
|
||||
if (!endMarkerType) {
|
||||
log.warn(`Unknown arrow type: ${arrowType}`);
|
||||
return; // unknown arrow type, ignore
|
||||
}
|
||||
|
||||
const suffix = position === 'start' ? 'Start' : 'End';
|
||||
svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`);
|
||||
};
|
|
@ -6,6 +6,8 @@ import { getConfig } from '../diagram-api/diagramAPI.js';
|
|||
import utils from '../utils.js';
|
||||
import { evaluate } from '../diagrams/common/common.js';
|
||||
import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js';
|
||||
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
|
||||
import { addEdgeMarkers } from './edgeMarker.js';
|
||||
|
||||
let edgeLabels = {};
|
||||
let terminalLabels = {};
|
||||
|
@ -135,6 +137,8 @@ function setTerminalWidth(fo, value) {
|
|||
export const positionEdgeLabel = (edge, paths) => {
|
||||
log.info('Moving label abc78 ', edge.id, edge.label, edgeLabels[edge.id]);
|
||||
let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
|
||||
const siteConfig = getConfig();
|
||||
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
||||
if (edge.label) {
|
||||
const el = edgeLabels[edge.id];
|
||||
let x = edge.x;
|
||||
|
@ -158,7 +162,7 @@ export const positionEdgeLabel = (edge, paths) => {
|
|||
y = pos.y;
|
||||
}
|
||||
}
|
||||
el.attr('transform', 'translate(' + x + ', ' + y + ')');
|
||||
el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`);
|
||||
}
|
||||
|
||||
//let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
|
||||
|
@ -172,7 +176,7 @@ export const positionEdgeLabel = (edge, paths) => {
|
|||
x = pos.x;
|
||||
y = pos.y;
|
||||
}
|
||||
el.attr('transform', 'translate(' + x + ', ' + y + ')');
|
||||
el.attr('transform', `translate(${x}, ${y})`);
|
||||
}
|
||||
if (edge.startLabelRight) {
|
||||
const el = terminalLabels[edge.id].startRight;
|
||||
|
@ -188,7 +192,7 @@ export const positionEdgeLabel = (edge, paths) => {
|
|||
x = pos.x;
|
||||
y = pos.y;
|
||||
}
|
||||
el.attr('transform', 'translate(' + x + ', ' + y + ')');
|
||||
el.attr('transform', `translate(${x}, ${y})`);
|
||||
}
|
||||
if (edge.endLabelLeft) {
|
||||
const el = terminalLabels[edge.id].endLeft;
|
||||
|
@ -200,7 +204,7 @@ export const positionEdgeLabel = (edge, paths) => {
|
|||
x = pos.x;
|
||||
y = pos.y;
|
||||
}
|
||||
el.attr('transform', 'translate(' + x + ', ' + y + ')');
|
||||
el.attr('transform', `translate(${x}, ${y})`);
|
||||
}
|
||||
if (edge.endLabelRight) {
|
||||
const el = terminalLabels[edge.id].endRight;
|
||||
|
@ -212,7 +216,7 @@ export const positionEdgeLabel = (edge, paths) => {
|
|||
x = pos.x;
|
||||
y = pos.y;
|
||||
}
|
||||
el.attr('transform', 'translate(' + x + ', ' + y + ')');
|
||||
el.attr('transform', `translate(${x}, ${y})`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -506,108 +510,8 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
|
|||
log.info('arrowTypeStart', edge.arrowTypeStart);
|
||||
log.info('arrowTypeEnd', edge.arrowTypeEnd);
|
||||
|
||||
switch (edge.arrowTypeStart) {
|
||||
case 'arrow_cross':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'arrow_point':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'arrow_barb':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'arrow_circle':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'aggregation':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'extension':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'composition':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'dependency':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
|
||||
);
|
||||
break;
|
||||
case 'lollipop':
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
switch (edge.arrowTypeEnd) {
|
||||
case 'arrow_cross':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
|
||||
break;
|
||||
case 'arrow_point':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
|
||||
break;
|
||||
case 'arrow_barb':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
|
||||
break;
|
||||
case 'arrow_circle':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
|
||||
break;
|
||||
case 'aggregation':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'extension':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'composition':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'dependency':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
|
||||
);
|
||||
break;
|
||||
case 'lollipop':
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
addEdgeMarkers(svgPath, edge, url, id, diagramType);
|
||||
|
||||
let paths = {};
|
||||
if (pointsHasChanged) {
|
||||
paths.updatedPath = points;
|
||||
|
|
|
@ -13,8 +13,10 @@ import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './no
|
|||
import { insertCluster, clear as clearClusters } from './clusters.js';
|
||||
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js';
|
||||
import { log } from '../logger.js';
|
||||
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
|
||||
import { getConfig } from '../diagram-api/diagramAPI.js';
|
||||
|
||||
const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) => {
|
||||
const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster, siteConfig) => {
|
||||
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
|
||||
const dir = graph.graph().rankdir;
|
||||
log.trace('Dir in recursive render - dir:', dir);
|
||||
|
@ -52,7 +54,14 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
|
|||
if (node && node.clusterNode) {
|
||||
// const children = graph.children(v);
|
||||
log.info('Cluster identified', v, node.width, graph.node(v));
|
||||
const o = await recursiveRender(nodes, node.graph, diagramtype, id, graph.node(v));
|
||||
const o = await recursiveRender(
|
||||
nodes,
|
||||
node.graph,
|
||||
diagramtype,
|
||||
id,
|
||||
graph.node(v),
|
||||
siteConfig
|
||||
);
|
||||
const newEl = o.elem;
|
||||
updateNodeBounds(node, newEl);
|
||||
node.diff = o.diff || 0;
|
||||
|
@ -101,6 +110,7 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
|
|||
log.info('Graph after layout:', graphlibJson.write(graph));
|
||||
// Move the nodes to the correct place
|
||||
let diff = 0;
|
||||
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
||||
sortNodesByHierarchy(graph).forEach(function (v) {
|
||||
const node = graph.node(v);
|
||||
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
|
@ -114,16 +124,18 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
|
|||
);
|
||||
if (node && node.clusterNode) {
|
||||
// clusterDb[node.id].node = node;
|
||||
|
||||
node.y += subGraphTitleTotalMargin;
|
||||
positionNode(node);
|
||||
} else {
|
||||
// Non cluster node
|
||||
if (graph.children(v).length > 0) {
|
||||
// A cluster in the non-recursive way
|
||||
// positionCluster(node);
|
||||
node.height += subGraphTitleTotalMargin;
|
||||
insertCluster(clusters, node);
|
||||
clusterDb[node.id].node = node;
|
||||
} else {
|
||||
node.y += subGraphTitleTotalMargin / 2;
|
||||
positionNode(node);
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +146,7 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
|
|||
const edge = graph.edge(e);
|
||||
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
|
||||
|
||||
edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
|
||||
const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph, id);
|
||||
positionEdgeLabel(edge, paths);
|
||||
});
|
||||
|
@ -159,7 +172,8 @@ export const render = async (elem, graph, markers, diagramtype, id) => {
|
|||
adjustClustersAndEdges(graph);
|
||||
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
|
||||
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
|
||||
await recursiveRender(elem, graph, diagramtype, id);
|
||||
const siteConfig = getConfig();
|
||||
await recursiveRender(elem, graph, diagramtype, id, undefined, siteConfig);
|
||||
};
|
||||
|
||||
// const shapeDefinitions = {};
|
||||
|
|
|
@ -80,7 +80,9 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
|
|||
? getConfig().fontSize
|
||||
: window.getComputedStyle(document.body).fontSize;
|
||||
const enlargingFactor = 5;
|
||||
img.style.width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
|
||||
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
|
||||
img.style.minWidth = width;
|
||||
img.style.maxWidth = width;
|
||||
} else {
|
||||
img.style.width = '100%';
|
||||
}
|
||||
|
|
|
@ -446,11 +446,13 @@ const getNamespaces = function (): NamespaceMap {
|
|||
* @public
|
||||
*/
|
||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
classNames.map((className) => {
|
||||
if (namespaces[id] === undefined) {
|
||||
return;
|
||||
}
|
||||
for (const name of classNames) {
|
||||
const { className } = splitClassNameAndType(name);
|
||||
classes[className].parent = id;
|
||||
namespaces[id].classes[className] = classes[className];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1043,6 +1043,19 @@ foo()
|
|||
`;
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle namespace with generic types', () => {
|
||||
parser.parse(`classDiagram
|
||||
|
||||
namespace space {
|
||||
class Square~Shape~{
|
||||
int id
|
||||
List~int~ position
|
||||
setPoints(List~int~ points)
|
||||
getPoints() List~int~
|
||||
}
|
||||
}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -145,6 +145,7 @@ g.classGroup line {
|
|||
|
||||
.edgeTerminals {
|
||||
font-size: 11px;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.classTitleText {
|
||||
|
|
|
@ -255,11 +255,12 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||
log.debug('in mergeBranch');
|
||||
};
|
||||
|
||||
export const cherryPick = function (sourceId, targetId, tag) {
|
||||
export const cherryPick = function (sourceId, targetId, tag, parentCommitId) {
|
||||
log.debug('Entering cherryPick:', sourceId, targetId, tag);
|
||||
sourceId = common.sanitizeText(sourceId, getConfig());
|
||||
targetId = common.sanitizeText(targetId, getConfig());
|
||||
tag = common.sanitizeText(tag, getConfig());
|
||||
parentCommitId = common.sanitizeText(parentCommitId, getConfig());
|
||||
|
||||
if (!sourceId || commits[sourceId] === undefined) {
|
||||
let error = new Error(
|
||||
|
@ -274,20 +275,21 @@ export const cherryPick = function (sourceId, targetId, tag) {
|
|||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
let sourceCommit = commits[sourceId];
|
||||
let sourceCommitBranch = sourceCommit.branch;
|
||||
if (sourceCommit.type === commitType.MERGE) {
|
||||
if (
|
||||
parentCommitId &&
|
||||
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
|
||||
) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "cherryPick". Source commit should not be a merge commit'
|
||||
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
token: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
if (!targetId || commits[targetId] === undefined) {
|
||||
|
@ -327,7 +329,11 @@ export const cherryPick = function (sourceId, targetId, tag) {
|
|||
parents: [head == null ? null : head.id, sourceCommit.id],
|
||||
branch: curBranch,
|
||||
type: commitType.CHERRY_PICK,
|
||||
tag: tag ?? 'cherry-pick:' + sourceCommit.id,
|
||||
tag:
|
||||
tag ??
|
||||
`cherry-pick:${sourceCommit.id}${
|
||||
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
||||
}`,
|
||||
};
|
||||
head = commit;
|
||||
commits[commit.id] = commit;
|
||||
|
|
|
@ -673,6 +673,145 @@ describe('when parsing a gitGraph', function () {
|
|||
expect(commits[cherryPickCommitID].branch).toBe('main');
|
||||
});
|
||||
|
||||
it('should support cherry-picking of merge commits', function () {
|
||||
const str = `gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
cherry-pick id: "M" parent:"B"
|
||||
`;
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
const cherryPickCommitID = Object.keys(commits)[4];
|
||||
expect(commits[cherryPickCommitID].tag).toBe('cherry-pick:M|parent:B');
|
||||
expect(commits[cherryPickCommitID].branch).toBe('release');
|
||||
});
|
||||
|
||||
it('should support cherry-picking of merge commits with tag', function () {
|
||||
const str = `gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
cherry-pick id: "M" parent:"ZERO" tag: "v1.0"
|
||||
`;
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
const cherryPickCommitID = Object.keys(commits)[4];
|
||||
expect(commits[cherryPickCommitID].tag).toBe('v1.0');
|
||||
expect(commits[cherryPickCommitID].branch).toBe('release');
|
||||
});
|
||||
|
||||
it('should support cherry-picking of merge commits with additional commit', function () {
|
||||
const str = `gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
commit id: "C"
|
||||
cherry-pick id: "M" tag: "v2.1:ZERO" parent:"ZERO"
|
||||
commit id: "D"
|
||||
`;
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
const cherryPickCommitID = Object.keys(commits)[5];
|
||||
expect(commits[cherryPickCommitID].tag).toBe('v2.1:ZERO');
|
||||
expect(commits[cherryPickCommitID].branch).toBe('release');
|
||||
});
|
||||
|
||||
it('should support cherry-picking of merge commits with empty tag', function () {
|
||||
const str = `gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
commit id: "C"
|
||||
cherry-pick id:"M" parent: "ZERO" tag:""
|
||||
commit id: "D"
|
||||
cherry-pick id:"M" tag:"" parent: "B"
|
||||
`;
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
const cherryPickCommitID = Object.keys(commits)[5];
|
||||
const cherryPickCommitID2 = Object.keys(commits)[7];
|
||||
expect(commits[cherryPickCommitID].tag).toBe('');
|
||||
expect(commits[cherryPickCommitID2].tag).toBe('');
|
||||
expect(commits[cherryPickCommitID].branch).toBe('release');
|
||||
});
|
||||
|
||||
it('should fail cherry-picking of merge commits if the parent of merge commits is not specified', function () {
|
||||
expect(() =>
|
||||
parser
|
||||
.parse(
|
||||
`gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
commit id: "C"
|
||||
cherry-pick id:"M"
|
||||
`
|
||||
)
|
||||
.toThrow(
|
||||
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail cherry-picking of merge commits when the parent provided is not an immediate parent of cherry picked commit', function () {
|
||||
expect(() =>
|
||||
parser
|
||||
.parse(
|
||||
`gitGraph
|
||||
commit id: "ZERO"
|
||||
branch feature
|
||||
branch release
|
||||
checkout feature
|
||||
commit id: "A"
|
||||
commit id: "B"
|
||||
checkout main
|
||||
merge feature id: "M"
|
||||
checkout release
|
||||
commit id: "C"
|
||||
cherry-pick id:"M" parent: "A"
|
||||
`
|
||||
)
|
||||
.toThrow(
|
||||
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when try to branch existing branch: main', function () {
|
||||
const str = `gitGraph
|
||||
commit
|
||||
|
|
|
@ -39,6 +39,7 @@ branch(?=\s|$) return 'BRANCH';
|
|||
"order:" return 'ORDER';
|
||||
merge(?=\s|$) return 'MERGE';
|
||||
cherry\-pick(?=\s|$) return 'CHERRY_PICK';
|
||||
"parent:" return 'PARENT_COMMIT'
|
||||
// "reset" return 'RESET';
|
||||
checkout(?=\s|$) return 'CHECKOUT';
|
||||
"LR" return 'DIR';
|
||||
|
@ -109,10 +110,17 @@ branchStatement
|
|||
|
||||
cherryPickStatement
|
||||
: CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3, '', undefined)}
|
||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($3, '', undefined,$5)}
|
||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG STR {yy.cherryPick($3, '', $5)}
|
||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '')}
|
||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR COMMIT_TAG STR {yy.cherryPick($3, '', $7,$5)}
|
||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG STR PARENT_COMMIT STR {yy.cherryPick($3, '', $5,$7)}
|
||||
| CHERRY_PICK COMMIT_TAG STR COMMIT_ID STR {yy.cherryPick($5, '', $3)}
|
||||
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR {yy.cherryPick($3, '', '')}
|
||||
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR {yy.cherryPick($5, '', '')}
|
||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '')}
|
||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '',$5)}
|
||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR PARENT_COMMIT STR {yy.cherryPick($3, '', '',$7)}
|
||||
| CHERRY_PICK COMMIT_TAG STR COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($5, '', $3,$7)}
|
||||
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR PARENT_COMMIT STR{yy.cherryPick($5, '', '',$7)}
|
||||
;
|
||||
|
||||
mergeStatement
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type d3 from 'd3';
|
||||
import { scaleOrdinal, pie as d3pie, arc } from 'd3';
|
||||
|
||||
import { log } from '../../logger.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
|
@ -38,33 +37,25 @@ const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Sections>[] => {
|
|||
*/
|
||||
export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
log.debug('rendering pie chart\n' + text);
|
||||
|
||||
const db = diagObj.db as PieDB;
|
||||
const globalConfig: MermaidConfig = getConfig();
|
||||
const pieConfig: Required<PieDiagramConfig> = cleanAndMerge(db.getConfig(), globalConfig.pie);
|
||||
|
||||
const height = 450;
|
||||
// TODO: remove document width
|
||||
const width: number =
|
||||
document.getElementById(id)?.parentElement?.offsetWidth ?? pieConfig.useWidth;
|
||||
const svg: SVG = selectSvgElement(id);
|
||||
// Set viewBox
|
||||
svg.attr('viewBox', `0 0 ${width} ${height}`);
|
||||
configureSvgSize(svg, height, width, pieConfig.useMaxWidth);
|
||||
|
||||
const MARGIN = 40;
|
||||
const LEGEND_RECT_SIZE = 18;
|
||||
const LEGEND_SPACING = 4;
|
||||
|
||||
const height = 450;
|
||||
const pieWidth: number = height;
|
||||
const svg: SVG = selectSvgElement(id);
|
||||
const group: Group = svg.append('g');
|
||||
group.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
const sections: Sections = db.getSections();
|
||||
group.attr('transform', 'translate(' + pieWidth / 2 + ',' + height / 2 + ')');
|
||||
|
||||
const { themeVariables } = globalConfig;
|
||||
let [outerStrokeWidth] = parseFontSize(themeVariables.pieOuterStrokeWidth);
|
||||
outerStrokeWidth ??= 2;
|
||||
|
||||
const textPosition: number = pieConfig.textPosition;
|
||||
const radius: number = Math.min(width, height) / 2 - MARGIN;
|
||||
const radius: number = Math.min(pieWidth, height) / 2 - MARGIN;
|
||||
// Shape helper to build arcs:
|
||||
const arcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||
d3.PieArcDatum<D3Sections>
|
||||
|
@ -84,7 +75,6 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
|||
.attr('r', radius + outerStrokeWidth / 2)
|
||||
.attr('class', 'pieOuterCircle');
|
||||
|
||||
const sections: Sections = db.getSections();
|
||||
const arcs: d3.PieArcDatum<D3Sections>[] = createPieArcs(sections);
|
||||
|
||||
const myGeneratedColors = [
|
||||
|
@ -177,6 +167,19 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
|||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
const longestTextWidth = Math.max(
|
||||
...legend
|
||||
.selectAll('text')
|
||||
.nodes()
|
||||
.map((node) => (node as Element)?.getBoundingClientRect().width ?? 0)
|
||||
);
|
||||
|
||||
const totalWidth = pieWidth + MARGIN + LEGEND_RECT_SIZE + LEGEND_SPACING + longestTextWidth;
|
||||
|
||||
// Set viewBox
|
||||
svg.attr('viewBox', `0 0 ${totalWidth} ${height}`);
|
||||
configureSvgSize(svg, height, totalWidth, pieConfig.useMaxWidth);
|
||||
};
|
||||
|
||||
export const renderer = { draw };
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
sankeyCenter as d3SankeyCenter,
|
||||
sankeyJustify as d3SankeyJustify,
|
||||
} from 'd3-sankey';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||
import { Uid } from '../../rendering-util/uid.js';
|
||||
import type { SankeyNodeAlignment } from '../../config.type.js';
|
||||
|
||||
|
@ -70,12 +70,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||
const suffix = conf?.suffix ?? defaultSankeyConfig.suffix!;
|
||||
const showValues = conf?.showValues ?? defaultSankeyConfig.showValues!;
|
||||
|
||||
// FIX: using max width prevents height from being set, is it intended?
|
||||
// to add height directly one can use `svg.attr('height', height)`
|
||||
//
|
||||
// @ts-ignore TODO: svg type vs selection mismatch
|
||||
configureSvgSize(svg, height, width, useMaxWidth);
|
||||
|
||||
// Prepare data for construction based on diagObj.db
|
||||
// This must be a mutable object with `nodes` and `links` properties:
|
||||
//
|
||||
|
@ -208,6 +202,8 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||
.attr('d', d3SankeyLinkHorizontal())
|
||||
.attr('stroke', coloring)
|
||||
.attr('stroke-width', (d: any) => Math.max(1, d.width));
|
||||
|
||||
setupGraphViewbox(undefined, svg, 0, useMaxWidth);
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -109,6 +109,8 @@ Communication tools and platforms
|
|||
|
||||
### Wikis
|
||||
|
||||
- [PmWiki](https://www.pmwiki.org)
|
||||
- [MermaidJs Cookbook recipe](https://www.pmwiki.org/wiki/Cookbook/MermaidJs)
|
||||
- [MediaWiki](https://www.mediawiki.org)
|
||||
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
|
||||
- [Flex Diagrams Extension](https://www.mediawiki.org/wiki/Extension:Flex_Diagrams)
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
"preview-https": "pnpm build && serve .vitepress/dist",
|
||||
"preview-https-no-prefetch": "pnpm build-no-prefetch && serve .vitepress/dist",
|
||||
"prefetch": "pnpm fetch-contributors && pnpm fetch-avatars",
|
||||
"fetch-avatars": "ts-node-esm .vitepress/scripts/fetch-avatars.ts",
|
||||
"fetch-contributors": "ts-node-esm .vitepress/scripts/fetch-contributors.ts"
|
||||
"fetch-avatars": "tsx .vitepress/scripts/fetch-avatars.ts",
|
||||
"fetch-contributors": "tsx .vitepress/scripts/fetch-contributors.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.1.0",
|
||||
|
@ -22,17 +22,17 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/carbon": "^1.1.16",
|
||||
"@unocss/reset": "^0.57.0",
|
||||
"@unocss/reset": "^0.58.0",
|
||||
"@vite-pwa/vitepress": "^0.3.0",
|
||||
"@vitejs/plugin-vue": "^4.2.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"https-localhost": "^4.7.1",
|
||||
"pathe": "^1.1.0",
|
||||
"unocss": "^0.57.0",
|
||||
"unplugin-vue-components": "^0.25.0",
|
||||
"vite": "^4.3.9",
|
||||
"unocss": "^0.58.0",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^4.4.12",
|
||||
"vite-plugin-pwa": "^0.17.0",
|
||||
"vitepress": "1.0.0-rc.29",
|
||||
"vitepress": "1.0.0-rc.31",
|
||||
"workbox-window": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,6 +244,8 @@ A few important rules to note here are:
|
|||
1. You need to provide the `id` for an existing commit to be cherry-picked. If given commit id does not exist it will result in an error. For this, make use of the `commit id:$value` format of declaring commits. See the examples from above.
|
||||
2. The given commit must not exist on the current branch. The cherry-picked commit must always be a different branch than the current branch.
|
||||
3. Current branch must have at least one commit, before you can cherry-pick, otherwise it will cause an error is throw.
|
||||
4. When cherry-picking a merge commit, providing a parent commit ID is mandatory. If the parent attribute is omitted or an invalid parent commit ID is provided, an error will be thrown.
|
||||
5. The specified parent commit must be an immediate parent of the merge commit being cherry-picked.
|
||||
|
||||
Let see an example:
|
||||
|
||||
|
@ -251,14 +253,17 @@ Let see an example:
|
|||
gitGraph
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
branch release
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
merge develop id:"MERGE"
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
checkout release
|
||||
cherry-pick id:"MERGE" parent:"B"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
|
|
|
@ -2,7 +2,7 @@ import mermaid from './mermaid.js';
|
|||
import { mermaidAPI } from './mermaidAPI.js';
|
||||
import './diagram-api/diagram-orchestration.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
import { beforeAll, describe, it, expect, vi } from 'vitest';
|
||||
import { beforeAll, describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import type { DiagramDefinition } from './diagram-api/types.js';
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
|
@ -67,6 +67,7 @@ vi.mock('stylis', () => {
|
|||
});
|
||||
import { compile, serialize } from 'stylis';
|
||||
import { decodeEntities, encodeEntities } from './utils.js';
|
||||
import { Diagram } from './Diagram.js';
|
||||
|
||||
/**
|
||||
* @see https://vitest.dev/guide/mocking.html Mock part of a module
|
||||
|
@ -744,4 +745,16 @@ describe('mermaidAPI', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDiagramFromText', () => {
|
||||
it('should clean up comments when present in diagram definition', async () => {
|
||||
const diagram = await mermaidAPI.getDiagramFromText(
|
||||
`flowchart LR
|
||||
%% This is a comment A -- text --> B{node}
|
||||
A -- text --> B -- text2 --> C`
|
||||
);
|
||||
expect(diagram).toBeInstanceOf(Diagram);
|
||||
expect(diagram.type).toBe('flowchart-v2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ import { compile, serialize, stringify } from 'stylis';
|
|||
import { version } from '../package.json';
|
||||
import * as configApi from './config.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
import { Diagram, getDiagramFromText } from './Diagram.js';
|
||||
import { Diagram, getDiagramFromText as getDiagramFromTextInternal } from './Diagram.js';
|
||||
import errorRenderer from './diagrams/error/errorRenderer.js';
|
||||
import { attachFunctions } from './interactionDb.js';
|
||||
import { log, setLogLevel } from './logger.js';
|
||||
|
@ -28,7 +28,7 @@ import type { MermaidConfig } from './config.type.js';
|
|||
import { evaluate } from './diagrams/common/common.js';
|
||||
import isEmpty from 'lodash-es/isEmpty.js';
|
||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
|
||||
import type { DiagramStyleClassDef } from './diagram-api/types.js';
|
||||
import type { DiagramMetadata, DiagramStyleClassDef } from './diagram-api/types.js';
|
||||
import { preprocessDiagram } from './preprocess.js';
|
||||
import { decodeEntities } from './utils.js';
|
||||
|
||||
|
@ -519,6 +519,11 @@ function initialize(options: MermaidConfig = {}) {
|
|||
addDiagrams();
|
||||
}
|
||||
|
||||
const getDiagramFromText = (text: string, metadata: Pick<DiagramMetadata, 'title'> = {}) => {
|
||||
const { code } = preprocessDiagram(text);
|
||||
return getDiagramFromTextInternal(code, metadata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add accessibility (a11y) information to the diagram.
|
||||
*
|
||||
|
|
|
@ -1844,6 +1844,7 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
unevaluatedProperties: false
|
||||
required:
|
||||
- titleTopMargin
|
||||
- subGraphTitleMargin
|
||||
- diagramPadding
|
||||
- htmlLabels
|
||||
- nodeSpacing
|
||||
|
@ -1856,6 +1857,20 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
titleTopMargin:
|
||||
$ref: '#/$defs/GitGraphDiagramConfig/properties/titleTopMargin'
|
||||
default: 25
|
||||
subGraphTitleMargin:
|
||||
description: |
|
||||
Defines a top/bottom margin for subgraph titles
|
||||
type: object
|
||||
properties:
|
||||
top:
|
||||
type: integer
|
||||
minimum: 0
|
||||
bottom:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default:
|
||||
top: 0
|
||||
bottom: 0
|
||||
arrowMarkerAbsolute:
|
||||
type: boolean # TODO, is this actually used here (it has no default value but was in types)
|
||||
diagramPadding:
|
||||
|
|
|
@ -44,6 +44,8 @@ export const configureSvgSize = function (svgElem, height, width, useMaxWidth) {
|
|||
const attrs = calculateSvgSizeAttrs(height, width, useMaxWidth);
|
||||
d3Attrs(svgElem, attrs);
|
||||
};
|
||||
|
||||
// TODO v11: Remove the graph parameter. It is not used.
|
||||
export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth) {
|
||||
const svgBounds = svgElem.node().getBBox();
|
||||
const sWidth = svgBounds.width;
|
||||
|
@ -55,26 +57,13 @@ export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth)
|
|||
let height = 0;
|
||||
log.info(`Graph bounds: ${width}x${height}`, graph);
|
||||
|
||||
// let tx = 0;
|
||||
// let ty = 0;
|
||||
// if (sWidth > width) {
|
||||
// tx = (sWidth - width) / 2 + padding;
|
||||
width = sWidth + padding * 2;
|
||||
// } else {
|
||||
// if (Math.abs(sWidth - width) >= 2 * padding + 1) {
|
||||
// width = width - padding;
|
||||
// }
|
||||
// }
|
||||
// if (sHeight > height) {
|
||||
// ty = (sHeight - height) / 2 + padding;
|
||||
height = sHeight + padding * 2;
|
||||
// }
|
||||
|
||||
log.info(`Calculated bounds: ${width}x${height}`);
|
||||
configureSvgSize(svgElem, height, width, useMaxWidth);
|
||||
|
||||
// Ensure the viewBox includes the whole svgBounds area with extra space for padding
|
||||
// const vBox = `0 0 ${width} ${height}`;
|
||||
const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${
|
||||
svgBounds.width + 2 * padding
|
||||
} ${svgBounds.height + 2 * padding}`;
|
||||
|
|
|
@ -19,9 +19,12 @@ const markerOffsets = {
|
|||
* @returns The angle, deltaX and deltaY
|
||||
*/
|
||||
function calculateDeltaAndAngle(
|
||||
point1: Point | [number, number],
|
||||
point2: Point | [number, number]
|
||||
point1?: Point | [number, number],
|
||||
point2?: Point | [number, number]
|
||||
): { angle: number; deltaX: number; deltaY: number } {
|
||||
if (point1 === undefined || point2 === undefined) {
|
||||
return { angle: 0, deltaX: 0, deltaY: 0 };
|
||||
}
|
||||
point1 = pointTransformer(point1);
|
||||
point2 = pointTransformer(point2);
|
||||
const [x1, y1] = [point1.x, point1.y];
|
||||
|
@ -90,3 +93,44 @@ export const getLineFunctionsWithOffset = (
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { it, expect, describe } = import.meta.vitest;
|
||||
describe('calculateDeltaAndAngle', () => {
|
||||
it('should calculate the angle and deltas between two points', () => {
|
||||
expect(calculateDeltaAndAngle([0, 0], [0, 1])).toStrictEqual({
|
||||
angle: 1.5707963267948966,
|
||||
deltaX: 0,
|
||||
deltaY: 1,
|
||||
});
|
||||
expect(calculateDeltaAndAngle([1, 0], [0, -1])).toStrictEqual({
|
||||
angle: 0.7853981633974483,
|
||||
deltaX: -1,
|
||||
deltaY: -1,
|
||||
});
|
||||
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, [0, -1])).toStrictEqual({
|
||||
angle: 0.7853981633974483,
|
||||
deltaX: -1,
|
||||
deltaY: -1,
|
||||
});
|
||||
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, { x: 1, y: 0 })).toStrictEqual({
|
||||
angle: NaN,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should calculate the angle and deltas if one point in undefined', () => {
|
||||
expect(calculateDeltaAndAngle(undefined, [0, 1])).toStrictEqual({
|
||||
angle: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
});
|
||||
expect(calculateDeltaAndAngle([0, 1], undefined)).toStrictEqual({
|
||||
angle: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { getSubGraphTitleMargins } from './subGraphTitleMargins.js';
|
||||
import * as configApi from '../config.js';
|
||||
|
||||
describe('getSubGraphTitleMargins', () => {
|
||||
it('should get subgraph title margins after config has been set', () => {
|
||||
const config_0 = {
|
||||
flowchart: {
|
||||
subGraphTitleMargin: {
|
||||
top: 10,
|
||||
bottom: 5,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
configApi.setSiteConfig(config_0);
|
||||
expect(getSubGraphTitleMargins(config_0)).toEqual({
|
||||
subGraphTitleTopMargin: 10,
|
||||
subGraphTitleBottomMargin: 5,
|
||||
subGraphTitleTotalMargin: 15,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
import type { FlowchartDiagramConfig } from '../config.type.js';
|
||||
|
||||
export const getSubGraphTitleMargins = ({
|
||||
flowchart,
|
||||
}: {
|
||||
flowchart: FlowchartDiagramConfig;
|
||||
}): {
|
||||
subGraphTitleTopMargin: number;
|
||||
subGraphTitleBottomMargin: number;
|
||||
subGraphTitleTotalMargin: number;
|
||||
} => {
|
||||
const subGraphTitleTopMargin = flowchart?.subGraphTitleMargin?.top ?? 0;
|
||||
const subGraphTitleBottomMargin = flowchart?.subGraphTitleMargin?.bottom ?? 0;
|
||||
const subGraphTitleTotalMargin = subGraphTitleTopMargin + subGraphTitleBottomMargin;
|
||||
|
||||
return {
|
||||
subGraphTitleTopMargin,
|
||||
subGraphTitleBottomMargin,
|
||||
subGraphTitleTotalMargin,
|
||||
};
|
||||
};
|
|
@ -2,7 +2,8 @@
|
|||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./package.json"]
|
||||
}
|
||||
|
|
1792
pnpm-lock.yaml
1792
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,7 @@ export default defineConfig({
|
|||
reportsDirectory: './coverage/vitest',
|
||||
exclude: [...defaultExclude, './tests/**', '**/__mocks__/**', '**/generated/'],
|
||||
},
|
||||
includeSource: ['packages/*/src/**/*.{js,ts}'],
|
||||
},
|
||||
build: {
|
||||
/** If you set esmExternals to true, this plugins assumes that
|
||||
|
@ -33,4 +34,7 @@ export default defineConfig({
|
|||
esmExternals: true,
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'import.meta.vitest': 'undefined',
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue