feat(arch): extended parser to control how edges pass through groups

This commit is contained in:
NicolasNewman
2024-05-09 14:33:44 -05:00
parent 48e6901936
commit 5b6c95cea3
6 changed files with 211 additions and 143 deletions

View File

@@ -1,23 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Architecture Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h1>Architecture diagram demo</h1>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Architecture Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<h2>Simple diagram with groups</h2>
<pre class="mermaid">
<body>
<h1>Architecture diagram demo</h1>
<h2>Simple diagram with groups</h2>
<pre class="mermaid">
architecture
group api(cloud)[API]
@@ -32,10 +33,10 @@
disk2 T--B db
server T--B gateway
</pre>
<hr />
<hr />
<h2>Groups within groups</h2>
<pre class="mermaid">
<h2>Groups within groups</h2>
<pre class="mermaid">
architecture
group api[API]
group public[Public API] in api
@@ -56,17 +57,17 @@
serv1 L--R gateway
</pre>
<hr />
<hr />
<h2>Default icon (?) from uknown icon name</h2>
<pre class="mermaid">
<h2>Default icon (?) from uknown icon name</h2>
<pre class="mermaid">
architecture
service unknown(iconnamedoesntexist)[Uknown Icon]
</pre>
<hr />
<hr />
<h2>Split Direction</h2>
<pre class="mermaid">
<h2>Split Direction</h2>
<pre class="mermaid">
architecture
service db(database)[Database]
service s3(disk)[Storage]
@@ -79,10 +80,10 @@
serv2 L--B s3
serv1 T--B disk
</pre>
<hr />
<hr />
<h2>Arrow Tests</h2>
<pre class="mermaid">
<h2>Arrow Tests</h2>
<pre class="mermaid">
architecture
service servC(server)[Server 1]
service servL(server)[Server 2]
@@ -100,7 +101,7 @@
servR (T--R) servT
servR (B--R) servB
</pre>
<pre class="mermaid">
<pre class="mermaid">
architecture
service servC(server)[Server 1]
service servL(server)[Server 2]
@@ -118,10 +119,32 @@
servT (R--T) servR
servB (R--B) servR
</pre>
<hr />
<hr />
<h2>Edge Label Test</h2>
<pre class="mermaid">
<h2>Group Edges</h2>
<pre class="mermaid">
architecture
group left_group(cloud)[Left]
group right_group(cloud)[Right]
group top_group(cloud)[Top]
group bottom_group(cloud)[Bottom]
group center_group(cloud)[Center]
service left_disk(disk)[Disk] in left_group
service right_disk(disk)[Disk] in right_group
service top_disk(disk)[Disk] in top_group
service bottom_disk(disk)[Disk] in bottom_group
service center_disk(disk)[Disk] in center_group
left_disk{group} (R--L) center_disk{group}
right_disk{group} (L--R) center_disk{group}
top_disk{group} (B--T) center_disk{group}
bottom_disk{group} (T--B) center_disk{group}
</pre>
<hr />
<h2>Edge Label Test</h2>
<pre class="mermaid">
architecture
service servC(server)[Server 1]
service servL(server)[Server 2]
@@ -139,7 +162,7 @@
servR T-[Label]-R servT
servR B-[Label]-R servB
</pre>
<pre class="mermaid">
<pre class="mermaid">
architecture
service servC(server)[Server 1]
service servL(server)[Server 2]
@@ -158,74 +181,75 @@
servR B-[Label that is Long]-R servB
</pre>
<hr />
<hr />
<script type="module">
import mermaid from './mermaid.esm.mjs';
<script type="module">
import mermaid from './mermaid.esm.mjs';
const ALLOWED_TAGS = [
'a',
'b',
'blockquote',
'br',
'dd',
'div',
'dl',
'dt',
'em',
'foreignObject',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'h7',
'h8',
'hr',
'i',
'li',
'ul',
'ol',
'p',
'pre',
'span',
'strike',
'strong',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr',
];
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'base',
startOnLoad: true,
logLevel: 0,
flowchart: {
useMaxWidth: false,
htmlLabels: true,
},
gantt: {
useMaxWidth: false,
},
architecture: {
iconSize: 80,
},
const ALLOWED_TAGS = [
'a',
'b',
'blockquote',
'br',
'dd',
'div',
'dl',
'dt',
'em',
'foreignObject',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'h7',
'h8',
'hr',
'i',
'li',
'ul',
'ol',
'p',
'pre',
'span',
'strike',
'strong',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr',
];
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'base',
startOnLoad: true,
logLevel: 0,
flowchart: {
useMaxWidth: false,
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
</html>
htmlLabels: true,
},
gantt: {
useMaxWidth: false,
},
architecture: {
iconSize: 80,
},
useMaxWidth: false,
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
</html>

View File

@@ -128,6 +128,8 @@ const addEdge = function ({
rhsDir,
lhsInto,
rhsInto,
lhsGroup,
rhsGroup,
title,
}: ArchitectureEdge) {
if (!isArchitectureDirection(lhsDir)) {
@@ -152,14 +154,29 @@ const addEdge = function ({
);
}
const lhsGroupId = state.records.services[lhsId].in
const rhsGroupId = state.records.services[rhsId].in
if (lhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
throw new Error(
`The left-hand id [${lhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
)
}
if (rhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
throw new Error(
`The right-hand id [${rhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
)
}
const edge = {
lhsId,
lhsDir,
lhsInto,
lhsGroup,
rhsId,
rhsDir,
title,
lhsInto,
rhsInto,
rhsGroup,
title,
};
state.records.edges.push(edge);

View File

@@ -84,7 +84,7 @@ function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) {
function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
edges.forEach((parsedEdge) => {
const { lhsId, rhsId, lhsInto, rhsInto, lhsDir, rhsDir, title } = parsedEdge;
const { lhsId, rhsId, lhsInto, lhsGroup, rhsInto, lhsDir, rhsDir, rhsGroup, title } = parsedEdge;
const edgeType = isArchitectureDirectionXY(parsedEdge.lhsDir, parsedEdge.rhsDir)
? 'segments'
: 'straight';
@@ -94,6 +94,7 @@ function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
source: lhsId,
sourceDir: lhsDir,
sourceArrow: lhsInto,
sourceGroup: lhsGroup,
sourceEndpoint:
lhsDir === 'L'
? '0 50%'
@@ -105,6 +106,7 @@ function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
target: rhsId,
targetDir: rhsDir,
targetArrow: rhsInto,
targetGroup: rhsGroup,
targetEndpoint:
rhsDir === 'L'
? '0 50%'

View File

@@ -199,11 +199,13 @@ export interface ArchitectureGroup {
export interface ArchitectureEdge {
lhsId: string;
lhsDir: ArchitectureDirection;
title?: string;
lhsInto?: boolean;
lhsGroup?: boolean;
rhsId: string;
rhsDir: ArchitectureDirection;
lhsInto?: boolean;
rhsInto?: boolean;
rhsGroup?: boolean;
title?: string;
}
export interface ArchitectureDB extends DiagramDB {
@@ -246,9 +248,11 @@ export type EdgeSingularData = {
source: string;
sourceDir: ArchitectureDirection;
sourceArrow?: boolean;
sourceGroup?: boolean;
target: string;
targetDir: ArchitectureDirection;
targetArrow?: boolean;
targetGroup?: boolean;
[key: string]: any;
};
@@ -274,23 +278,23 @@ export interface EdgeSingular extends cytoscape.EdgeSingular {
export type NodeSingularData =
| {
type: 'service';
id: string;
icon?: string;
label?: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
type: 'service';
id: string;
icon?: string;
label?: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
| {
type: 'group';
id: string;
icon?: string;
label?: string;
parent?: string;
[key: string]: any;
};
type: 'group';
id: string;
icon?: string;
label?: string;
parent?: string;
[key: string]: any;
};
export const nodeData = (node: cytoscape.NodeSingular) => {
return node.data() as NodeSingularData;

View File

@@ -22,17 +22,37 @@ import { getConfigField } from './architectureDb.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
export const drawEdges = function (edgesEl: D3Element, cy: cytoscape.Core) {
const padding = getConfigField('padding');
const iconSize = getConfigField('iconSize');
const arrowSize = iconSize / 6;
const halfArrowSize = arrowSize / 2;
cy.edges().map((edge, id) => {
const { sourceDir, sourceArrow, targetDir, targetArrow, label } = edgeData(edge);
const { x: startX, y: startY } = edge[0].sourceEndpoint();
const { sourceDir, sourceArrow, sourceGroup, targetDir, targetArrow, targetGroup, label } = edgeData(edge);
let { x: startX, y: startY } = edge[0].sourceEndpoint();
const { x: midX, y: midY } = edge[0].midpoint();
const { x: endX, y: endY } = edge[0].targetEndpoint();
let { x: endX, y: endY } = edge[0].targetEndpoint();
const groupEdgeShift = padding + 4;
// +18 comes from the service label height that extends the padding on the bottom side of each group
if (sourceGroup) {
if (isArchitectureDirectionX(sourceDir)) {
sourceDir === 'L' ? startX -= groupEdgeShift : startX += groupEdgeShift;
} else {
sourceDir === 'T' ? startY -= groupEdgeShift : startY += (groupEdgeShift + 18);
}
}
if (targetGroup) {
if (isArchitectureDirectionX(targetDir)) {
targetDir === 'L' ? endX -= groupEdgeShift : endX += groupEdgeShift;
} else {
targetDir === 'T' ? endY -= groupEdgeShift : endY += (groupEdgeShift + 18);
}
}
if (edge[0]._private.rscratch) {
const bounds = edge[0]._private.rscratch;
// const bounds = edge[0]._private.rscratch;
const g = edgesEl.insert('g');
@@ -42,11 +62,11 @@ export const drawEdges = function (edgesEl: D3Element, cy: cytoscape.Core) {
if (sourceArrow) {
const xShift = isArchitectureDirectionX(sourceDir)
? ArchitectureDirectionArrowShift[sourceDir](bounds.startX, arrowSize)
: bounds.startX - halfArrowSize;
? ArchitectureDirectionArrowShift[sourceDir](startX, arrowSize)
: startX - halfArrowSize;
const yShift = isArchitectureDirectionY(sourceDir)
? ArchitectureDirectionArrowShift[sourceDir](bounds.startY, arrowSize)
: bounds.startY - halfArrowSize;
? ArchitectureDirectionArrowShift[sourceDir](startY, arrowSize)
: startY - halfArrowSize;
g.insert('polygon')
.attr('points', ArchitectureDirectionArrow[sourceDir](arrowSize))
@@ -55,11 +75,11 @@ export const drawEdges = function (edgesEl: D3Element, cy: cytoscape.Core) {
}
if (targetArrow) {
const xShift = isArchitectureDirectionX(targetDir)
? ArchitectureDirectionArrowShift[targetDir](bounds.endX, arrowSize)
: bounds.endX - halfArrowSize;
? ArchitectureDirectionArrowShift[targetDir](endX, arrowSize)
: endX - halfArrowSize;
const yShift = isArchitectureDirectionY(targetDir)
? ArchitectureDirectionArrowShift[targetDir](bounds.endY, arrowSize)
: bounds.endY - halfArrowSize;
? ArchitectureDirectionArrowShift[targetDir](endY, arrowSize)
: endY - halfArrowSize;
g.insert('polygon')
.attr('points', ArchitectureDirectionArrow[targetDir](arrowSize))
@@ -165,10 +185,10 @@ export const drawGroups = function (groupsEl: D3Element, cy: cytoscape.Core) {
bkgElem.attr(
'transform',
'translate(' +
(shiftedX1 + halfIconSize + 1) +
', ' +
(shiftedY1 + halfIconSize + 1) +
')'
(shiftedX1 + halfIconSize + 1) +
', ' +
(shiftedY1 + halfIconSize + 1) +
')'
);
shiftedX1 += groupIconSize;
// TODO: test with more values
@@ -196,10 +216,10 @@ export const drawGroups = function (groupsEl: D3Element, cy: cytoscape.Core) {
textElem.attr(
'transform',
'translate(' +
(shiftedX1 + halfIconSize + 4) +
', ' +
(shiftedY1 + halfIconSize + 2) +
')'
(shiftedX1 + halfIconSize + 4) +
', ' +
(shiftedY1 + halfIconSize + 2) +
')'
);
}
}

View File

@@ -30,7 +30,7 @@ Service:
;
Edge:
lhsId=ARCH_ID Arrow rhsId=ARCH_ID EOL
lhsId=ARCH_ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ARCH_ID rhsGroup?=ARROW_GROUP? EOL
;
terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B';
@@ -38,4 +38,5 @@ terminal ARCH_ID: /[\w]+/;
terminal ARCH_TEXT_ICON: /\("[^"]+"\)/;
terminal ARCH_ICON: /\([\w]+\)/;
terminal ARCH_TITLE: /\[[\w ]+\]/;
terminal ARROW_GROUP: /\{group\}/;
terminal ARROW_INTO: /\(|\)/;