mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-12 10:39:44 +02:00
Compare commits
1 Commits
architectu
...
subgraph-t
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1a9d45abf0 |
5
.changeset/busy-mirrors-try.md
Normal file
5
.changeset/busy-mirrors-try.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Resolved parsing error where direction TD was not recognized within subgraphs
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -26,8 +26,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: ['javascript', 'actions']
|
language: ['javascript']
|
||||||
# CodeQL supports [ 'actions', 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@@ -973,4 +973,19 @@ graph TD
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('70: should render a subgraph with direction TD', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
flowchart LR
|
||||||
|
subgraph A
|
||||||
|
direction TD
|
||||||
|
a --> b
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
fontFamily: 'courier',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -3,9 +3,7 @@ import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
|||||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||||
import type { D3Element } from '../../types.js';
|
import type { D3Element } from '../../types.js';
|
||||||
import { cleanAndMerge, getEdgeId } from '../../utils.js';
|
import { cleanAndMerge } from '../../utils.js';
|
||||||
import type { LayoutData, Node, Edge } from '../../rendering-util/types.js';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clear as commonClear,
|
clear as commonClear,
|
||||||
getAccDescription,
|
getAccDescription,
|
||||||
@@ -353,147 +351,15 @@ export class ArchitectureDB implements DiagramDB {
|
|||||||
public getDiagramTitle = getDiagramTitle;
|
public getDiagramTitle = getDiagramTitle;
|
||||||
public getAccDescription = getAccDescription;
|
public getAccDescription = getAccDescription;
|
||||||
public setAccDescription = setAccDescription;
|
public setAccDescription = setAccDescription;
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts architecture diagram data to LayoutData format for unified rendering
|
|
||||||
*/
|
|
||||||
public getData(): LayoutData {
|
|
||||||
const config = commonGetConfig();
|
|
||||||
const nodes: Node[] = [];
|
|
||||||
const edges: Edge[] = [];
|
|
||||||
|
|
||||||
const groups = this.getGroups();
|
|
||||||
for (const group of groups) {
|
|
||||||
const padding = this.getConfigField('padding');
|
|
||||||
const fontSize = this.getConfigField('fontSize');
|
|
||||||
|
|
||||||
const groupWidth = 200;
|
|
||||||
let groupHeight = 150;
|
|
||||||
|
|
||||||
if (group.title || group.icon) {
|
|
||||||
groupHeight += fontSize + padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes.push({
|
|
||||||
id: group.id,
|
|
||||||
label: group.title,
|
|
||||||
parentId: group.in,
|
|
||||||
isGroup: true,
|
|
||||||
shape: 'rect',
|
|
||||||
icon: group.icon,
|
|
||||||
width: groupWidth,
|
|
||||||
height: groupHeight,
|
|
||||||
padding: padding,
|
|
||||||
cssClasses: 'architecture-group',
|
|
||||||
cssCompiledStyles: [
|
|
||||||
'stroke: #cccccc',
|
|
||||||
'stroke-width: 2px',
|
|
||||||
'stroke-dasharray: 8,8',
|
|
||||||
'fill: transparent',
|
|
||||||
],
|
|
||||||
labelStyle: '',
|
|
||||||
look: config.look || 'classic',
|
|
||||||
rx: 5,
|
|
||||||
ry: 5,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const services = this.getServices();
|
|
||||||
for (const service of services) {
|
|
||||||
const iconSize = this.getConfigField('iconSize');
|
|
||||||
let nodeWidth = iconSize;
|
|
||||||
let nodeHeight = iconSize;
|
|
||||||
|
|
||||||
if (service.title) {
|
|
||||||
nodeHeight += iconSize * 0.3;
|
|
||||||
nodeWidth = Math.max(nodeWidth, iconSize * 1.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes.push({
|
|
||||||
id: service.id,
|
|
||||||
label: service.title,
|
|
||||||
parentId: service.in,
|
|
||||||
isGroup: false,
|
|
||||||
shape: service.icon || (service as any).iconText ? 'icon' : 'squareRect',
|
|
||||||
icon: service.icon ? `mermaid-architecture:${service.icon}` : 'mermaid-architecture:blank',
|
|
||||||
width: service.width || nodeWidth,
|
|
||||||
height: service.height || nodeHeight,
|
|
||||||
cssClasses: 'architecture-service',
|
|
||||||
look: config.look,
|
|
||||||
padding: this.getConfigField('padding') / 4,
|
|
||||||
description: (service as any).iconText ? [(service as any).iconText] : undefined,
|
|
||||||
assetWidth: iconSize,
|
|
||||||
assetHeight: iconSize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const junctions = this.getJunctions();
|
|
||||||
for (const junction of junctions) {
|
|
||||||
nodes.push({
|
|
||||||
id: junction.id,
|
|
||||||
parentId: junction.in,
|
|
||||||
isGroup: false,
|
|
||||||
shape: 'squareRect',
|
|
||||||
width: 2,
|
|
||||||
height: 2,
|
|
||||||
cssClasses: 'architecture-junction',
|
|
||||||
look: config.look,
|
|
||||||
type: 'junction' as any,
|
|
||||||
padding: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const architectureEdges = this.getEdges();
|
|
||||||
let edgeCounter = 0;
|
|
||||||
for (const edge of architectureEdges) {
|
|
||||||
const edgeData = {
|
|
||||||
id: getEdgeId(edge.lhsId, edge.rhsId, { counter: edgeCounter, prefix: 'L' }),
|
|
||||||
start: edge.lhsId,
|
|
||||||
end: edge.rhsId,
|
|
||||||
source: edge.lhsId,
|
|
||||||
target: edge.rhsId,
|
|
||||||
label: edge.title || '',
|
|
||||||
labelpos: 'c',
|
|
||||||
type: 'normal',
|
|
||||||
minlen: 2,
|
|
||||||
weight: 1,
|
|
||||||
classes: 'edge-thickness-normal edge-pattern-solid architecture-edge',
|
|
||||||
look: config.look || 'classic',
|
|
||||||
curve: 'linear',
|
|
||||||
arrowTypeStart: edge.lhsInto ? 'point' : 'none',
|
|
||||||
arrowTypeEnd: edge.rhsInto ? 'point' : 'none',
|
|
||||||
arrowheadStyle: 'fill: #333',
|
|
||||||
thickness: 'normal',
|
|
||||||
pattern: 'solid',
|
|
||||||
style: ['stroke: #333333', 'stroke-width: 3px', 'fill: none'],
|
|
||||||
cssCompiledStyles: [],
|
|
||||||
labelStyle: [],
|
|
||||||
lhsDir: edge.lhsDir,
|
|
||||||
rhsDir: edge.rhsDir,
|
|
||||||
lhsInto: edge.lhsInto,
|
|
||||||
rhsInto: edge.rhsInto,
|
|
||||||
lhsGroup: edge.lhsGroup,
|
|
||||||
rhsGroup: edge.rhsGroup,
|
|
||||||
} as Edge & {
|
|
||||||
lhsDir: any;
|
|
||||||
rhsDir: any;
|
|
||||||
lhsInto?: boolean;
|
|
||||||
rhsInto?: boolean;
|
|
||||||
lhsGroup?: boolean;
|
|
||||||
rhsGroup?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
edges.push(edgeData);
|
|
||||||
edgeCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
config,
|
|
||||||
dataStructures: this.getDataStructures(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed wrapper for resolving an architecture diagram's config fields. Returns the default value if undefined
|
||||||
|
* @param field - the config field to access
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
|
||||||
|
// field: T
|
||||||
|
// ): Required<ArchitectureDiagramConfig>[T] {
|
||||||
|
// return db.getConfig()[field];
|
||||||
|
// }
|
||||||
|
@@ -2,7 +2,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
|
|||||||
import { parser } from './architectureParser.js';
|
import { parser } from './architectureParser.js';
|
||||||
import { ArchitectureDB } from './architectureDb.js';
|
import { ArchitectureDB } from './architectureDb.js';
|
||||||
import styles from './architectureStyles.js';
|
import styles from './architectureStyles.js';
|
||||||
import { renderer } from './architectureRenderer-unified.js';
|
import { renderer } from './architectureRenderer.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
parser,
|
parser,
|
||||||
|
@@ -1,50 +0,0 @@
|
|||||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
|
||||||
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
|
|
||||||
import { log } from '../../logger.js';
|
|
||||||
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
|
||||||
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
|
|
||||||
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
|
||||||
import type { LayoutData } from '../../rendering-util/types.js';
|
|
||||||
import utils from '../../utils.js';
|
|
||||||
|
|
||||||
import { registerIconPacks } from '../../rendering-util/icons.js';
|
|
||||||
import { architectureIcons } from './architectureIcons.js';
|
|
||||||
|
|
||||||
export const getClasses = function (
|
|
||||||
_text: string,
|
|
||||||
_diagramObj: any
|
|
||||||
): Map<string, DiagramStyleClassDef> {
|
|
||||||
return new Map();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const draw = async function (_text: string, id: string, _version: string, diag: any) {
|
|
||||||
registerIconPacks([
|
|
||||||
{
|
|
||||||
name: architectureIcons.prefix,
|
|
||||||
icons: architectureIcons,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const { securityLevel, architecture: conf, layout } = getConfig();
|
|
||||||
|
|
||||||
const data4Layout = diag.db.getData() as LayoutData;
|
|
||||||
|
|
||||||
const svg = getDiagramElement(id, securityLevel);
|
|
||||||
|
|
||||||
data4Layout.type = diag.type;
|
|
||||||
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout, { fallback: 'dagre' });
|
|
||||||
|
|
||||||
data4Layout.nodeSpacing = 100;
|
|
||||||
data4Layout.rankSpacing = 100;
|
|
||||||
data4Layout.markers = ['point'];
|
|
||||||
data4Layout.diagramId = id;
|
|
||||||
|
|
||||||
log.debug('Architecture layout data:', data4Layout);
|
|
||||||
await render(data4Layout, svg);
|
|
||||||
|
|
||||||
const padding = conf?.padding ?? 8;
|
|
||||||
utils.insertTitle(svg, 'architectureTitleText', 0, diag.db.getDiagramTitle());
|
|
||||||
|
|
||||||
setupViewPortForSVG(svg, padding, 'architecture', conf?.useMaxWidth ?? true);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderer = { draw };
|
|
@@ -2,7 +2,6 @@ import type { DiagramDBBase } from '../../diagram-api/types.js';
|
|||||||
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||||
import type { D3Element } from '../../types.js';
|
import type { D3Element } from '../../types.js';
|
||||||
import type cytoscape from 'cytoscape';
|
import type cytoscape from 'cytoscape';
|
||||||
import type { LayoutData } from '../../rendering-util/types.js';
|
|
||||||
|
|
||||||
/*=======================================*\
|
/*=======================================*\
|
||||||
| Architecture Diagram Types |
|
| Architecture Diagram Types |
|
||||||
@@ -257,8 +256,7 @@ export interface ArchitectureDB extends DiagramDBBase<ArchitectureDiagramConfig>
|
|||||||
getEdges: () => ArchitectureEdge[];
|
getEdges: () => ArchitectureEdge[];
|
||||||
setElementForId: (id: string, element: D3Element) => void;
|
setElementForId: (id: string, element: D3Element) => void;
|
||||||
getElementById: (id: string) => D3Element;
|
getElementById: (id: string) => D3Element;
|
||||||
getData: () => LayoutData;
|
getDataStructures: () => ArchitectureDataStructures;
|
||||||
getDirection: () => string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
|
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
|
||||||
|
@@ -140,6 +140,7 @@ that id.
|
|||||||
.*direction\s+BT[^\n]* return 'direction_bt';
|
.*direction\s+BT[^\n]* return 'direction_bt';
|
||||||
.*direction\s+RL[^\n]* return 'direction_rl';
|
.*direction\s+RL[^\n]* return 'direction_rl';
|
||||||
.*direction\s+LR[^\n]* return 'direction_lr';
|
.*direction\s+LR[^\n]* return 'direction_lr';
|
||||||
|
.*direction\s+TD[^\n]* return 'direction_td';
|
||||||
|
|
||||||
[^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; }
|
[^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; }
|
||||||
[0-9]+ return 'NUM';
|
[0-9]+ return 'NUM';
|
||||||
@@ -626,6 +627,8 @@ direction
|
|||||||
{ $$={stmt:'dir', value:'RL'};}
|
{ $$={stmt:'dir', value:'RL'};}
|
||||||
| direction_lr
|
| direction_lr
|
||||||
{ $$={stmt:'dir', value:'LR'};}
|
{ $$={stmt:'dir', value:'LR'};}
|
||||||
|
| direction_td
|
||||||
|
{ $$={stmt:'dir', value:'TD'};}
|
||||||
;
|
;
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@@ -309,4 +309,21 @@ describe('when parsing subgraphs', function () {
|
|||||||
expect(subgraphA.nodes).toContain('a');
|
expect(subgraphA.nodes).toContain('a');
|
||||||
expect(subgraphA.nodes).not.toContain('c');
|
expect(subgraphA.nodes).not.toContain('c');
|
||||||
});
|
});
|
||||||
|
it('should correctly parse direction TD inside a subgraph', function () {
|
||||||
|
const res = flow.parser.parse(`
|
||||||
|
graph LR
|
||||||
|
subgraph WithTD
|
||||||
|
direction TD
|
||||||
|
A1 --> A2
|
||||||
|
end
|
||||||
|
`);
|
||||||
|
|
||||||
|
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||||
|
expect(subgraphs.length).toBe(1);
|
||||||
|
const subgraph = subgraphs[0];
|
||||||
|
|
||||||
|
expect(subgraph.dir).toBe('TD');
|
||||||
|
expect(subgraph.nodes).toContain('A1');
|
||||||
|
expect(subgraph.nodes).toContain('A2');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user