mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-20 12:44:18 +01:00
Merge branch next into add-pie-langium-parser
This commit is contained in:
45
packages/mermaid-flowchart-elk/package.json
Normal file
45
packages/mermaid-flowchart-elk/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@mermaid-js/flowchart-elk",
|
||||
"version": "1.0.0-rc.1",
|
||||
"description": "Flowchart plugin for mermaid with ELK layout",
|
||||
"module": "dist/mermaid-flowchart-elk.core.mjs",
|
||||
"types": "dist/packages/mermaid-flowchart-elk/src/detector.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-flowchart-elk.core.mjs",
|
||||
"types": "./dist/packages/mermaid-flowchart-elk/src/detector.d.ts"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
"flowchart",
|
||||
"elk",
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid"
|
||||
},
|
||||
"author": "Knut Sveidqvist",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d3": "^7.4.0",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"khroma": "^2.0.0",
|
||||
"elkjs": "^0.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
32
packages/mermaid-flowchart-elk/src/detector.ts
Normal file
32
packages/mermaid-flowchart-elk/src/detector.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type {
|
||||
ExternalDiagramDefinition,
|
||||
DiagramDetector,
|
||||
DiagramLoader,
|
||||
} from '../../mermaid/src/diagram-api/types.js';
|
||||
|
||||
const id = 'flowchart-elk';
|
||||
|
||||
const detector: DiagramDetector = (txt, config): boolean => {
|
||||
if (
|
||||
// If diagram explicitly states flowchart-elk
|
||||
/^\s*flowchart-elk/.test(txt) ||
|
||||
// If a flowchart/graph diagram has their default renderer set to elk
|
||||
(/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const loader: DiagramLoader = async () => {
|
||||
const { diagram } = await import('./diagram-definition.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
12
packages/mermaid-flowchart-elk/src/diagram-definition.ts
Normal file
12
packages/mermaid-flowchart-elk/src/diagram-definition.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// @ts-ignore: JISON typing missing
|
||||
import parser from '../../mermaid/src/diagrams/flowchart/parser/flow.jison';
|
||||
import * as db from '../../mermaid/src/diagrams/flowchart/flowDb.js';
|
||||
import styles from '../../mermaid/src/diagrams/flowchart/styles.js';
|
||||
import renderer from './flowRenderer-elk.js';
|
||||
|
||||
export const diagram = {
|
||||
db,
|
||||
renderer,
|
||||
parser,
|
||||
styles,
|
||||
};
|
||||
@@ -1,16 +1,17 @@
|
||||
import { select, line, curveLinear } from 'd3';
|
||||
import { insertNode } from '../../../dagre-wrapper/nodes.js';
|
||||
import insertMarkers from '../../../dagre-wrapper/markers.js';
|
||||
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
|
||||
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
|
||||
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
|
||||
import { insertEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
|
||||
import { findCommonAncestor } from './render-utils.js';
|
||||
import { labelHelper } from '../../../dagre-wrapper/shapes/util.js';
|
||||
import { getConfig } from '../../../config.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import { setupGraphViewbox } from '../../../setupGraphViewbox.js';
|
||||
import common from '../../common/common.js';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../../utils.js';
|
||||
import { labelHelper } from '../../mermaid/src/dagre-wrapper/shapes/util.js';
|
||||
import { getConfig } from '../../mermaid/src/config.js';
|
||||
import { log } from '../../mermaid/src/logger.js';
|
||||
import { setupGraphViewbox } from '../../mermaid/src/setupGraphViewbox.js';
|
||||
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 '../../../utils/lineWithOffset.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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -695,7 +595,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
|
||||
*
|
||||
* @param text
|
||||
* @param diagObj
|
||||
* @returns {Record<string, import('../../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
|
||||
* @returns {Record<string, import('../../mermaid/src/diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
|
||||
*/
|
||||
export const getClasses = function (text, diagObj) {
|
||||
log.info('Extracting classes');
|
||||
10
packages/mermaid-flowchart-elk/tsconfig.json
Normal file
10
packages/mermaid-flowchart-elk/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "../..",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "11.0.0-alpha.2",
|
||||
"version": "11.0.0-alpha.6",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@@ -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"
|
||||
@@ -60,6 +60,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.1",
|
||||
"@mermaid-js/parser": "workspace:^",
|
||||
"cytoscape": "^3.23.0",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
@@ -68,11 +69,9 @@
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "^3.0.5",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"mermaid-parser": "workspace:^",
|
||||
"stylis": "^4.1.3",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"uuid": "^9.0.0"
|
||||
|
||||
@@ -233,6 +233,23 @@ async function generateTypescript(mermaidConfigSchema: JSONSchemaType<MermaidCon
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for type duplication when a $ref property has siblings.
|
||||
*
|
||||
* @param json - The input JSON object.
|
||||
*
|
||||
* @see https://github.com/bcherny/json-schema-to-typescript/issues/193
|
||||
*/
|
||||
function removeProp(json: any, name: string) {
|
||||
for (const prop in json) {
|
||||
if (prop === name) {
|
||||
delete json[prop];
|
||||
} else if (typeof json[prop] === 'object') {
|
||||
removeProp(json[prop], name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Main function */
|
||||
async function main() {
|
||||
if (verifyOnly) {
|
||||
@@ -243,6 +260,8 @@ async function main() {
|
||||
|
||||
const configJsonSchema = await loadJsonSchemaFromYaml();
|
||||
|
||||
removeProp(configJsonSchema, 'default');
|
||||
|
||||
validateSchema(configJsonSchema);
|
||||
|
||||
// Generate types from JSON Schema
|
||||
|
||||
@@ -768,8 +768,8 @@ export interface XYChartConfig extends BaseDiagramConfig {
|
||||
* Should show the chart title
|
||||
*/
|
||||
showTitle?: boolean;
|
||||
xAxis?: XYChartAxisConfig1;
|
||||
yAxis?: XYChartAxisConfig2;
|
||||
xAxis?: XYChartAxisConfig;
|
||||
yAxis?: XYChartAxisConfig;
|
||||
/**
|
||||
* How to plot will be drawn horizontal or vertical
|
||||
*/
|
||||
@@ -779,104 +779,6 @@ export interface XYChartConfig extends BaseDiagramConfig {
|
||||
*/
|
||||
plotReservedSpacePercent?: number;
|
||||
}
|
||||
/**
|
||||
* This object contains configuration for XYChart axis config
|
||||
*/
|
||||
export interface XYChartAxisConfig1 {
|
||||
/**
|
||||
* Should show the axis labels (tick text)
|
||||
*/
|
||||
showLabel?: boolean;
|
||||
/**
|
||||
* font size of the axis labels (tick text)
|
||||
*/
|
||||
labelFontSize?: number;
|
||||
/**
|
||||
* top and bottom space from axis label (tick text)
|
||||
*/
|
||||
labelPadding?: number;
|
||||
/**
|
||||
* Should show the axis title
|
||||
*/
|
||||
showTitle?: boolean;
|
||||
/**
|
||||
* font size of the axis title
|
||||
*/
|
||||
titleFontSize?: number;
|
||||
/**
|
||||
* top and bottom space from axis title
|
||||
*/
|
||||
titlePadding?: number;
|
||||
/**
|
||||
* Should show the axis tick lines
|
||||
*/
|
||||
showTick?: boolean;
|
||||
/**
|
||||
* length of the axis tick lines
|
||||
*/
|
||||
tickLength?: number;
|
||||
/**
|
||||
* width of the axis tick lines
|
||||
*/
|
||||
tickWidth?: number;
|
||||
/**
|
||||
* Show line across the axis
|
||||
*/
|
||||
showAxisLine?: boolean;
|
||||
/**
|
||||
* Width of the axis line
|
||||
*/
|
||||
axisLineWidth?: number;
|
||||
}
|
||||
/**
|
||||
* This object contains configuration for XYChart axis config
|
||||
*/
|
||||
export interface XYChartAxisConfig2 {
|
||||
/**
|
||||
* Should show the axis labels (tick text)
|
||||
*/
|
||||
showLabel?: boolean;
|
||||
/**
|
||||
* font size of the axis labels (tick text)
|
||||
*/
|
||||
labelFontSize?: number;
|
||||
/**
|
||||
* top and bottom space from axis label (tick text)
|
||||
*/
|
||||
labelPadding?: number;
|
||||
/**
|
||||
* Should show the axis title
|
||||
*/
|
||||
showTitle?: boolean;
|
||||
/**
|
||||
* font size of the axis title
|
||||
*/
|
||||
titleFontSize?: number;
|
||||
/**
|
||||
* top and bottom space from axis title
|
||||
*/
|
||||
titlePadding?: number;
|
||||
/**
|
||||
* Should show the axis tick lines
|
||||
*/
|
||||
showTick?: boolean;
|
||||
/**
|
||||
* length of the axis tick lines
|
||||
*/
|
||||
tickLength?: number;
|
||||
/**
|
||||
* width of the axis tick lines
|
||||
*/
|
||||
tickWidth?: number;
|
||||
/**
|
||||
* Show line across the axis
|
||||
*/
|
||||
showAxisLine?: boolean;
|
||||
/**
|
||||
* Width of the axis line
|
||||
*/
|
||||
axisLineWidth?: number;
|
||||
}
|
||||
/**
|
||||
* The object containing configurations specific for entity relationship diagrams
|
||||
*
|
||||
@@ -1396,6 +1298,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
|
||||
@@ -1464,13 +1374,7 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
|
||||
*
|
||||
*/
|
||||
linkColor?: SankeyLinkColor | string;
|
||||
/**
|
||||
* Controls the alignment of the Sankey diagrams.
|
||||
*
|
||||
* See <https://github.com/d3/d3-sankey#alignments>.
|
||||
*
|
||||
*/
|
||||
nodeAlignment?: 'left' | 'right' | 'center' | 'justify';
|
||||
nodeAlignment?: SankeyNodeAlignment;
|
||||
useMaxWidth?: boolean;
|
||||
/**
|
||||
* Toggle to display or hide values along with title.
|
||||
|
||||
@@ -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 +
|
||||
')'
|
||||
);
|
||||
|
||||
|
||||
79
packages/mermaid/src/dagre-wrapper/edgeMarker.spec.ts
Normal file
79
packages/mermaid/src/dagre-wrapper/edgeMarker.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
57
packages/mermaid/src/dagre-wrapper/edgeMarker.ts
Normal file
57
packages/mermaid/src/dagre-wrapper/edgeMarker.ts
Normal file
@@ -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%';
|
||||
}
|
||||
|
||||
@@ -71,10 +71,9 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio
|
||||
|
||||
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
|
||||
if (detectors[key]) {
|
||||
log.error(`Detector with key ${key} already exists`);
|
||||
} else {
|
||||
detectors[key] = { detector, loader };
|
||||
log.warn(`Detector with key ${key} already exists. Overwriting.`);
|
||||
}
|
||||
detectors[key] = { detector, loader };
|
||||
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export const registerDiagram = (
|
||||
detector?: DiagramDetector
|
||||
) => {
|
||||
if (diagrams[id]) {
|
||||
throw new Error(`Diagram ${id} already registered.`);
|
||||
log.warn(`Diagram with id ${id} already registered. Overwriting.`);
|
||||
}
|
||||
diagrams[id] = diagram;
|
||||
if (detector) {
|
||||
|
||||
@@ -2,9 +2,32 @@ import { describe, test, expect } from 'vitest';
|
||||
import { Diagram, getDiagramFromText } from './Diagram.js';
|
||||
import { addDetector } from './diagram-api/detectType.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
import type { DiagramLoader } from './diagram-api/types.js';
|
||||
|
||||
addDiagrams();
|
||||
|
||||
const getDummyDiagram = (id: string, title?: string): Awaited<ReturnType<DiagramLoader>> => {
|
||||
return {
|
||||
id,
|
||||
diagram: {
|
||||
db: {
|
||||
getDiagramTitle: () => title ?? id,
|
||||
},
|
||||
parser: {
|
||||
parse: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('diagram detection', () => {
|
||||
test('should detect inbuilt diagrams', async () => {
|
||||
const graph = (await getDiagramFromText('graph TD; A-->B')) as Diagram;
|
||||
@@ -21,30 +44,25 @@ describe('diagram detection', () => {
|
||||
addDetector(
|
||||
'loki',
|
||||
(str) => str.startsWith('loki'),
|
||||
() =>
|
||||
Promise.resolve({
|
||||
id: 'loki',
|
||||
diagram: {
|
||||
db: {},
|
||||
parser: {
|
||||
parse: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
})
|
||||
() => Promise.resolve(getDummyDiagram('loki'))
|
||||
);
|
||||
const diagram = (await getDiagramFromText('loki TD; A-->B')) as Diagram;
|
||||
const diagram = await getDiagramFromText('loki TD; A-->B');
|
||||
expect(diagram).toBeInstanceOf(Diagram);
|
||||
expect(diagram.type).toBe('loki');
|
||||
});
|
||||
|
||||
test('should allow external diagrams to override internal ones with same ID', async () => {
|
||||
const title = 'overridden';
|
||||
addDetector(
|
||||
'flowchart-elk',
|
||||
(str) => str.startsWith('flowchart-elk'),
|
||||
() => Promise.resolve(getDummyDiagram('flowchart-elk', title))
|
||||
);
|
||||
const diagram = await getDiagramFromText('flowchart-elk TD; A-->B');
|
||||
expect(diagram).toBeInstanceOf(Diagram);
|
||||
expect(diagram.db.getDiagramTitle?.()).toBe(title);
|
||||
});
|
||||
|
||||
test('should throw the right error for incorrect diagram', async () => {
|
||||
await expect(getDiagramFromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Parse error on line 2:
|
||||
|
||||
@@ -446,11 +446,13 @@ const getNamespaces = function (): NamespaceMap {
|
||||
* @public
|
||||
*/
|
||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
classNames.map((className) => {
|
||||
classes[className].parent = id;
|
||||
namespaces[id].classes[className] = classes[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~
|
||||
}
|
||||
}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -681,3 +681,82 @@ describe('given text representing a method, ', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given text representing an attribute', () => {
|
||||
describe('when the attribute has no modifiers', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = 'name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has public "+" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '+name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('+name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has protected "#" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '#name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('#name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has private "-" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '-name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('-name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has internal "~" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '~name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('~name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has static "$" modifier', () => {
|
||||
it('should parse the display text correctly and apply static css style', () => {
|
||||
const str = 'name String$';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('name String');
|
||||
expect(displayDetails.cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has abstract "*" modifier', () => {
|
||||
it('should parse the display text correctly and apply abstract css style', () => {
|
||||
const str = 'name String*';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('name String');
|
||||
expect(displayDetails.cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ export class ClassMember {
|
||||
this.visibility = firstChar as Visibility;
|
||||
}
|
||||
|
||||
if (lastChar.match(/[*?]/)) {
|
||||
if (lastChar.match(/[$*]/)) {
|
||||
potentialClassifier = lastChar;
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ g.classGroup line {
|
||||
|
||||
.edgeTerminals {
|
||||
font-size: 11px;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.classTitleText {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { DiagramAST } from 'mermaid-parser';
|
||||
|
||||
import type { DiagramAST } from '@mermaid-js/parser';
|
||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
|
||||
export function populateCommonDb(ast: DiagramAST, db: DiagramDB) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
DiagramDetector,
|
||||
DiagramLoader,
|
||||
} from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
|
||||
const id = 'flowchart-elk';
|
||||
|
||||
@@ -13,13 +14,21 @@ const detector: DiagramDetector = (txt, config): boolean => {
|
||||
// If a flowchart/graph diagram has their default renderer set to elk
|
||||
(/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
|
||||
) {
|
||||
// This will log at the end, hopefully.
|
||||
setTimeout(
|
||||
() =>
|
||||
log.warn(
|
||||
'flowchart-elk was moved to an external package in Mermaid v11. Please refer [release notes](link) for more details. This diagram will be rendered using `dagre` layout as a fallback.'
|
||||
),
|
||||
500
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const loader: DiagramLoader = async () => {
|
||||
const { diagram } = await import('./flowchart-elk-definition.js');
|
||||
const { diagram } = await import('../flowDiagram-v2.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// @ts-ignore: JISON typing missing
|
||||
import parser from '../parser/flow.jison';
|
||||
|
||||
import * as db from '../flowDb.js';
|
||||
import renderer from './flowRenderer-elk.js';
|
||||
import styles from './styles.js';
|
||||
|
||||
export const diagram = {
|
||||
db,
|
||||
renderer,
|
||||
parser,
|
||||
styles,
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -338,26 +338,34 @@ const drawCommits = (svg, commits, modifyGraph) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect if there are other commits between commit1's x-position and commit2's x-position on the
|
||||
* same branch as commit2.
|
||||
* Detect if there are commits
|
||||
* between commitA's x-position
|
||||
* and commitB's x-position on the
|
||||
* same branch as commitA, where
|
||||
* commitA isn't main
|
||||
*
|
||||
* @param {any} commit1
|
||||
* @param {any} commit2
|
||||
* @param {any} commitA
|
||||
* @param {any} commitB
|
||||
* @param branchToGetCurve
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param allCommits
|
||||
* @returns {boolean} If there are commits between commit1's x-position and commit2's x-position
|
||||
* @returns {boolean}
|
||||
* If there are commits between
|
||||
* commitA's x-position
|
||||
* and commitB's x-position
|
||||
* on the source branch, where
|
||||
* source branch is not main
|
||||
* return true
|
||||
*/
|
||||
const hasOverlappingCommits = (commit1, commit2, allCommits) => {
|
||||
// Find commits on the same branch as commit2
|
||||
const keys = Object.keys(allCommits);
|
||||
const overlappingComits = keys.filter((key) => {
|
||||
return (
|
||||
allCommits[key].branch === commit2.branch &&
|
||||
allCommits[key].seq > commit1.seq &&
|
||||
allCommits[key].seq < commit2.seq
|
||||
);
|
||||
const shouldRerouteArrow = (commitA, commitB, p1, p2, allCommits) => {
|
||||
const commitBIsFurthest = dir === 'TB' ? p1.x < p2.x : p1.y < p2.y;
|
||||
const branchToGetCurve = commitBIsFurthest ? commitB.branch : commitA.branch;
|
||||
const isOnBranchToGetCurve = (x) => x.branch === branchToGetCurve;
|
||||
const isBetweenCommits = (x) => x.seq > commitA.seq && x.seq < commitB.seq;
|
||||
return Object.values(allCommits).some((commitX) => {
|
||||
return isBetweenCommits(commitX) && isOnBranchToGetCurve(commitX);
|
||||
});
|
||||
|
||||
return overlappingComits.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -388,49 +396,61 @@ const findLane = (y1, y2, depth = 0) => {
|
||||
* Draw the lines between the commits. They were arrows initially.
|
||||
*
|
||||
* @param {any} svg
|
||||
* @param {any} commit1
|
||||
* @param {any} commit2
|
||||
* @param {any} commitA
|
||||
* @param {any} commitB
|
||||
* @param {any} allCommits
|
||||
*/
|
||||
const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
const p1 = commitPos[commit1.id];
|
||||
const p2 = commitPos[commit2.id];
|
||||
const overlappingCommits = hasOverlappingCommits(commit1, commit2, allCommits);
|
||||
// log.debug('drawArrow', p1, p2, overlappingCommits, commit1.id, commit2.id);
|
||||
const drawArrow = (svg, commitA, commitB, allCommits) => {
|
||||
const p1 = commitPos[commitA.id]; // arrowStart
|
||||
const p2 = commitPos[commitB.id]; // arrowEnd
|
||||
const arrowNeedsRerouting = shouldRerouteArrow(commitA, commitB, p1, p2, allCommits);
|
||||
// log.debug('drawArrow', p1, p2, arrowNeedsRerouting, commitA.id, commitB.id);
|
||||
|
||||
// Lower-right quadrant logic; top-left is 0,0
|
||||
|
||||
let arc = '';
|
||||
let arc2 = '';
|
||||
let radius = 0;
|
||||
let offset = 0;
|
||||
let colorClassNum = branchPos[commit2.branch].index;
|
||||
let colorClassNum = branchPos[commitB.branch].index;
|
||||
let lineDef;
|
||||
if (overlappingCommits) {
|
||||
if (arrowNeedsRerouting) {
|
||||
arc = 'A 10 10, 0, 0, 0,';
|
||||
arc2 = 'A 10 10, 0, 0, 1,';
|
||||
radius = 10;
|
||||
offset = 10;
|
||||
// Figure out the color of the arrow,arrows going down take the color from the destination branch
|
||||
colorClassNum = branchPos[commit2.branch].index;
|
||||
|
||||
const lineY = p1.y < p2.y ? findLane(p1.y, p2.y) : findLane(p2.y, p1.y);
|
||||
const lineX = p1.x < p2.x ? findLane(p1.x, p2.x) : findLane(p2.x, p1.x);
|
||||
|
||||
if (dir === 'TB') {
|
||||
if (p1.x < p2.x) {
|
||||
// Source commit is on branch position left of destination commit
|
||||
// so render arrow rightward with colour of destination branch
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc2} ${lineX} ${
|
||||
p1.y + offset
|
||||
} L ${lineX} ${p2.y - radius} ${arc} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
// Source commit is on branch position right of destination commit
|
||||
// so render arrow leftward with colour of source branch
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc} ${lineX} ${
|
||||
p1.y + offset
|
||||
} L ${lineX} ${p2.y - radius} ${arc2} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
} else {
|
||||
if (p1.y < p2.y) {
|
||||
// Source commit is on branch positioned above destination commit
|
||||
// so render arrow downward with colour of destination branch
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
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 {
|
||||
// Source commit is on branch positioned below destination commit
|
||||
// so render arrow upward with colour of source branch
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
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}`;
|
||||
@@ -445,7 +465,7 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
offset = 20;
|
||||
|
||||
// Figure out the color of the arrow,arrows going down take the color from the destination branch
|
||||
colorClassNum = branchPos[commit2.branch].index;
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
|
||||
p1.y + offset
|
||||
@@ -458,14 +478,14 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
offset = 20;
|
||||
|
||||
// Arrows going up take the color from the source branch
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
|
||||
if (p1.x === p2.x) {
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x + radius} ${p1.y} ${arc} ${p1.x + offset} ${
|
||||
p2.y + radius
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
@@ -475,10 +495,8 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
|
||||
// Figure out the color of the arrow,arrows going down take the color from the destination branch
|
||||
colorClassNum = branchPos[commit2.branch].index;
|
||||
|
||||
// Arrows going up take the color from the target branch
|
||||
colorClassNum = branchPos[commitB.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}`;
|
||||
@@ -487,16 +505,15 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
|
||||
// Arrows going up take the color from the source branch
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.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}`;
|
||||
}
|
||||
|
||||
if (p1.y === p2.y) {
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.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}`;
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
import type { Info } from 'mermaid-parser';
|
||||
import { parse } from 'mermaid-parser';
|
||||
import type { Info } from '@mermaid-js/parser';
|
||||
import { parse } from '@mermaid-js/parser';
|
||||
|
||||
import { log } from '../../logger.js';
|
||||
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Pie } from 'mermaid-parser';
|
||||
import { parse } from 'mermaid-parser';
|
||||
import type { Pie } from '@mermaid-js/parser';
|
||||
import { parse } from '@mermaid-js/parser';
|
||||
import { log } from '../../logger.js';
|
||||
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||
import { populateCommonDb } from '../common/populateCommonDb.js';
|
||||
|
||||
@@ -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,24 @@ const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Section>[] => {
|
||||
*/
|
||||
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 + ')');
|
||||
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<D3Section>> = arc<d3.PieArcDatum<D3Section>>()
|
||||
.innerRadius(0)
|
||||
@@ -175,6 +165,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 {
|
||||
|
||||
@@ -4,7 +4,10 @@ import { defineConfig, MarkdownOptions } from 'vitepress';
|
||||
|
||||
const allMarkdownTransformers: MarkdownOptions = {
|
||||
// the shiki theme to highlight code blocks
|
||||
theme: 'github-dark',
|
||||
theme: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
config: async (md) => {
|
||||
await MermaidExample(md);
|
||||
},
|
||||
|
||||
@@ -328,15 +328,17 @@ module.exports = (options) ->
|
||||
|
||||
## Advanced usage
|
||||
|
||||
**Syntax validation without rendering (Work in Progress)**
|
||||
### Syntax validation without rendering
|
||||
|
||||
The **mermaid.parse(txt)** function validates graph definitions without rendering a graph. **[This function is still a work in progress](https://github.com/mermaid-js/mermaid/issues/1066), find alternatives below.**
|
||||
The `mermaid.parse(text, parseOptions)` function validates graph definitions without rendering a graph.
|
||||
|
||||
The function **mermaid.parse(txt)**, takes a text string as an argument and returns true if the definition follows mermaid's syntax and
|
||||
false if it does not. The parseError function will be called when the parse function returns false.
|
||||
The function `mermaid.parse(text, parseOptions)`, takes a text string as an argument and returns `{ diagramType: string }` if the definition follows mermaid's syntax.
|
||||
|
||||
When the parser encounters invalid syntax the **mermaid.parseError** function is called. It is possible to override this
|
||||
function in order to handle the error in an application-specific way.
|
||||
If the definition is invalid, the function returns `false` if `parseOptions.suppressErrors` is set to `true`. Otherwise, it throws an error.
|
||||
|
||||
The parseError function will be called when the parse function throws an error. It will not be called if `parseOptions.suppressErrors` is set to `true`.
|
||||
|
||||
It is possible to override this function in order to handle the error in an application-specific way.
|
||||
|
||||
The code-example below in meta code illustrates how this could work:
|
||||
|
||||
@@ -356,26 +358,10 @@ const textFieldUpdated = async function () {
|
||||
bindEventHandler('change', 'code', textFieldUpdated);
|
||||
```
|
||||
|
||||
**Alternative to mermaid.parse():**
|
||||
One effective and more future-proof method of validating your graph definitions, is to paste and render them via the [Mermaid Live Editor](https://mermaid.live/). This will ensure that your code is compliant with the syntax of Mermaid's most recent version.
|
||||
|
||||
## Configuration
|
||||
|
||||
Mermaid takes a number of options which lets you tweak the rendering of the diagrams. Currently there are three ways of
|
||||
setting the options in mermaid.
|
||||
|
||||
1. Instantiation of the configuration using the initialize call
|
||||
2. _Using the global mermaid object_ - **Deprecated**
|
||||
3. _using the global mermaid_config object_ - **Deprecated**
|
||||
4. Instantiation of the configuration using the **mermaid.init** call- **Deprecated**
|
||||
|
||||
The list above has two ways too many of doing this. Three are deprecated and will eventually be removed. The list of
|
||||
configuration objects are described [in the mermaidAPI documentation](./setup/README.md).
|
||||
|
||||
## Using the `mermaidAPI.initialize`/`mermaid.initialize` call
|
||||
|
||||
The future proof way of setting the configuration is by using the initialization call to mermaid or mermaidAPI depending
|
||||
on what kind of integration you use.
|
||||
You can pass the required configuration to the `mermaid.initialize` call. This is the preferred way of configuring mermaid.
|
||||
The list of configuration objects are described [in the mermaidAPI documentation](./setup/README.md).
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
@@ -406,34 +392,3 @@ mermaid.startOnLoad = true;
|
||||
```warning
|
||||
This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
|
||||
```
|
||||
|
||||
## Using the mermaid_config
|
||||
|
||||
It is possible to set some configuration via the mermaid object. The two parameters that are supported using this
|
||||
approach are:
|
||||
|
||||
- mermaid_config.startOnLoad
|
||||
- mermaid_config.htmlLabels
|
||||
|
||||
```javascript
|
||||
mermaid_config.startOnLoad = true;
|
||||
```
|
||||
|
||||
```warning
|
||||
This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
|
||||
```
|
||||
|
||||
## Using the mermaid.init call
|
||||
|
||||
To set some configuration via the mermaid object. The two parameters that are supported using this approach are:
|
||||
|
||||
- mermaid_config.startOnLoad
|
||||
- mermaid_config.htmlLabels
|
||||
|
||||
```javascript
|
||||
mermaid_config.startOnLoad = true;
|
||||
```
|
||||
|
||||
```warning
|
||||
This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
@@ -177,7 +179,7 @@ Communication tools and platforms
|
||||
### Document Generation
|
||||
|
||||
- [Docusaurus](https://docusaurus.io/docs/markdown-features/diagrams) ✅
|
||||
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts)
|
||||
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-)
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/)
|
||||
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
|
||||
- [remark](https://remark.js.org/)
|
||||
@@ -232,5 +234,5 @@ Communication tools and platforms
|
||||
- [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
|
||||
- [NiceGUI: Let any browser be the frontend of your Python code](https://nicegui.io)
|
||||
- [ui.mermaid(...)](https://nicegui.io/reference#mermaid_diagrams)
|
||||
- [ui.markdown(..., extras=['mermaid'])](https://nicegui.io/reference#markdown_element)
|
||||
- [ui.mermaid(...)](https://nicegui.io/documentation/section_text_elements#markdown_element)
|
||||
- [ui.markdown(..., extras=['mermaid'])](https://nicegui.io/documentation/section_text_elements#mermaid_diagrams)
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
# Announcements
|
||||
|
||||
<br />
|
||||
Check out our latest blog posts below. See more blog posts [here](blog.md).
|
||||
|
||||
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
|
||||
|
||||
## Calling all fans of Mermaid and Mermaid Chart! 🎉
|
||||
14 November 2023 · 5 mins
|
||||
|
||||
We’ve officially made our Product Hunt debut, and would love any and all support from the community!
|
||||
|
||||
[Click here](https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart) to check out our Product Hunt launch.
|
||||
|
||||
Feel free to drop us a comment and let us know what you think. All new sign ups will receive a 30-day free trial of our Pro subscription, plus 25% off your first year.
|
||||
|
||||
We’re on a mission to make text-based diagramming fun again. And we need your help to make that happen.
|
||||
|
||||
Your support means the world to us. Thank you for being part of the diagramming movement.
|
||||
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# Blog
|
||||
|
||||
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
|
||||
|
||||
14 November 2023 · 5 mins
|
||||
|
||||
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.
|
||||
|
||||
## [How to Use Mermaid Chart as an AI Diagram Generator](https://www.mermaidchart.com/blog/posts/how-to-use-mermaid-chart-as-an-ai-diagram-generator/)
|
||||
|
||||
1 November 2023 · 5 mins
|
||||
|
||||
Would an AI diagram generator make your life easier?
|
||||
|
||||
## [Diagrams, Made Even Easier: Introducing “Code Snippets” in the Mermaid Chart Editor](https://www.mermaidchart.com/blog/posts/easier-diagram-editing-with-code-snippets/)
|
||||
|
||||
12 October 2023 · 4 mins
|
||||
|
||||
Mermaid Chart introduces Code Snippets in its editor, streamlining the diagramming process for developers and professionals.
|
||||
|
||||
## [How to Make a Git Graph with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-git-graph-with-mermaid-chart/)
|
||||
|
||||
22 September 2023 · 7 mins
|
||||
|
||||
@@ -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",
|
||||
"@vite-pwa/vitepress": "^0.2.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",
|
||||
"vite-plugin-pwa": "^0.16.0",
|
||||
"vitepress": "1.0.0-rc.25",
|
||||
"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.31",
|
||||
"workbox-window": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,24 +244,29 @@ 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:
|
||||
|
||||
```mermaid-example
|
||||
gitGraph
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
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"
|
||||
checkout release
|
||||
cherry-pick id:"MERGE" parent:"B"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
```
|
||||
|
||||
## Gitgraph specific configuration options
|
||||
|
||||
@@ -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 () => {
|
||||
@@ -89,7 +89,7 @@ describe('when using mermaid and ', () => {
|
||||
).resolves.not.toThrow();
|
||||
// should still render, even if lazyLoadedDiagrams fails
|
||||
expect(mermaidAPI.render).toHaveBeenCalled();
|
||||
});
|
||||
}, 20_000);
|
||||
|
||||
it('should defer diagram load based on parameter', async () => {
|
||||
let loaded = false;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { dedent } from 'ts-dedent';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
import { log } from './logger.js';
|
||||
import utils from './utils.js';
|
||||
import type { ParseOptions, RenderResult } from './mermaidAPI.js';
|
||||
import type { ParseOptions, ParseResult, RenderResult } from './mermaidAPI.js';
|
||||
import { mermaidAPI } from './mermaidAPI.js';
|
||||
import { registerLazyLoadedDiagrams, detectType } from './diagram-api/detectType.js';
|
||||
import { loadRegisteredDiagrams } from './diagram-api/loadDiagram.js';
|
||||
@@ -15,6 +15,7 @@ import { isDetailedError } from './utils.js';
|
||||
import type { DetailedError } from './utils.js';
|
||||
import type { ExternalDiagramDefinition } from './diagram-api/types.js';
|
||||
import type { UnknownDiagramError } from './errors.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
|
||||
export type {
|
||||
MermaidConfig,
|
||||
@@ -23,6 +24,7 @@ export type {
|
||||
ParseErrorFunction,
|
||||
RenderResult,
|
||||
ParseOptions,
|
||||
ParseResult,
|
||||
UnknownDiagramError,
|
||||
};
|
||||
|
||||
@@ -243,6 +245,7 @@ const registerExternalDiagrams = async (
|
||||
lazyLoad?: boolean;
|
||||
} = {}
|
||||
) => {
|
||||
addDiagrams();
|
||||
registerLazyLoadedDiagrams(...diagrams);
|
||||
if (lazyLoad === false) {
|
||||
await loadRegisteredDiagrams();
|
||||
@@ -311,11 +314,23 @@ const executeQueue = async () => {
|
||||
/**
|
||||
* Parse the text and validate the syntax.
|
||||
* @param text - The mermaid diagram definition.
|
||||
* @param parseOptions - Options for parsing.
|
||||
* @returns true if the diagram is valid, false otherwise if parseOptions.suppressErrors is true.
|
||||
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false.
|
||||
* @param parseOptions - Options for parsing. @see {@link ParseOptions}
|
||||
* @returns If valid, {@link ParseResult} otherwise `false` if parseOptions.suppressErrors is `true`.
|
||||
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* console.log(await mermaid.parse('flowchart \n a --> b'));
|
||||
* // { diagramType: 'flowchart-v2' }
|
||||
* console.log(await mermaid.parse('wrong \n a --> b', { suppressErrors: true }));
|
||||
* // false
|
||||
* console.log(await mermaid.parse('wrong \n a --> b', { suppressErrors: false }));
|
||||
* // throws Error
|
||||
* console.log(await mermaid.parse('wrong \n a --> b'));
|
||||
* // throws Error
|
||||
* ```
|
||||
*/
|
||||
const parse = async (text: string, parseOptions?: ParseOptions): Promise<boolean | void> => {
|
||||
const parse: typeof mermaidAPI.parse = async (text, parseOptions) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// This promise will resolve when the render call is done.
|
||||
// It will be queued first and will be executed when it is first in line
|
||||
@@ -364,7 +379,7 @@ const parse = async (text: string, parseOptions?: ParseOptions): Promise<boolean
|
||||
* element will be removed when rendering is completed.
|
||||
* @returns Returns the SVG Definition and BindFunctions.
|
||||
*/
|
||||
const render = (id: string, text: string, container?: Element): Promise<RenderResult> => {
|
||||
const render: typeof mermaidAPI.render = (id, text, container) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// This promise will resolve when the mermaidAPI.render call is done.
|
||||
// It will be queued first and will be executed when it is first in line
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { vi } from 'vitest';
|
||||
import { vi, it, expect, describe, beforeEach } from 'vitest';
|
||||
|
||||
// -------------------------------------
|
||||
// Mocks and mocking
|
||||
@@ -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
|
||||
@@ -684,14 +685,21 @@ describe('mermaidAPI', () => {
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
it('resolves for valid definition', async () => {
|
||||
await expect(
|
||||
mermaidAPI.parse('graph TD;A--x|text including URL space|B;')
|
||||
).resolves.toBeTruthy();
|
||||
await expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"diagramType": "flowchart-v2",
|
||||
}
|
||||
`);
|
||||
});
|
||||
it('returns true for valid definition with silent option', async () => {
|
||||
await expect(
|
||||
mermaidAPI.parse('graph TD;A--x|text including URL space|B;', { suppressErrors: true })
|
||||
).resolves.toBe(true);
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"diagramType": "flowchart-v2",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -733,7 +741,8 @@ describe('mermaidAPI', () => {
|
||||
it('should set aria-roledscription to the diagram type AND should call addSVGa11yTitleDescription', async () => {
|
||||
const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo');
|
||||
const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription');
|
||||
await mermaidAPI.render(id, diagramText);
|
||||
const result = await mermaidAPI.render(id, diagramText);
|
||||
expect(result.diagramType).toBe(expectedDiagramType);
|
||||
expect(a11yDiagramInfo_spy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expectedDiagramType
|
||||
@@ -744,4 +753,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';
|
||||
|
||||
@@ -57,9 +57,19 @@ const DOMPURIFY_TAGS = ['foreignobject'];
|
||||
const DOMPURIFY_ATTR = ['dominant-baseline'];
|
||||
|
||||
export interface ParseOptions {
|
||||
/**
|
||||
* If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
|
||||
* The `parseError` function will not be called.
|
||||
*/
|
||||
suppressErrors?: boolean;
|
||||
}
|
||||
|
||||
export interface ParseResult {
|
||||
/**
|
||||
* The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
*/
|
||||
diagramType: string;
|
||||
}
|
||||
// This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type.
|
||||
export type D3Element = any;
|
||||
|
||||
@@ -68,6 +78,10 @@ export interface RenderResult {
|
||||
* The svg code for the rendered graph.
|
||||
*/
|
||||
svg: string;
|
||||
/**
|
||||
* The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
*/
|
||||
diagramType: string;
|
||||
/**
|
||||
* Bind function to be called after the svg has been inserted into the DOM.
|
||||
* This is necessary for adding event listeners to the elements in the svg.
|
||||
@@ -90,29 +104,29 @@ function processAndSetConfigs(text: string) {
|
||||
/**
|
||||
* Parse the text and validate the syntax.
|
||||
* @param text - The mermaid diagram definition.
|
||||
* @param parseOptions - Options for parsing.
|
||||
* @returns true if the diagram is valid, false otherwise if parseOptions.suppressErrors is true.
|
||||
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false.
|
||||
* @param parseOptions - Options for parsing. @see {@link ParseOptions}
|
||||
* @returns An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
||||
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
||||
*/
|
||||
|
||||
async function parse(text: string, parseOptions?: ParseOptions): Promise<boolean> {
|
||||
async function parse(
|
||||
text: string,
|
||||
parseOptions: ParseOptions & { suppressErrors: true }
|
||||
): Promise<ParseResult | false>;
|
||||
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult>;
|
||||
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult | false> {
|
||||
addDiagrams();
|
||||
|
||||
text = processAndSetConfigs(text).code;
|
||||
|
||||
try {
|
||||
await getDiagramFromText(text);
|
||||
const { code } = processAndSetConfigs(text);
|
||||
const diagram = await getDiagramFromText(code);
|
||||
return { diagramType: diagram.type };
|
||||
} catch (error) {
|
||||
if (parseOptions?.suppressErrors) {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// append !important; to each cssClass followed by a final !important, all enclosed in { }
|
||||
//
|
||||
/**
|
||||
* Create a CSS style that starts with the given class name, then the element,
|
||||
* with an enclosing block that has each of the cssClasses followed by !important;
|
||||
@@ -483,6 +497,7 @@ const render = async function (
|
||||
}
|
||||
|
||||
return {
|
||||
diagramType,
|
||||
svg: svgCode,
|
||||
bindFunctions: diag.db.bindFunctions,
|
||||
};
|
||||
@@ -519,6 +534,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.
|
||||
*
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
# - Use `meta:enum` to document enum values (from jsonschema2md)
|
||||
# - Use `tsType` to override the TypeScript type (from json-schema-to-typescript)
|
||||
# - If adding a new object to `MermaidConfig` (e.g. a new diagram type),
|
||||
# you may need to add it to `.build/jsonSchema.ts` and `src/docs.mts`
|
||||
# to get the docs/default values to generate properly.
|
||||
# you may need to add it to `.build/jsonSchema.ts`, `src/docs.mts`
|
||||
# and `scripts/create-types-from-json-schema.mjs`
|
||||
# to get the default values/docs/types to generate properly.
|
||||
$id: https://mermaid-js.github.io/schemas/config.schema.json
|
||||
$schema: https://json-schema.org/draft/2019-09/schema
|
||||
title: Mermaid Config
|
||||
@@ -1844,6 +1845,7 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
||||
unevaluatedProperties: false
|
||||
required:
|
||||
- titleTopMargin
|
||||
- subGraphTitleMargin
|
||||
- diagramPadding
|
||||
- htmlLabels
|
||||
- nodeSpacing
|
||||
@@ -1856,6 +1858,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:
|
||||
@@ -1966,10 +1982,12 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
||||
$ref: '#/$defs/SankeyNodeAlignment'
|
||||
default: justify
|
||||
useMaxWidth:
|
||||
type: boolean
|
||||
default: false
|
||||
showValues:
|
||||
description: |
|
||||
Toggle to display or hide values along with title.
|
||||
type: boolean
|
||||
default: true
|
||||
prefix:
|
||||
description: |
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -17,7 +17,6 @@ import theme from './themes/index.js';
|
||||
import c4 from './diagrams/c4/styles.js';
|
||||
import classDiagram from './diagrams/class/styles.js';
|
||||
import flowchart from './diagrams/flowchart/styles.js';
|
||||
import flowchartElk from './diagrams/flowchart/elk/styles.js';
|
||||
import er from './diagrams/er/styles.js';
|
||||
import git from './diagrams/git/styles.js';
|
||||
import gantt from './diagrams/gantt/styles.js';
|
||||
@@ -86,7 +85,6 @@ describe('styles', () => {
|
||||
classDiagram,
|
||||
er,
|
||||
flowchart,
|
||||
flowchartElk,
|
||||
gantt,
|
||||
git,
|
||||
journey,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
22
packages/mermaid/src/utils/subGraphTitleMargins.spec.ts
Normal file
22
packages/mermaid/src/utils/subGraphTitleMargins.spec.ts
Normal file
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
21
packages/mermaid/src/utils/subGraphTitleMargins.ts
Normal file
21
packages/mermaid/src/utils/subGraphTitleMargins.ts
Normal file
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ Mermaid Parser
|
||||
Mermaid parser package
|
||||
<p>
|
||||
|
||||
[](https://www.npmjs.com/package/mermaid-parser)
|
||||
[](https://www.npmjs.com/package/@mermaid-js/parser)
|
||||
|
||||
## How the package works
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid-parser",
|
||||
"version": "0.2.0",
|
||||
"name": "@mermaid-js/parser",
|
||||
"version": "0.1.0-rc.1",
|
||||
"description": "MermaidJS parser",
|
||||
"author": "Yokozuna59",
|
||||
"contributors": [
|
||||
|
||||
Reference in New Issue
Block a user