mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-27 04:06:39 +02:00
Compare commits
24 Commits
@mermaid-j
...
chore/fix_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3e57d35aee | ||
![]() |
801bdea300 | ||
![]() |
ef572bb859 | ||
![]() |
77a16fc3fa | ||
![]() |
a80171a7ce | ||
![]() |
c5106a1d1f | ||
![]() |
df636c6d0a | ||
![]() |
64554a6c60 | ||
![]() |
54d485f173 | ||
![]() |
bff32827b5 | ||
![]() |
65f9b29b86 | ||
![]() |
b4879d13b8 | ||
![]() |
95964b5487 | ||
![]() |
4e17da0a30 | ||
![]() |
2a91849a38 | ||
![]() |
082de76eef | ||
![]() |
570ae78b15 | ||
![]() |
885ac6f947 | ||
![]() |
193fdb225e | ||
![]() |
7cbd80af33 | ||
![]() |
16c448b89b | ||
![]() |
cb0a4703bd | ||
![]() |
8cb1c68166 | ||
![]() |
d752240efc |
5
.changeset/angry-bags-brake.md
Normal file
5
.changeset/angry-bags-brake.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: architecture diagrams no longer grow to extreme heights due to conflicting alignments
|
5
.changeset/lucky-points-wave.md
Normal file
5
.changeset/lucky-points-wave.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Rename internal fields for class diagrams to match documentation and usage
|
@@ -1 +1 @@
|
|||||||
./packages/mermaid/src/docs/community/contributing.md
|
./packages/mermaid/src/docs/community/contributing.md
|
||||||
|
@@ -171,6 +171,58 @@ describe.skip('architecture diagram', () => {
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render an architecture diagram with a resonable height', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
group federated(cloud)[Federated Environment]
|
||||||
|
service server1(server)[System] in federated
|
||||||
|
service edge(server)[Edge Device] in federated
|
||||||
|
server1:R -- L:edge
|
||||||
|
|
||||||
|
group on_prem(cloud)[Hub]
|
||||||
|
service firewall(server)[Firewall Device] in on_prem
|
||||||
|
service server(server)[Server] in on_prem
|
||||||
|
firewall:R -- L:server
|
||||||
|
|
||||||
|
service db1(database)[db1] in on_prem
|
||||||
|
service db2(database)[db2] in on_prem
|
||||||
|
service db3(database)[db3] in on_prem
|
||||||
|
service db4(database)[db4] in on_prem
|
||||||
|
service db5(database)[db5] in on_prem
|
||||||
|
service db6(database)[db6] in on_prem
|
||||||
|
|
||||||
|
junction mid in on_prem
|
||||||
|
server:B -- T:mid
|
||||||
|
|
||||||
|
junction 1Leftofmid in on_prem
|
||||||
|
1Leftofmid:R -- L:mid
|
||||||
|
1Leftofmid:B -- T:db1
|
||||||
|
|
||||||
|
junction 2Leftofmid in on_prem
|
||||||
|
2Leftofmid:R -- L:1Leftofmid
|
||||||
|
2Leftofmid:B -- T:db2
|
||||||
|
|
||||||
|
junction 3Leftofmid in on_prem
|
||||||
|
3Leftofmid:R -- L:2Leftofmid
|
||||||
|
3Leftofmid:B -- T:db3
|
||||||
|
|
||||||
|
junction 1RightOfMid in on_prem
|
||||||
|
mid:R -- L:1RightOfMid
|
||||||
|
1RightOfMid:B -- T:db4
|
||||||
|
|
||||||
|
junction 2RightOfMid in on_prem
|
||||||
|
1RightOfMid:R -- L:2RightOfMid
|
||||||
|
2RightOfMid:B -- T:db5
|
||||||
|
|
||||||
|
junction 3RightOfMid in on_prem
|
||||||
|
2RightOfMid:R -- L:3RightOfMid
|
||||||
|
3RightOfMid:B -- T:db6
|
||||||
|
|
||||||
|
edge:R -- L:firewall
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
|
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
|
||||||
|
@@ -650,12 +650,12 @@ class Class10
|
|||||||
{ logLevel: 1, htmlLabels: true, layout: 'elk' }
|
{ logLevel: 1, htmlLabels: true, layout: 'elk' }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('ELK: should render a class with a text label, members and annotation', () => {
|
it('ELK: should render a class with a text label, attribute and annotation', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
<<interface>>
|
<<interface>>
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2`,
|
C1 --> C2`,
|
||||||
{ logLevel: 1, htmlLabels: true, layout: 'elk' }
|
{ logLevel: 1, htmlLabels: true, layout: 'elk' }
|
||||||
|
@@ -650,12 +650,12 @@ class Class10
|
|||||||
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('HD: should render a class with a text label, members and annotation', () => {
|
it('HD: should render a class with a text label, attribute and annotation', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
<<interface>>
|
<<interface>>
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2`,
|
C1 --> C2`,
|
||||||
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
||||||
|
@@ -500,12 +500,12 @@ class Class10
|
|||||||
C1 --> C2`
|
C1 --> C2`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should render a class with a text label, members and annotation', () => {
|
it('should render a class with a text label, attribute and annotation', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
<<interface>>
|
<<interface>>
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2`
|
C1 --> C2`
|
||||||
);
|
);
|
||||||
|
@@ -647,12 +647,12 @@ class Class10
|
|||||||
C1 --> C2`
|
C1 --> C2`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should render a class with a text label, members and annotation', () => {
|
it('should render a class with a text label, attribute and annotation', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
<<interface>>
|
<<interface>>
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2`
|
C1 --> C2`
|
||||||
);
|
);
|
||||||
|
@@ -430,7 +430,7 @@ describe('Class diagram', () => {
|
|||||||
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||||
+String Also
|
+String Also
|
||||||
-Stirng Many
|
-Stirng Many
|
||||||
#int Members
|
#int attribute
|
||||||
+And()
|
+And()
|
||||||
-Many()
|
-Many()
|
||||||
#Methods()
|
#Methods()
|
||||||
@@ -444,7 +444,7 @@ describe('Class diagram', () => {
|
|||||||
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||||
+String Also
|
+String Also
|
||||||
-Stirng Many
|
-Stirng Many
|
||||||
#int Members
|
#int attribute
|
||||||
+And()
|
+And()
|
||||||
-Many()
|
-Many()
|
||||||
#Methods()
|
#Methods()
|
||||||
@@ -460,7 +460,7 @@ describe('Class diagram', () => {
|
|||||||
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||||
+String Also
|
+String Also
|
||||||
-Stirng Many
|
-Stirng Many
|
||||||
#int Members
|
#int attribute
|
||||||
+And()
|
+And()
|
||||||
-Many()
|
-Many()
|
||||||
#Methods()
|
#Methods()
|
||||||
|
@@ -39,8 +39,8 @@ graph TB
|
|||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import mermaid from '/mermaid.esm.mjs';
|
import mermaid from '/mermaid.esm.mjs';
|
||||||
import flowchartELK from '/mermaid-flowchart-elk.esm.mjs';
|
import layouts from '/mermaid-layout-elk.esm.mjs';
|
||||||
await mermaid.registerExternalDiagrams([flowchartELK]);
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
async function render(str) {
|
async function render(str) {
|
||||||
const { svg } = await mermaid.render('dynamic', str);
|
const { svg } = await mermaid.render('dynamic', str);
|
||||||
document.getElementById('dynamicDiagram').innerHTML = svg;
|
document.getElementById('dynamicDiagram').innerHTML = svg;
|
||||||
|
@@ -500,7 +500,7 @@ mermaid.ganttConfig = {
|
|||||||
sectionFontSize: 24, // Font size for sections
|
sectionFontSize: 24, // Font size for sections
|
||||||
numberSectionStyles: 1, // The number of alternating section styles
|
numberSectionStyles: 1, // The number of alternating section styles
|
||||||
axisFormat: '%d/%m', // Date/time format of the axis
|
axisFormat: '%d/%m', // Date/time format of the axis
|
||||||
tickInterval: '1 week', // Axis ticks
|
tickInterval: '1week', // Axis ticks
|
||||||
topAxis: true, // When this flag is set, date labels will be added to the top of the chart
|
topAxis: true, // When this flag is set, date labels will be added to the top of the chart
|
||||||
displayMode: 'compact', // Turns compact mode on
|
displayMode: 'compact', // Turns compact mode on
|
||||||
weekday: 'sunday', // On which day a week-based interval should start
|
weekday: 'sunday', // On which day a week-based interval should start
|
||||||
|
@@ -86,7 +86,7 @@ todo[Todo]
|
|||||||
|
|
||||||
## Configuration Options
|
## Configuration Options
|
||||||
|
|
||||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams tacketBaseUrl. This can be set as in the the following example:
|
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams `ticketBaseUrl`. This can be set as in the the following example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
@@ -15,6 +15,7 @@ interface LabelData {
|
|||||||
interface NodeWithVertex extends Omit<Node, 'domId'> {
|
interface NodeWithVertex extends Omit<Node, 'domId'> {
|
||||||
children?: unknown[];
|
children?: unknown[];
|
||||||
labelData?: LabelData;
|
labelData?: LabelData;
|
||||||
|
|
||||||
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
|
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -950,8 +950,8 @@ const class_box = (parent, node) => {
|
|||||||
maxWidth = classTitleBBox.width;
|
maxWidth = classTitleBBox.width;
|
||||||
}
|
}
|
||||||
const classAttributes = [];
|
const classAttributes = [];
|
||||||
node.classData.members.forEach((member) => {
|
node.classData.attributes.forEach((attribute) => {
|
||||||
const parsedInfo = member.getDisplayDetails();
|
const parsedInfo = attribute.getDisplayDetails();
|
||||||
let parsedText = parsedInfo.displayText;
|
let parsedText = parsedInfo.displayText;
|
||||||
if (getConfig().flowchart.htmlLabels) {
|
if (getConfig().flowchart.htmlLabels) {
|
||||||
parsedText = parsedText.replace(/</g, '<').replace(/>/g, '>');
|
parsedText = parsedText.replace(/</g, '<').replace(/>/g, '>');
|
||||||
@@ -984,8 +984,8 @@ const class_box = (parent, node) => {
|
|||||||
maxHeight += lineHeight;
|
maxHeight += lineHeight;
|
||||||
|
|
||||||
const classMethods = [];
|
const classMethods = [];
|
||||||
node.classData.methods.forEach((member) => {
|
node.classData.methods.forEach((method) => {
|
||||||
const parsedInfo = member.getDisplayDetails();
|
const parsedInfo = method.getDisplayDetails();
|
||||||
let displayText = parsedInfo.displayText;
|
let displayText = parsedInfo.displayText;
|
||||||
if (getConfig().flowchart.htmlLabels) {
|
if (getConfig().flowchart.htmlLabels) {
|
||||||
displayText = displayText.replace(/</g, '<').replace(/>/g, '>');
|
displayText = displayText.replace(/</g, '<').replace(/>/g, '>');
|
||||||
@@ -1059,9 +1059,9 @@ const class_box = (parent, node) => {
|
|||||||
((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) +
|
((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) +
|
||||||
')'
|
')'
|
||||||
);
|
);
|
||||||
//get the height of the bounding box of each member if exists
|
//get the height of the bounding box of each attribute if exists
|
||||||
const memberBBox = lbl?.getBBox();
|
const fieldBBox = lbl?.getBBox();
|
||||||
verticalPos += (memberBBox?.height ?? 0) + rowPadding;
|
verticalPos += (fieldBBox?.height ?? 0) + rowPadding;
|
||||||
});
|
});
|
||||||
|
|
||||||
verticalPos += lineHeight;
|
verticalPos += lineHeight;
|
||||||
@@ -1079,8 +1079,8 @@ const class_box = (parent, node) => {
|
|||||||
'transform',
|
'transform',
|
||||||
'translate( ' + -maxWidth / 2 + ', ' + ((-1 * maxHeight) / 2 + verticalPos) + ')'
|
'translate( ' + -maxWidth / 2 + ', ' + ((-1 * maxHeight) / 2 + verticalPos) + ')'
|
||||||
);
|
);
|
||||||
const memberBBox = lbl?.getBBox();
|
const methodBBox = lbl?.getBBox();
|
||||||
verticalPos += (memberBBox?.height ?? 0) + rowPadding;
|
verticalPos += (methodBBox?.height ?? 0) + rowPadding;
|
||||||
});
|
});
|
||||||
|
|
||||||
rect
|
rect
|
||||||
|
@@ -13,6 +13,7 @@ import {
|
|||||||
setDiagramTitle,
|
setDiagramTitle,
|
||||||
} from '../common/commonDb.js';
|
} from '../common/commonDb.js';
|
||||||
import type {
|
import type {
|
||||||
|
ArchitectureAlignment,
|
||||||
ArchitectureDB,
|
ArchitectureDB,
|
||||||
ArchitectureDirectionPair,
|
ArchitectureDirectionPair,
|
||||||
ArchitectureDirectionPairMap,
|
ArchitectureDirectionPairMap,
|
||||||
@@ -25,6 +26,7 @@ import type {
|
|||||||
ArchitectureState,
|
ArchitectureState,
|
||||||
} from './architectureTypes.js';
|
} from './architectureTypes.js';
|
||||||
import {
|
import {
|
||||||
|
getArchitectureDirectionAlignment,
|
||||||
getArchitectureDirectionPair,
|
getArchitectureDirectionPair,
|
||||||
isArchitectureDirection,
|
isArchitectureDirection,
|
||||||
isArchitectureJunction,
|
isArchitectureJunction,
|
||||||
@@ -211,12 +213,18 @@ const addEdge = function ({
|
|||||||
const getEdges = (): ArchitectureEdge[] => state.records.edges;
|
const getEdges = (): ArchitectureEdge[] => state.records.edges;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current diagram's adjacency list & spatial map.
|
* Returns the current diagram's adjacency list, spatial map, & group alignments.
|
||||||
* If they have not been created, run the algorithms to generate them.
|
* If they have not been created, run the algorithms to generate them.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getDataStructures = () => {
|
const getDataStructures = () => {
|
||||||
if (state.records.dataStructures === undefined) {
|
if (state.records.dataStructures === undefined) {
|
||||||
|
// Tracks how groups are aligned with one another. Generated while creating the adj list
|
||||||
|
const groupAlignments: Record<
|
||||||
|
string,
|
||||||
|
Record<string, Exclude<ArchitectureAlignment, 'bend'>>
|
||||||
|
> = {};
|
||||||
|
|
||||||
// Create an adjacency list of the diagram to perform BFS on
|
// Create an adjacency list of the diagram to perform BFS on
|
||||||
// Outer reduce applied on all services
|
// Outer reduce applied on all services
|
||||||
// Inner reduce applied on the edges for a service
|
// Inner reduce applied on the edges for a service
|
||||||
@@ -224,6 +232,19 @@ const getDataStructures = () => {
|
|||||||
Record<string, ArchitectureDirectionPairMap>
|
Record<string, ArchitectureDirectionPairMap>
|
||||||
>((prevOuter, [id, service]) => {
|
>((prevOuter, [id, service]) => {
|
||||||
prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
|
prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
|
||||||
|
// track the direction groups connect to one another
|
||||||
|
const lhsGroupId = getNode(edge.lhsId)?.in;
|
||||||
|
const rhsGroupId = getNode(edge.rhsId)?.in;
|
||||||
|
if (lhsGroupId && rhsGroupId && lhsGroupId !== rhsGroupId) {
|
||||||
|
const alignment = getArchitectureDirectionAlignment(edge.lhsDir, edge.rhsDir);
|
||||||
|
if (alignment !== 'bend') {
|
||||||
|
groupAlignments[lhsGroupId] ??= {};
|
||||||
|
groupAlignments[lhsGroupId][rhsGroupId] = alignment;
|
||||||
|
groupAlignments[rhsGroupId] ??= {};
|
||||||
|
groupAlignments[rhsGroupId][lhsGroupId] = alignment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (edge.lhsId === id) {
|
if (edge.lhsId === id) {
|
||||||
// source is LHS
|
// source is LHS
|
||||||
const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
|
const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
|
||||||
@@ -245,6 +266,7 @@ const getDataStructures = () => {
|
|||||||
// Configuration for the initial pass of BFS
|
// Configuration for the initial pass of BFS
|
||||||
const firstId = Object.keys(adjList)[0];
|
const firstId = Object.keys(adjList)[0];
|
||||||
const visited = { [firstId]: 1 };
|
const visited = { [firstId]: 1 };
|
||||||
|
// If a key is present in this object, it has not been visited
|
||||||
const notVisited = Object.keys(adjList).reduce(
|
const notVisited = Object.keys(adjList).reduce(
|
||||||
(prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
|
(prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
|
||||||
{} as Record<string, number>
|
{} as Record<string, number>
|
||||||
@@ -283,6 +305,7 @@ const getDataStructures = () => {
|
|||||||
state.records.dataStructures = {
|
state.records.dataStructures = {
|
||||||
adjList,
|
adjList,
|
||||||
spatialMaps,
|
spatialMaps,
|
||||||
|
groupAlignments,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return state.records.dataStructures;
|
return state.records.dataStructures;
|
||||||
|
@@ -12,7 +12,9 @@ import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
|||||||
import { getConfigField } from './architectureDb.js';
|
import { getConfigField } from './architectureDb.js';
|
||||||
import { architectureIcons } from './architectureIcons.js';
|
import { architectureIcons } from './architectureIcons.js';
|
||||||
import type {
|
import type {
|
||||||
|
ArchitectureAlignment,
|
||||||
ArchitectureDataStructures,
|
ArchitectureDataStructures,
|
||||||
|
ArchitectureGroupAlignments,
|
||||||
ArchitectureJunction,
|
ArchitectureJunction,
|
||||||
ArchitectureSpatialMap,
|
ArchitectureSpatialMap,
|
||||||
EdgeSingular,
|
EdgeSingular,
|
||||||
@@ -149,25 +151,91 @@ function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignmentConstraint {
|
function getAlignments(
|
||||||
|
db: ArchitectureDB,
|
||||||
|
spatialMaps: ArchitectureSpatialMap[],
|
||||||
|
groupAlignments: ArchitectureGroupAlignments
|
||||||
|
): fcose.FcoseAlignmentConstraint {
|
||||||
|
/**
|
||||||
|
* Flattens the alignment object so nodes in different groups will be in the same alignment array IFF their groups don't connect in a conflicting alignment
|
||||||
|
*
|
||||||
|
* i.e., two groups which connect horizontally should not have nodes with vertical alignments to one another
|
||||||
|
*
|
||||||
|
* See: #5952
|
||||||
|
*
|
||||||
|
* @param alignmentObj - alignment object with the outer key being the row/col # and the inner key being the group name mapped to the nodes on that axis in the group
|
||||||
|
* @param alignmentDir - alignment direction
|
||||||
|
* @returns flattened alignment object with an arbitrary key mapping to nodes in the same row/col
|
||||||
|
*/
|
||||||
|
const flattenAlignments = (
|
||||||
|
alignmentObj: Record<number, Record<string, string[]>>,
|
||||||
|
alignmentDir: ArchitectureAlignment
|
||||||
|
): Record<string, string[]> => {
|
||||||
|
return Object.entries(alignmentObj).reduce(
|
||||||
|
(prev, [dir, alignments]) => {
|
||||||
|
// prev is the mapping of x/y coordinate to an array of the nodes in that row/column
|
||||||
|
let cnt = 0;
|
||||||
|
const arr = Object.entries(alignments); // [group name, array of nodes within the group on axis dir]
|
||||||
|
if (arr.length === 1) {
|
||||||
|
// If only one group exists in the row/column, we don't need to do anything else
|
||||||
|
prev[dir] = arr[0][1];
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < arr.length - 1; i++) {
|
||||||
|
for (let j = i + 1; j < arr.length; j++) {
|
||||||
|
const [aGroupId, aNodeIds] = arr[i];
|
||||||
|
const [bGroupId, bNodeIds] = arr[j];
|
||||||
|
const alignment = groupAlignments[aGroupId]?.[bGroupId]; // Get how the two groups are intended to align (undefined if they aren't)
|
||||||
|
|
||||||
|
if (alignment === alignmentDir) {
|
||||||
|
// If the intended alignment between the two groups is the same as the alignment we are parsing
|
||||||
|
prev[dir] ??= [];
|
||||||
|
prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds]; // add the node ids of both groups to the axis array in prev
|
||||||
|
} else if (aGroupId === 'default' || bGroupId === 'default') {
|
||||||
|
// If either of the groups are in the default space (not in a group), use the same behavior as above
|
||||||
|
prev[dir] ??= [];
|
||||||
|
prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds];
|
||||||
|
} else {
|
||||||
|
// Otherwise, the nodes in the two groups are not intended to align
|
||||||
|
const keyA = `${dir}-${cnt++}`;
|
||||||
|
prev[keyA] = aNodeIds;
|
||||||
|
const keyB = `${dir}-${cnt++}`;
|
||||||
|
prev[keyB] = bNodeIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
{} as Record<string, string[]>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const alignments = spatialMaps.map((spatialMap) => {
|
const alignments = spatialMaps.map((spatialMap) => {
|
||||||
const horizontalAlignments: Record<number, string[]> = {};
|
const horizontalAlignments: Record<number, Record<string, string[]>> = {};
|
||||||
const verticalAlignments: Record<number, string[]> = {};
|
const verticalAlignments: Record<number, Record<string, string[]>> = {};
|
||||||
|
|
||||||
// Group service ids in an object with their x and y coordinate as the key
|
// Group service ids in an object with their x and y coordinate as the key
|
||||||
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
|
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
|
||||||
if (!horizontalAlignments[y]) {
|
const nodeGroup = db.getNode(id)?.in ?? 'default';
|
||||||
horizontalAlignments[y] = [];
|
|
||||||
}
|
horizontalAlignments[y] ??= {};
|
||||||
if (!verticalAlignments[x]) {
|
horizontalAlignments[y][nodeGroup] ??= [];
|
||||||
verticalAlignments[x] = [];
|
horizontalAlignments[y][nodeGroup].push(id);
|
||||||
}
|
|
||||||
horizontalAlignments[y].push(id);
|
verticalAlignments[x] ??= {};
|
||||||
verticalAlignments[x].push(id);
|
verticalAlignments[x][nodeGroup] ??= [];
|
||||||
|
verticalAlignments[x][nodeGroup].push(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Merge the values of each object into a list if the inner list has at least 2 elements
|
// Merge the values of each object into a list if the inner list has at least 2 elements
|
||||||
return {
|
return {
|
||||||
horiz: Object.values(horizontalAlignments).filter((arr) => arr.length > 1),
|
horiz: Object.values(flattenAlignments(horizontalAlignments, 'horizontal')).filter(
|
||||||
vert: Object.values(verticalAlignments).filter((arr) => arr.length > 1),
|
(arr) => arr.length > 1
|
||||||
|
),
|
||||||
|
vert: Object.values(flattenAlignments(verticalAlignments, 'vertical')).filter(
|
||||||
|
(arr) => arr.length > 1
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -244,7 +312,8 @@ function layoutArchitecture(
|
|||||||
junctions: ArchitectureJunction[],
|
junctions: ArchitectureJunction[],
|
||||||
groups: ArchitectureGroup[],
|
groups: ArchitectureGroup[],
|
||||||
edges: ArchitectureEdge[],
|
edges: ArchitectureEdge[],
|
||||||
{ spatialMaps }: ArchitectureDataStructures
|
db: ArchitectureDB,
|
||||||
|
{ spatialMaps, groupAlignments }: ArchitectureDataStructures
|
||||||
): Promise<cytoscape.Core> {
|
): Promise<cytoscape.Core> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
|
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
|
||||||
@@ -318,9 +387,8 @@ function layoutArchitecture(
|
|||||||
addServices(services, cy);
|
addServices(services, cy);
|
||||||
addJunctions(junctions, cy);
|
addJunctions(junctions, cy);
|
||||||
addEdges(edges, cy);
|
addEdges(edges, cy);
|
||||||
|
|
||||||
// Use the spatial map to create alignment arrays for fcose
|
// Use the spatial map to create alignment arrays for fcose
|
||||||
const alignmentConstraint = getAlignments(spatialMaps);
|
const alignmentConstraint = getAlignments(db, spatialMaps, groupAlignments);
|
||||||
|
|
||||||
// Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
|
// Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
|
||||||
const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
|
const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
|
||||||
@@ -454,7 +522,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
|
|||||||
await drawServices(db, servicesElem, services);
|
await drawServices(db, servicesElem, services);
|
||||||
drawJunctions(db, servicesElem, junctions);
|
drawJunctions(db, servicesElem, junctions);
|
||||||
|
|
||||||
const cy = await layoutArchitecture(services, junctions, groups, edges, ds);
|
const cy = await layoutArchitecture(services, junctions, groups, edges, db, ds);
|
||||||
|
|
||||||
await drawEdges(edgesElem, cy);
|
await drawEdges(edgesElem, cy);
|
||||||
await drawGroups(groupElem, cy);
|
await drawGroups(groupElem, cy);
|
||||||
|
@@ -7,6 +7,8 @@ import type cytoscape from 'cytoscape';
|
|||||||
| Architecture Diagram Types |
|
| Architecture Diagram Types |
|
||||||
\*=======================================*/
|
\*=======================================*/
|
||||||
|
|
||||||
|
export type ArchitectureAlignment = 'vertical' | 'horizontal' | 'bend';
|
||||||
|
|
||||||
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
|
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
|
||||||
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
|
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
|
||||||
export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>;
|
export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>;
|
||||||
@@ -170,6 +172,18 @@ export const getArchitectureDirectionXYFactors = function (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getArchitectureDirectionAlignment = function (
|
||||||
|
a: ArchitectureDirection,
|
||||||
|
b: ArchitectureDirection
|
||||||
|
): ArchitectureAlignment {
|
||||||
|
if (isArchitectureDirectionXY(a, b)) {
|
||||||
|
return 'bend';
|
||||||
|
} else if (isArchitectureDirectionX(a)) {
|
||||||
|
return 'horizontal';
|
||||||
|
}
|
||||||
|
return 'vertical';
|
||||||
|
};
|
||||||
|
|
||||||
export interface ArchitectureStyleOptions {
|
export interface ArchitectureStyleOptions {
|
||||||
archEdgeColor: string;
|
archEdgeColor: string;
|
||||||
archEdgeArrowColor: string;
|
archEdgeArrowColor: string;
|
||||||
@@ -249,9 +263,27 @@ export interface ArchitectureDB extends DiagramDB {
|
|||||||
|
|
||||||
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
|
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
|
||||||
export type ArchitectureSpatialMap = Record<string, number[]>;
|
export type ArchitectureSpatialMap = Record<string, number[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the direction that groups connect from.
|
||||||
|
*
|
||||||
|
* **Outer key**: ID of group A
|
||||||
|
*
|
||||||
|
* **Inner key**: ID of group B
|
||||||
|
*
|
||||||
|
* **Value**: 'vertical' or 'horizontal'
|
||||||
|
*
|
||||||
|
* Note: tmp[groupA][groupB] == tmp[groupB][groupA]
|
||||||
|
*/
|
||||||
|
export type ArchitectureGroupAlignments = Record<
|
||||||
|
string,
|
||||||
|
Record<string, Exclude<ArchitectureAlignment, 'bend'>>
|
||||||
|
>;
|
||||||
|
|
||||||
export interface ArchitectureDataStructures {
|
export interface ArchitectureDataStructures {
|
||||||
adjList: ArchitectureAdjacencyList;
|
adjList: ArchitectureAdjacencyList;
|
||||||
spatialMaps: ArchitectureSpatialMap[];
|
spatialMaps: ArchitectureSpatialMap[];
|
||||||
|
groupAlignments: ArchitectureGroupAlignments;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArchitectureState extends Record<string, unknown> {
|
export interface ArchitectureState extends Record<string, unknown> {
|
||||||
|
@@ -40,30 +40,18 @@ let functions: any[] = [];
|
|||||||
|
|
||||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
||||||
|
|
||||||
const splitClassNameAndType = function (_id: string) {
|
const splitClassIdAndType = function (_id: string) {
|
||||||
const id = common.sanitizeText(_id, getConfig());
|
const id = sanitizeText(_id);
|
||||||
let genericType = '';
|
let genericType = '';
|
||||||
let className = id;
|
let classId = id;
|
||||||
|
|
||||||
if (id.indexOf('~') > 0) {
|
if (id.indexOf('~') > 0) {
|
||||||
const split = id.split('~');
|
const split = id.split('~');
|
||||||
className = sanitizeText(split[0]);
|
classId = sanitizeText(split[0]);
|
||||||
genericType = sanitizeText(split[1]);
|
genericType = sanitizeText(split[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { className: className, type: genericType };
|
return { classId, type: genericType };
|
||||||
};
|
|
||||||
|
|
||||||
export const setClassLabel = function (_id: string, label: string) {
|
|
||||||
const id = common.sanitizeText(_id, getConfig());
|
|
||||||
if (label) {
|
|
||||||
label = sanitizeText(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { className } = splitClassNameAndType(id);
|
|
||||||
classes.get(className)!.label = label;
|
|
||||||
classes.get(className)!.text =
|
|
||||||
`${label}${classes.get(className)!.type ? `<${classes.get(className)!.type}>` : ''}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,28 +60,33 @@ export const setClassLabel = function (_id: string, label: string) {
|
|||||||
* @param id - Id of the class to add
|
* @param id - Id of the class to add
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const addClass = function (_id: string) {
|
export const addClass = function (_id: string, label?: string) {
|
||||||
const id = common.sanitizeText(_id, getConfig());
|
const id = sanitizeText(_id);
|
||||||
const { className, type } = splitClassNameAndType(id);
|
const { classId, type } = splitClassIdAndType(id);
|
||||||
// Only add class if not exists
|
let newLabel = classId;
|
||||||
if (classes.has(className)) {
|
|
||||||
|
if (classes.has(classId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// alert('Adding class: ' + className);
|
|
||||||
const name = common.sanitizeText(className, getConfig());
|
if (label) {
|
||||||
// alert('Adding class after: ' + name);
|
newLabel = sanitizeText(label);
|
||||||
classes.set(name, {
|
}
|
||||||
id: name,
|
|
||||||
|
const text = `${newLabel}${type ? `<${type}>` : ''}`;
|
||||||
|
|
||||||
|
classes.set(classId, {
|
||||||
|
id: classId,
|
||||||
type: type,
|
type: type,
|
||||||
label: name,
|
label: newLabel,
|
||||||
text: `${name}${type ? `<${type}>` : ''}`,
|
text: text,
|
||||||
shape: 'classBox',
|
shape: 'classBox',
|
||||||
cssClasses: 'default',
|
cssClasses: 'default',
|
||||||
methods: [],
|
methods: [],
|
||||||
members: [],
|
attributes: [],
|
||||||
annotations: [],
|
annotations: [],
|
||||||
styles: [],
|
styles: [],
|
||||||
domId: MERMAID_DOM_ID_PREFIX + name + '-' + classCounter,
|
domId: `${MERMAID_DOM_ID_PREFIX}${classId}-${classCounter}`,
|
||||||
} as ClassNode);
|
} as ClassNode);
|
||||||
|
|
||||||
classCounter++;
|
classCounter++;
|
||||||
@@ -116,7 +109,7 @@ const addInterface = function (label: string, classId: string) {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const lookUpDomId = function (_id: string): string {
|
export const lookUpDomId = function (_id: string): string {
|
||||||
const id = common.sanitizeText(_id, getConfig());
|
const id = sanitizeText(_id);
|
||||||
if (classes.has(id)) {
|
if (classes.has(id)) {
|
||||||
return classes.get(id)!.domId;
|
return classes.get(id)!.domId;
|
||||||
}
|
}
|
||||||
@@ -182,18 +175,12 @@ export const addRelation = function (classRelation: ClassRelation) {
|
|||||||
addClass(classRelation.id2);
|
addClass(classRelation.id2);
|
||||||
}
|
}
|
||||||
|
|
||||||
classRelation.id1 = splitClassNameAndType(classRelation.id1).className;
|
classRelation.id1 = splitClassIdAndType(classRelation.id1).classId;
|
||||||
classRelation.id2 = splitClassNameAndType(classRelation.id2).className;
|
classRelation.id2 = splitClassIdAndType(classRelation.id2).classId;
|
||||||
|
|
||||||
classRelation.relationTitle1 = common.sanitizeText(
|
classRelation.relationTitle1 = sanitizeText(classRelation.relationTitle1.trim());
|
||||||
classRelation.relationTitle1.trim(),
|
|
||||||
getConfig()
|
|
||||||
);
|
|
||||||
|
|
||||||
classRelation.relationTitle2 = common.sanitizeText(
|
classRelation.relationTitle2 = sanitizeText(classRelation.relationTitle2.trim());
|
||||||
classRelation.relationTitle2.trim(),
|
|
||||||
getConfig()
|
|
||||||
);
|
|
||||||
|
|
||||||
relations.push(classRelation);
|
relations.push(classRelation);
|
||||||
};
|
};
|
||||||
@@ -202,29 +189,29 @@ export const addRelation = function (classRelation: ClassRelation) {
|
|||||||
* Adds an annotation to the specified class Annotations mark special properties of the given type
|
* Adds an annotation to the specified class Annotations mark special properties of the given type
|
||||||
* (like 'interface' or 'service')
|
* (like 'interface' or 'service')
|
||||||
*
|
*
|
||||||
* @param className - The class name
|
* @param classId - The class name
|
||||||
* @param annotation - The name of the annotation without any brackets
|
* @param annotation - The name of the annotation without any brackets
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const addAnnotation = function (className: string, annotation: string) {
|
export const addAnnotation = function (className: string, annotation: string) {
|
||||||
const validatedClassName = splitClassNameAndType(className).className;
|
const validatedClassName = splitClassIdAndType(className).classId;
|
||||||
classes.get(validatedClassName)!.annotations.push(annotation);
|
classes.get(validatedClassName)!.annotations.push(annotation);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a member to the specified class
|
* Adds a member to the specified class
|
||||||
*
|
*
|
||||||
* @param className - The class name
|
* @param classId - The class name
|
||||||
* @param member - The full name of the member. If the member is enclosed in `<<brackets>>` it is
|
* @param member - The full name of the member. If the member is enclosed in `<<brackets>>` it is
|
||||||
* treated as an annotation If the member is ending with a closing bracket ) it is treated as a
|
* treated as an annotation If the member is ending with a closing bracket ) it is treated as a
|
||||||
* method Otherwise the member will be treated as a normal property
|
* method Otherwise the member will be treated as a normal property
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const addMember = function (className: string, member: string) {
|
export const addMember = function (classId: string, member: string) {
|
||||||
addClass(className);
|
addClass(classId);
|
||||||
|
|
||||||
const validatedClassName = splitClassNameAndType(className).className;
|
const validatedClassId = splitClassIdAndType(classId).classId;
|
||||||
const theClass = classes.get(validatedClassName)!;
|
const theClass = classes.get(validatedClassId)!;
|
||||||
|
|
||||||
if (typeof member === 'string') {
|
if (typeof member === 'string') {
|
||||||
// Member can contain white spaces, we trim them out
|
// Member can contain white spaces, we trim them out
|
||||||
@@ -237,7 +224,7 @@ export const addMember = function (className: string, member: string) {
|
|||||||
//its a method
|
//its a method
|
||||||
theClass.methods.push(new ClassMember(memberString, 'method'));
|
theClass.methods.push(new ClassMember(memberString, 'method'));
|
||||||
} else if (memberString) {
|
} else if (memberString) {
|
||||||
theClass.members.push(new ClassMember(memberString, 'attribute'));
|
theClass.attributes.push(new ClassMember(memberString, 'attribute'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -377,7 +364,7 @@ export const setClickEvent = function (ids: string, functionName: string, functi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) {
|
const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) {
|
||||||
const domId = common.sanitizeText(_domId, getConfig());
|
const domId = sanitizeText(_domId);
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
if (config.securityLevel !== 'loose') {
|
if (config.securityLevel !== 'loose') {
|
||||||
return;
|
return;
|
||||||
@@ -520,17 +507,15 @@ const getNamespaces = function (): NamespaceMap {
|
|||||||
* Function called by parser when a namespace definition has been found.
|
* Function called by parser when a namespace definition has been found.
|
||||||
*
|
*
|
||||||
* @param id - Id of the namespace to add
|
* @param id - Id of the namespace to add
|
||||||
* @param classNames - Ids of the class to add
|
* @param classIds - Ids of the class to add
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
export const addClassesToNamespace = function (_id: string, classIds: string[]) {
|
||||||
if (!namespaces.has(id)) {
|
addNamespace(_id);
|
||||||
return;
|
for (const id of classIds) {
|
||||||
}
|
const { classId } = splitClassIdAndType(id);
|
||||||
for (const name of classNames) {
|
classes.get(classId)!.parent = _id;
|
||||||
const { className } = splitClassNameAndType(name);
|
namespaces.get(_id)!.classes.set(classId, classes.get(classId)!);
|
||||||
classes.get(className)!.parent = id;
|
|
||||||
namespaces.get(id)!.classes.set(className, classes.get(className)!);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -726,7 +711,6 @@ export default {
|
|||||||
lookUpDomId,
|
lookUpDomId,
|
||||||
setDiagramTitle,
|
setDiagramTitle,
|
||||||
getDiagramTitle,
|
getDiagramTitle,
|
||||||
setClassLabel,
|
|
||||||
addNamespace,
|
addNamespace,
|
||||||
addClassesToNamespace,
|
addClassesToNamespace,
|
||||||
getNamespace,
|
getNamespace,
|
||||||
|
@@ -212,29 +212,29 @@ describe('given a basic class diagram, ', function () {
|
|||||||
expect(c2.label).toBe('Class 2 with chars @?');
|
expect(c2.label).toBe('Class 2 with chars @?');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with a text label and member', () => {
|
it('should parse a class with a text label and attribute', () => {
|
||||||
const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: member1';
|
const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: attribute1';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members.length).toBe(1);
|
expect(c1.attributes.length).toBe(1);
|
||||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('member1');
|
expect(c1.attributes[0].getDisplayDetails().displayText).toBe('attribute1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with a text label, member and annotation', () => {
|
it('should parse a class with a text label, attribute and annotation', () => {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class C1["Class 1 with text label"]\n' +
|
'class C1["Class 1 with text label"]\n' +
|
||||||
'<<interface>> C1\n' +
|
'<<interface>> C1\n' +
|
||||||
'C1 : int member1';
|
'C1 : int attribute1';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members.length).toBe(1);
|
expect(c1.attributes.length).toBe(1);
|
||||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
|
expect(c1.attributes[0].getDisplayDetails().displayText).toBe('int attribute1');
|
||||||
expect(c1.annotations.length).toBe(1);
|
expect(c1.annotations.length).toBe(1);
|
||||||
expect(c1.annotations[0]).toBe('interface');
|
expect(c1.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
@@ -253,14 +253,14 @@ describe('given a basic class diagram, ', function () {
|
|||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class C1["Class 1 with text label"]\n' +
|
'class C1["Class 1 with text label"]\n' +
|
||||||
'C1 : int member1\n' +
|
'C1 : int attribute1\n' +
|
||||||
'cssClass "C1" styleClass';
|
'cssClass "C1" styleClass';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
|
expect(c1.attributes[0].getDisplayDetails().displayText).toBe('int attribute1');
|
||||||
expect(c1.cssClasses).toBe('default styleClass');
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ describe('given a basic class diagram, ', function () {
|
|||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class C1["Class 1 with text label"]\n' +
|
'class C1["Class 1 with text label"]\n' +
|
||||||
'C1 : int member1\n' +
|
'C1 : int attribute1\n' +
|
||||||
'class C2["Long long long long long long long long long long label"]\n' +
|
'class C2["Long long long long long long long long long long label"]\n' +
|
||||||
'cssClass "C1,C2" styleClass';
|
'cssClass "C1,C2" styleClass';
|
||||||
|
|
||||||
@@ -486,7 +486,7 @@ class C13["With Città foreign language"]
|
|||||||
expect(studentClass).toMatchObject({
|
expect(studentClass).toMatchObject({
|
||||||
id: 'Student',
|
id: 'Student',
|
||||||
label: 'Student',
|
label: 'Student',
|
||||||
members: [
|
attributes: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: 'idCard : IdCard',
|
id: 'idCard : IdCard',
|
||||||
visibility: '-',
|
visibility: '-',
|
||||||
@@ -500,11 +500,7 @@ class C13["With Città foreign language"]
|
|||||||
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
|
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"annotations": [],
|
"annotations": [],
|
||||||
"cssClasses": "default",
|
"attributes": [
|
||||||
"domId": "classId-Student-141",
|
|
||||||
"id": "Student",
|
|
||||||
"label": "Student",
|
|
||||||
"members": [
|
|
||||||
ClassMember {
|
ClassMember {
|
||||||
"classifier": "",
|
"classifier": "",
|
||||||
"id": "idCard : IdCard",
|
"id": "idCard : IdCard",
|
||||||
@@ -513,6 +509,10 @@ class C13["With Città foreign language"]
|
|||||||
"visibility": "-",
|
"visibility": "-",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"cssClasses": "default",
|
||||||
|
"domId": "classId-Student-141",
|
||||||
|
"id": "Student",
|
||||||
|
"label": "Student",
|
||||||
"methods": [],
|
"methods": [],
|
||||||
"shape": "classBox",
|
"shape": "classBox",
|
||||||
"styles": [],
|
"styles": [],
|
||||||
@@ -569,7 +569,7 @@ class C13["With Città foreign language"]
|
|||||||
parser.yy = classDb;
|
parser.yy = classDb;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle member definitions', function () {
|
it('should handle attribute definitions', function () {
|
||||||
const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}';
|
const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
@@ -588,7 +588,7 @@ class C13["With Città foreign language"]
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle member and method definitions', () => {
|
it('should handle attribute and method definitions', () => {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' + 'class Dummy_Class {\n' + 'String data\n' + 'void methods()\n' + '}';
|
'classDiagram\n' + 'class Dummy_Class {\n' + 'String data\n' + 'void methods()\n' + '}';
|
||||||
|
|
||||||
@@ -611,45 +611,46 @@ class C13["With Città foreign language"]
|
|||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class Class1 {\n' +
|
'class Class1 {\n' +
|
||||||
'int testMember\n' +
|
'int testAttribute\n' +
|
||||||
'test()\n' +
|
'test()\n' +
|
||||||
'string fooMember\n' +
|
'string fooAttribute\n' +
|
||||||
'foo()\n' +
|
'foo()\n' +
|
||||||
'}';
|
'}';
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.members.length).toBe(2);
|
expect(actual.attributes.length).toBe(2);
|
||||||
expect(actual.methods.length).toBe(2);
|
expect(actual.methods.length).toBe(2);
|
||||||
expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember');
|
expect(actual.attributes[0].getDisplayDetails().displayText).toBe('int testAttribute');
|
||||||
expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember');
|
expect(actual.attributes[1].getDisplayDetails().displayText).toBe('string fooAttribute');
|
||||||
expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()');
|
expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()');
|
||||||
expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()');
|
expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with a text label and members', () => {
|
it('should parse a class with a text label and attribute', () => {
|
||||||
const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+member1\n' + '}';
|
const str =
|
||||||
|
'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+attributes1\n' + '}';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members.length).toBe(1);
|
expect(c1.attributes.length).toBe(1);
|
||||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
|
expect(c1.attributes[0].getDisplayDetails().displayText).toBe('+attributes1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with a text label, members and annotation', () => {
|
it('should parse a class with a text label, attribute and annotation', () => {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class C1["Class 1 with text label"] {\n' +
|
'class C1["Class 1 with text label"] {\n' +
|
||||||
'<<interface>>\n' +
|
'<<interface>>\n' +
|
||||||
'+member1\n' +
|
'+attribute1\n' +
|
||||||
'}';
|
'}';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members.length).toBe(1);
|
expect(c1.attributes.length).toBe(1);
|
||||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
|
expect(c1.attributes[0].getDisplayDetails().displayText).toBe('+attribute1');
|
||||||
expect(c1.annotations.length).toBe(1);
|
expect(c1.annotations.length).toBe(1);
|
||||||
expect(c1.annotations[0]).toBe('interface');
|
expect(c1.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
@@ -868,12 +869,12 @@ foo()
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.annotations.length).toBe(1);
|
expect(actual.annotations.length).toBe(1);
|
||||||
expect(actual.members.length).toBe(0);
|
expect(actual.attributes.length).toBe(0);
|
||||||
expect(actual.methods.length).toBe(0);
|
expect(actual.attributes.length).toBe(0);
|
||||||
expect(actual.annotations[0]).toBe('interface');
|
expect(actual.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle class annotations with members and methods', function () {
|
it('should handle class annotations with attributes and methods', function () {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class Class1\n' +
|
'class Class1\n' +
|
||||||
@@ -884,7 +885,7 @@ foo()
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.annotations.length).toBe(1);
|
expect(actual.annotations.length).toBe(1);
|
||||||
expect(actual.members.length).toBe(1);
|
expect(actual.attributes.length).toBe(1);
|
||||||
expect(actual.methods.length).toBe(1);
|
expect(actual.methods.length).toBe(1);
|
||||||
expect(actual.annotations[0]).toBe('interface');
|
expect(actual.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
@@ -895,12 +896,12 @@ foo()
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.annotations.length).toBe(1);
|
expect(actual.annotations.length).toBe(1);
|
||||||
expect(actual.members.length).toBe(0);
|
expect(actual.attributes.length).toBe(0);
|
||||||
expect(actual.methods.length).toBe(0);
|
expect(actual.methods.length).toBe(0);
|
||||||
expect(actual.annotations[0]).toBe('interface');
|
expect(actual.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle class annotations in brackets with members and methods', function () {
|
it('should handle class annotations in brackets with attributes and methods', function () {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class Class1 {\n' +
|
'class Class1 {\n' +
|
||||||
@@ -912,41 +913,41 @@ foo()
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.annotations.length).toBe(1);
|
expect(actual.annotations.length).toBe(1);
|
||||||
expect(actual.members.length).toBe(1);
|
expect(actual.attributes.length).toBe(1);
|
||||||
expect(actual.methods.length).toBe(1);
|
expect(actual.methods.length).toBe(1);
|
||||||
expect(actual.annotations[0]).toBe('interface');
|
expect(actual.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('given a class diagram with members and methods ', function () {
|
describe('given a class diagram with attributes and methods ', function () {
|
||||||
describe('when parsing members', function () {
|
describe('when parsing attributes', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
classDb.clear();
|
classDb.clear();
|
||||||
parser.yy = classDb;
|
parser.yy = classDb;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle simple member declaration', function () {
|
it('should handle simple attribute declaration', function () {
|
||||||
const str = 'classDiagram\n' + 'class Car\n' + 'Car : wheels';
|
const str = 'classDiagram\n' + 'class Car\n' + 'Car : wheels';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle direct member declaration', function () {
|
it('should handle direct attribute declaration', function () {
|
||||||
parser.parse('classDiagram\n' + 'Car : wheels');
|
parser.parse('classDiagram\n' + 'Car : wheels');
|
||||||
const car = classDb.getClass('Car');
|
const car = classDb.getClass('Car');
|
||||||
expect(car.members.length).toBe(1);
|
expect(car.attributes.length).toBe(1);
|
||||||
expect(car.members[0].id).toBe('wheels');
|
expect(car.attributes[0].id).toBe('wheels');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle direct member declaration with type', function () {
|
it('should handle direct attribute declaration with type', function () {
|
||||||
parser.parse('classDiagram\n' + 'Car : int wheels');
|
parser.parse('classDiagram\n' + 'Car : int wheels');
|
||||||
const car = classDb.getClass('Car');
|
const car = classDb.getClass('Car');
|
||||||
expect(car.members.length).toBe(1);
|
expect(car.attributes.length).toBe(1);
|
||||||
expect(car.members[0].id).toBe('int wheels');
|
expect(car.attributes[0].id).toBe('int wheels');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle simple member declaration with type', function () {
|
it('should handle simple attribute declaration with type', function () {
|
||||||
const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels';
|
const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
@@ -956,20 +957,20 @@ describe('given a class diagram with members and methods ', function () {
|
|||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class actual\n' +
|
'class actual\n' +
|
||||||
'actual : -int privateMember\n' +
|
'actual : -int privateAttribute\n' +
|
||||||
'actual : +int publicMember\n' +
|
'actual : +int publicAttribute\n' +
|
||||||
'actual : #int protectedMember\n' +
|
'actual : #int protectedAttribute\n' +
|
||||||
'actual : ~int privatePackage';
|
'actual : ~int privatePackage';
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const actual = parser.yy.getClass('actual');
|
const actual = parser.yy.getClass('actual');
|
||||||
expect(actual.members.length).toBe(4);
|
expect(actual.attributes.length).toBe(4);
|
||||||
expect(actual.methods.length).toBe(0);
|
expect(actual.methods.length).toBe(0);
|
||||||
expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember');
|
expect(actual.attributes[0].getDisplayDetails().displayText).toBe('-int privateAttribute');
|
||||||
expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember');
|
expect(actual.attributes[1].getDisplayDetails().displayText).toBe('+int publicAttribute');
|
||||||
expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember');
|
expect(actual.attributes[2].getDisplayDetails().displayText).toBe('#int protectedAttribute');
|
||||||
expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage');
|
expect(actual.attributes[3].getDisplayDetails().displayText).toBe('~int privatePackage');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle generic types', function () {
|
it('should handle generic types', function () {
|
||||||
@@ -1020,7 +1021,7 @@ describe('given a class diagram with members and methods ', function () {
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.annotations.length).toBe(0);
|
expect(actual.annotations.length).toBe(0);
|
||||||
expect(actual.members.length).toBe(0);
|
expect(actual.attributes.length).toBe(0);
|
||||||
expect(actual.methods.length).toBe(1);
|
expect(actual.methods.length).toBe(1);
|
||||||
const method = actual.methods[0];
|
const method = actual.methods[0];
|
||||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||||
@@ -1033,7 +1034,7 @@ describe('given a class diagram with members and methods ', function () {
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.annotations.length).toBe(0);
|
expect(actual.annotations.length).toBe(0);
|
||||||
expect(actual.members.length).toBe(0);
|
expect(actual.attributes.length).toBe(0);
|
||||||
expect(actual.methods.length).toBe(1);
|
expect(actual.methods.length).toBe(1);
|
||||||
const method = actual.methods[0];
|
const method = actual.methods[0];
|
||||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||||
@@ -1051,7 +1052,7 @@ describe('given a class diagram with members and methods ', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle generic types in members in class with brackets', function () {
|
it('should handle generic types in attributes in class with brackets', function () {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class Car {\n' +
|
'class Car {\n' +
|
||||||
@@ -1385,12 +1386,12 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.annotations.length).toBe(1);
|
expect(testClass.annotations.length).toBe(1);
|
||||||
expect(testClass.members.length).toBe(0);
|
expect(testClass.attributes.length).toBe(0);
|
||||||
expect(testClass.methods.length).toBe(0);
|
expect(testClass.methods.length).toBe(0);
|
||||||
expect(testClass.annotations[0]).toBe('interface');
|
expect(testClass.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle class annotations with members and methods', function () {
|
it('should handle class annotations with attributes and methods', function () {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class Class1\n' +
|
'class Class1\n' +
|
||||||
@@ -1401,7 +1402,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.annotations.length).toBe(1);
|
expect(testClass.annotations.length).toBe(1);
|
||||||
expect(testClass.members.length).toBe(1);
|
expect(testClass.attributes.length).toBe(1);
|
||||||
expect(testClass.methods.length).toBe(1);
|
expect(testClass.methods.length).toBe(1);
|
||||||
expect(testClass.annotations[0]).toBe('interface');
|
expect(testClass.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
@@ -1412,12 +1413,12 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.annotations.length).toBe(1);
|
expect(testClass.annotations.length).toBe(1);
|
||||||
expect(testClass.members.length).toBe(0);
|
expect(testClass.attributes.length).toBe(0);
|
||||||
expect(testClass.methods.length).toBe(0);
|
expect(testClass.methods.length).toBe(0);
|
||||||
expect(testClass.annotations[0]).toBe('interface');
|
expect(testClass.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle class annotations in brackets with members and methods', function () {
|
it('should handle class annotations in brackets with attributes and methods', function () {
|
||||||
const str =
|
const str =
|
||||||
'classDiagram\n' +
|
'classDiagram\n' +
|
||||||
'class Class1 {\n' +
|
'class Class1 {\n' +
|
||||||
@@ -1429,7 +1430,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.annotations.length).toBe(1);
|
expect(testClass.annotations.length).toBe(1);
|
||||||
expect(testClass.members.length).toBe(1);
|
expect(testClass.attributes.length).toBe(1);
|
||||||
expect(testClass.methods.length).toBe(1);
|
expect(testClass.methods.length).toBe(1);
|
||||||
expect(testClass.annotations[0]).toBe('interface');
|
expect(testClass.annotations[0]).toBe('interface');
|
||||||
});
|
});
|
||||||
@@ -1446,10 +1447,10 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.members.length).toBe(2);
|
expect(testClass.attributes.length).toBe(2);
|
||||||
expect(testClass.methods.length).toBe(2);
|
expect(testClass.methods.length).toBe(2);
|
||||||
expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test');
|
expect(testClass.attributes[0].getDisplayDetails().displayText).toBe('int : test');
|
||||||
expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo');
|
expect(testClass.attributes[1].getDisplayDetails().displayText).toBe('string : foo');
|
||||||
expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()');
|
expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()');
|
||||||
expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()');
|
expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()');
|
||||||
});
|
});
|
||||||
@@ -1460,7 +1461,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.annotations.length).toBe(0);
|
expect(testClass.annotations.length).toBe(0);
|
||||||
expect(testClass.members.length).toBe(0);
|
expect(testClass.attributes.length).toBe(0);
|
||||||
expect(testClass.methods.length).toBe(1);
|
expect(testClass.methods.length).toBe(1);
|
||||||
const method = testClass.methods[0];
|
const method = testClass.methods[0];
|
||||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||||
@@ -1473,7 +1474,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.annotations.length).toBe(0);
|
expect(testClass.annotations.length).toBe(0);
|
||||||
expect(testClass.members.length).toBe(0);
|
expect(testClass.attributes.length).toBe(0);
|
||||||
expect(testClass.methods.length).toBe(1);
|
expect(testClass.methods.length).toBe(1);
|
||||||
const method = testClass.methods[0];
|
const method = testClass.methods[0];
|
||||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||||
@@ -1688,17 +1689,17 @@ class Class2
|
|||||||
const testClasses = parser.yy.getClasses();
|
const testClasses = parser.yy.getClasses();
|
||||||
const testRelations = parser.yy.getRelations();
|
const testRelations = parser.yy.getRelations();
|
||||||
expect(testNamespaceA.classes.size).toBe(2);
|
expect(testNamespaceA.classes.size).toBe(2);
|
||||||
expect(testNamespaceA.classes.get('A1').members[0].getDisplayDetails().displayText).toBe(
|
expect(testNamespaceA.classes.get('A1').attributes[0].getDisplayDetails().displayText).toBe(
|
||||||
'+foo : string'
|
'+foo : string'
|
||||||
);
|
);
|
||||||
expect(testNamespaceA.classes.get('A2').members[0].getDisplayDetails().displayText).toBe(
|
expect(testNamespaceA.classes.get('A2').attributes[0].getDisplayDetails().displayText).toBe(
|
||||||
'+bar : int'
|
'+bar : int'
|
||||||
);
|
);
|
||||||
expect(testNamespaceB.classes.size).toBe(2);
|
expect(testNamespaceB.classes.size).toBe(2);
|
||||||
expect(testNamespaceB.classes.get('B1').members[0].getDisplayDetails().displayText).toBe(
|
expect(testNamespaceB.classes.get('B1').attributes[0].getDisplayDetails().displayText).toBe(
|
||||||
'+foo : bool'
|
'+foo : bool'
|
||||||
);
|
);
|
||||||
expect(testNamespaceB.classes.get('B2').members[0].getDisplayDetails().displayText).toBe(
|
expect(testNamespaceB.classes.get('B2').attributes[0].getDisplayDetails().displayText).toBe(
|
||||||
'+bar : float'
|
'+bar : float'
|
||||||
);
|
);
|
||||||
expect(testClasses.size).toBe(4);
|
expect(testClasses.size).toBe(4);
|
||||||
@@ -1742,37 +1743,37 @@ class Class2
|
|||||||
expect(c2.label).toBe('Class 2 with chars @?');
|
expect(c2.label).toBe('Class 2 with chars @?');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with a text label and members', () => {
|
it('should parse a class with a text label and attributes', () => {
|
||||||
parser.parse(`classDiagram
|
parser.parse(`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
`);
|
`);
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members.length).toBe(1);
|
expect(c1.attributes.length).toBe(1);
|
||||||
const member = c1.members[0];
|
const attribute = c1.attributes[0];
|
||||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
|
||||||
const c2 = classDb.getClass('C2');
|
const c2 = classDb.getClass('C2');
|
||||||
expect(c2.label).toBe('C2');
|
expect(c2.label).toBe('C2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with a text label, members and annotation', () => {
|
it('should parse a class with a text label, attributes and annotation', () => {
|
||||||
parser.parse(`classDiagram
|
parser.parse(`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
<<interface>>
|
<<interface>>
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
`);
|
`);
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members.length).toBe(1);
|
expect(c1.attributes.length).toBe(1);
|
||||||
expect(c1.annotations.length).toBe(1);
|
expect(c1.annotations.length).toBe(1);
|
||||||
expect(c1.annotations[0]).toBe('interface');
|
expect(c1.annotations[0]).toBe('interface');
|
||||||
const member = c1.members[0];
|
const attribute = c1.attributes[0];
|
||||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
|
||||||
|
|
||||||
const c2 = classDb.getClass('C2');
|
const c2 = classDb.getClass('C2');
|
||||||
expect(c2.label).toBe('C2');
|
expect(c2.label).toBe('C2');
|
||||||
@@ -1781,7 +1782,7 @@ class Class2
|
|||||||
it('should parse a class with text label and css class shorthand', () => {
|
it('should parse a class with text label and css class shorthand', () => {
|
||||||
parser.parse(`classDiagram
|
parser.parse(`classDiagram
|
||||||
class C1["Class 1 with text label"]:::styleClass {
|
class C1["Class 1 with text label"]:::styleClass {
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
`);
|
`);
|
||||||
@@ -1789,14 +1790,14 @@ C1 --> C2
|
|||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses).toBe('default styleClass');
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
const member = c1.members[0];
|
const attribute = c1.attributes[0];
|
||||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with text label and css class', () => {
|
it('should parse a class with text label and css class', () => {
|
||||||
parser.parse(`classDiagram
|
parser.parse(`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
cssClass "C1" styleClass
|
cssClass "C1" styleClass
|
||||||
@@ -1805,14 +1806,14 @@ cssClass "C1" styleClass
|
|||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses).toBe('default styleClass');
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
const member = c1.members[0];
|
const attribute = c1.attributes[0];
|
||||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse two classes with text labels and css classes', () => {
|
it('should parse two classes with text labels and css classes', () => {
|
||||||
parser.parse(`classDiagram
|
parser.parse(`classDiagram
|
||||||
class C1["Class 1 with text label"] {
|
class C1["Class 1 with text label"] {
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
class C2["Long long long long long long long long long long label"]
|
class C2["Long long long long long long long long long long label"]
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
@@ -1831,7 +1832,7 @@ cssClass "C1,C2" styleClass
|
|||||||
it('should parse two classes with text labels and css class shorthands', () => {
|
it('should parse two classes with text labels and css class shorthands', () => {
|
||||||
parser.parse(`classDiagram
|
parser.parse(`classDiagram
|
||||||
class C1["Class 1 with text label"]:::styleClass1 {
|
class C1["Class 1 with text label"]:::styleClass1 {
|
||||||
+member1
|
+attribute1
|
||||||
}
|
}
|
||||||
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
|
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
|
@@ -3,13 +3,13 @@ import { parseGenericTypes, sanitizeText } from '../common/common.js';
|
|||||||
|
|
||||||
export interface ClassNode {
|
export interface ClassNode {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type?: string;
|
||||||
label: string;
|
label?: string;
|
||||||
shape: string;
|
shape: string;
|
||||||
text: string;
|
text?: string;
|
||||||
cssClasses: string;
|
cssClasses: string;
|
||||||
methods: ClassMember[];
|
methods: ClassMember[];
|
||||||
members: ClassMember[];
|
attributes: ClassMember[];
|
||||||
annotations: string[];
|
annotations: string[];
|
||||||
domId: string;
|
domId: string;
|
||||||
styles: string[];
|
styles: string[];
|
||||||
|
@@ -297,7 +297,7 @@ classStatement
|
|||||||
|
|
||||||
classIdentifier
|
classIdentifier
|
||||||
: CLASS className {$$=$2; yy.addClass($2);}
|
: CLASS className {$$=$2; yy.addClass($2);}
|
||||||
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
|
| CLASS className classLabel {$$=$2; yy.addClass($2, $3);}
|
||||||
;
|
;
|
||||||
|
|
||||||
annotationStatement
|
annotationStatement
|
||||||
|
@@ -27,12 +27,12 @@ export async function textHelper<T extends SVGGraphicsElement>(
|
|||||||
|
|
||||||
let annotationGroup = null;
|
let annotationGroup = null;
|
||||||
let labelGroup = null;
|
let labelGroup = null;
|
||||||
let membersGroup = null;
|
let attributeGroup = null;
|
||||||
let methodsGroup = null;
|
let methodsGroup = null;
|
||||||
|
|
||||||
let annotationGroupHeight = 0;
|
let annotationGroupHeight = 0;
|
||||||
let labelGroupHeight = 0;
|
let labelGroupHeight = 0;
|
||||||
let membersGroupHeight = 0;
|
let attributeGroupHeight = 0;
|
||||||
|
|
||||||
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
|
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
|
||||||
if (node.annotations.length > 0) {
|
if (node.annotations.length > 0) {
|
||||||
@@ -48,15 +48,15 @@ export async function textHelper<T extends SVGGraphicsElement>(
|
|||||||
const labelGroupBBox = labelGroup.node()!.getBBox();
|
const labelGroupBBox = labelGroup.node()!.getBBox();
|
||||||
labelGroupHeight = labelGroupBBox.height;
|
labelGroupHeight = labelGroupBBox.height;
|
||||||
|
|
||||||
membersGroup = shapeSvg.insert('g').attr('class', 'members-group text');
|
attributeGroup = shapeSvg.insert('g').attr('class', 'attribute-group text');
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
for (const member of node.members) {
|
for (const attribute of node.attributes) {
|
||||||
const height = await addText(membersGroup, member, yOffset, [member.parseClassifier()]);
|
const height = await addText(attributeGroup, attribute, yOffset, [attribute.parseClassifier()]);
|
||||||
yOffset += height + TEXT_PADDING;
|
yOffset += height + TEXT_PADDING;
|
||||||
}
|
}
|
||||||
membersGroupHeight = membersGroup.node()!.getBBox().height;
|
attributeGroupHeight = attributeGroup.node()!.getBBox().height;
|
||||||
if (membersGroupHeight <= 0) {
|
if (attributeGroupHeight <= 0) {
|
||||||
membersGroupHeight = GAP / 2;
|
attributeGroupHeight = GAP / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
methodsGroup = shapeSvg.insert('g').attr('class', 'methods-group text');
|
methodsGroup = shapeSvg.insert('g').attr('class', 'methods-group text');
|
||||||
@@ -79,14 +79,14 @@ export async function textHelper<T extends SVGGraphicsElement>(
|
|||||||
|
|
||||||
bbox = shapeSvg.node()!.getBBox();
|
bbox = shapeSvg.node()!.getBBox();
|
||||||
|
|
||||||
membersGroup.attr(
|
attributeGroup.attr(
|
||||||
'transform',
|
'transform',
|
||||||
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + GAP * 2})`
|
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + GAP * 2})`
|
||||||
);
|
);
|
||||||
bbox = shapeSvg.node()!.getBBox();
|
bbox = shapeSvg.node()!.getBBox();
|
||||||
methodsGroup.attr(
|
methodsGroup.attr(
|
||||||
'transform',
|
'transform',
|
||||||
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + (membersGroupHeight ? membersGroupHeight + GAP * 4 : GAP * 2)})`
|
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + (attributeGroupHeight ? attributeGroupHeight + GAP * 4 : GAP * 2)})`
|
||||||
);
|
);
|
||||||
|
|
||||||
bbox = shapeSvg.node()!.getBBox();
|
bbox = shapeSvg.node()!.getBBox();
|
||||||
@@ -107,9 +107,10 @@ async function addText<T extends SVGGraphicsElement>(
|
|||||||
'useHtmlLabels' in node ? node.useHtmlLabels : (evaluate(config.htmlLabels) ?? true);
|
'useHtmlLabels' in node ? node.useHtmlLabels : (evaluate(config.htmlLabels) ?? true);
|
||||||
|
|
||||||
let textContent = '';
|
let textContent = '';
|
||||||
|
|
||||||
// Support regular node type (.label) and classNodes (.text)
|
// Support regular node type (.label) and classNodes (.text)
|
||||||
if ('text' in node) {
|
if ('text' in node) {
|
||||||
textContent = node.text;
|
textContent = node.text ?? '';
|
||||||
} else {
|
} else {
|
||||||
textContent = node.label!;
|
textContent = node.label!;
|
||||||
}
|
}
|
||||||
|
@@ -190,8 +190,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
|||||||
|
|
||||||
// add annotations
|
// add annotations
|
||||||
let isFirst = true;
|
let isFirst = true;
|
||||||
classDef.annotations.forEach(function (member) {
|
classDef.annotations.forEach(function (annotation) {
|
||||||
const titleText2 = title.append('tspan').text('«' + member + '»');
|
const titleText2 = title.append('tspan').text('«' + annotation + '»');
|
||||||
if (!isFirst) {
|
if (!isFirst) {
|
||||||
titleText2.attr('dy', conf.textHeight);
|
titleText2.attr('dy', conf.textHeight);
|
||||||
}
|
}
|
||||||
@@ -208,19 +208,19 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const titleHeight = title.node().getBBox().height;
|
const titleHeight = title.node().getBBox().height;
|
||||||
let membersLine;
|
let attributesLine;
|
||||||
let membersBox;
|
let attributesBox;
|
||||||
let methodsLine;
|
let methodsLine;
|
||||||
|
|
||||||
// don't draw box if no members
|
// don't draw box if no attributes
|
||||||
if (classDef.members.length > 0) {
|
if (classDef.attributes.length > 0) {
|
||||||
membersLine = g
|
attributesLine = g
|
||||||
.append('line') // text label for the x axis
|
.append('line') // text label for the x axis
|
||||||
.attr('x1', 0)
|
.attr('x1', 0)
|
||||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
||||||
|
|
||||||
const members = g
|
const attributes = g
|
||||||
.append('text') // text label for the x axis
|
.append('text') // text label for the x axis
|
||||||
.attr('x', conf.padding)
|
.attr('x', conf.padding)
|
||||||
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
||||||
@@ -228,12 +228,12 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
|||||||
.attr('class', 'classText');
|
.attr('class', 'classText');
|
||||||
|
|
||||||
isFirst = true;
|
isFirst = true;
|
||||||
classDef.members.forEach(function (member) {
|
classDef.attributes.forEach(function (attribute) {
|
||||||
addTspan(members, member, isFirst, conf);
|
addTspan(attributes, attribute, isFirst, conf);
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
membersBox = members.node().getBBox();
|
attributesBox = attributes.node().getBBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't draw box if no methods
|
// don't draw box if no methods
|
||||||
@@ -241,13 +241,13 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
|||||||
methodsLine = g
|
methodsLine = g
|
||||||
.append('line') // text label for the x axis
|
.append('line') // text label for the x axis
|
||||||
.attr('x1', 0)
|
.attr('x1', 0)
|
||||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
|
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + attributesBox.height)
|
||||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
|
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + attributesBox.height);
|
||||||
|
|
||||||
const methods = g
|
const methods = g
|
||||||
.append('text') // text label for the x axis
|
.append('text') // text label for the x axis
|
||||||
.attr('x', conf.padding)
|
.attr('x', conf.padding)
|
||||||
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
|
.attr('y', titleHeight + 2 * conf.dividerMargin + attributesBox.height + conf.textHeight)
|
||||||
.attr('fill', 'white')
|
.attr('fill', 'white')
|
||||||
.attr('class', 'classText');
|
.attr('class', 'classText');
|
||||||
|
|
||||||
@@ -286,8 +286,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
|||||||
title.insert('title').text(classDef.tooltip);
|
title.insert('title').text(classDef.tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membersLine) {
|
if (attributesLine) {
|
||||||
membersLine.attr('x2', rectWidth);
|
attributesLine.attr('x2', rectWidth);
|
||||||
}
|
}
|
||||||
if (methodsLine) {
|
if (methodsLine) {
|
||||||
methodsLine.attr('x2', rectWidth);
|
methodsLine.attr('x2', rectWidth);
|
||||||
|
@@ -390,7 +390,7 @@ mermaid.ganttConfig = {
|
|||||||
sectionFontSize: 24, // Font size for sections
|
sectionFontSize: 24, // Font size for sections
|
||||||
numberSectionStyles: 1, // The number of alternating section styles
|
numberSectionStyles: 1, // The number of alternating section styles
|
||||||
axisFormat: '%d/%m', // Date/time format of the axis
|
axisFormat: '%d/%m', // Date/time format of the axis
|
||||||
tickInterval: '1 week', // Axis ticks
|
tickInterval: '1week', // Axis ticks
|
||||||
topAxis: true, // When this flag is set, date labels will be added to the top of the chart
|
topAxis: true, // When this flag is set, date labels will be added to the top of the chart
|
||||||
displayMode: 'compact', // Turns compact mode on
|
displayMode: 'compact', // Turns compact mode on
|
||||||
weekday: 'sunday', // On which day a week-based interval should start
|
weekday: 'sunday', // On which day a week-based interval should start
|
||||||
|
@@ -64,7 +64,7 @@ todo[Todo]
|
|||||||
|
|
||||||
## Configuration Options
|
## Configuration Options
|
||||||
|
|
||||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams tacketBaseUrl. This can be set as in the the following example:
|
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams `ticketBaseUrl`. This can be set as in the the following example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
@@ -18,7 +18,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
// Treat node as classNode
|
// Treat node as classNode
|
||||||
const classNode = node as unknown as ClassNode;
|
const classNode = node as unknown as ClassNode;
|
||||||
classNode.annotations = classNode.annotations ?? [];
|
classNode.annotations = classNode.annotations ?? [];
|
||||||
classNode.members = classNode.members ?? [];
|
classNode.attributes = classNode.attributes ?? [];
|
||||||
classNode.methods = classNode.methods ?? [];
|
classNode.methods = classNode.methods ?? [];
|
||||||
|
|
||||||
const { shapeSvg, bbox } = await textHelper(parent, node, config, useHtmlLabels, GAP);
|
const { shapeSvg, bbox } = await textHelper(parent, node, config, useHtmlLabels, GAP);
|
||||||
@@ -35,7 +35,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderExtraBox =
|
const renderExtraBox =
|
||||||
classNode.members.length === 0 &&
|
classNode.attributes.length === 0 &&
|
||||||
classNode.methods.length === 0 &&
|
classNode.methods.length === 0 &&
|
||||||
!config.class?.hideEmptyMembersBox;
|
!config.class?.hideEmptyMembersBox;
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
|
|
||||||
const w = bbox.width;
|
const w = bbox.width;
|
||||||
let h = bbox.height;
|
let h = bbox.height;
|
||||||
if (classNode.members.length === 0 && classNode.methods.length === 0) {
|
if (classNode.attributes.length === 0 && classNode.methods.length === 0) {
|
||||||
h += GAP;
|
h += GAP;
|
||||||
} else if (classNode.members.length > 0 && classNode.methods.length === 0) {
|
} else if (classNode.attributes.length > 0 && classNode.methods.length === 0) {
|
||||||
h += GAP * 2;
|
h += GAP * 2;
|
||||||
}
|
}
|
||||||
const x = -w / 2;
|
const x = -w / 2;
|
||||||
@@ -66,7 +66,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
PADDING -
|
PADDING -
|
||||||
(renderExtraBox
|
(renderExtraBox
|
||||||
? PADDING
|
? PADDING
|
||||||
: classNode.members.length === 0 && classNode.methods.length === 0
|
: classNode.attributes.length === 0 && classNode.methods.length === 0
|
||||||
? -PADDING / 2
|
? -PADDING / 2
|
||||||
: 0),
|
: 0),
|
||||||
w + 2 * PADDING,
|
w + 2 * PADDING,
|
||||||
@@ -74,7 +74,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
2 * PADDING +
|
2 * PADDING +
|
||||||
(renderExtraBox
|
(renderExtraBox
|
||||||
? PADDING * 2
|
? PADDING * 2
|
||||||
: classNode.members.length === 0 && classNode.methods.length === 0
|
: classNode.attributes.length === 0 && classNode.methods.length === 0
|
||||||
? -PADDING
|
? -PADDING
|
||||||
: 0),
|
: 0),
|
||||||
options
|
options
|
||||||
@@ -107,7 +107,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
PADDING -
|
PADDING -
|
||||||
(renderExtraBox
|
(renderExtraBox
|
||||||
? PADDING
|
? PADDING
|
||||||
: classNode.members.length === 0 && classNode.methods.length === 0
|
: classNode.attributes.length === 0 && classNode.methods.length === 0
|
||||||
? -PADDING / 2
|
? -PADDING / 2
|
||||||
: 0);
|
: 0);
|
||||||
if (!useHtmlLabels) {
|
if (!useHtmlLabels) {
|
||||||
@@ -138,11 +138,11 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
const labelGroupHeight =
|
const labelGroupHeight =
|
||||||
(shapeSvg.select('.label-group').node() as SVGGraphicsElement).getBBox().height -
|
(shapeSvg.select('.label-group').node() as SVGGraphicsElement).getBBox().height -
|
||||||
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
||||||
const membersGroupHeight =
|
const attributeGroupHeight =
|
||||||
(shapeSvg.select('.members-group').node() as SVGGraphicsElement).getBBox().height -
|
(shapeSvg.select('.attribute-group').node() as SVGGraphicsElement).getBBox().height -
|
||||||
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
||||||
// First line (under label)
|
// First line (under label)
|
||||||
if (classNode.members.length > 0 || classNode.methods.length > 0 || renderExtraBox) {
|
if (classNode.attributes.length > 0 || classNode.methods.length > 0 || renderExtraBox) {
|
||||||
const roughLine = rc.line(
|
const roughLine = rc.line(
|
||||||
rectBBox.x,
|
rectBBox.x,
|
||||||
annotationGroupHeight + labelGroupHeight + y + PADDING,
|
annotationGroupHeight + labelGroupHeight + y + PADDING,
|
||||||
@@ -154,13 +154,13 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
|
|||||||
line.attr('class', 'divider').attr('style', styles);
|
line.attr('class', 'divider').attr('style', styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second line (under members)
|
// Second line (under attributes)
|
||||||
if (renderExtraBox || classNode.members.length > 0 || classNode.methods.length > 0) {
|
if (renderExtraBox || classNode.attributes.length > 0 || classNode.methods.length > 0) {
|
||||||
const roughLine = rc.line(
|
const roughLine = rc.line(
|
||||||
rectBBox.x,
|
rectBBox.x,
|
||||||
annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + GAP * 2 + PADDING,
|
annotationGroupHeight + labelGroupHeight + attributeGroupHeight + y + GAP * 2 + PADDING,
|
||||||
rectBBox.x + rectBBox.width,
|
rectBBox.x + rectBBox.width,
|
||||||
annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + PADDING + GAP * 2,
|
annotationGroupHeight + labelGroupHeight + attributeGroupHeight + y + PADDING + GAP * 2,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
const line = shapeSvg.insert(() => roughLine);
|
const line = shapeSvg.insert(() => roughLine);
|
||||||
|
Reference in New Issue
Block a user