mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-25 08:54:07 +02:00
feat: support multiple stereotypes in class diagrams
This commit is contained in:
@@ -661,6 +661,19 @@ 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 multiple annotations', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`classDiagram
|
||||||
|
class C1["Class 1 with text label"] {
|
||||||
|
<<interface>> <<injected>>
|
||||||
|
+member1
|
||||||
|
}
|
||||||
|
C1 --> C2`,
|
||||||
|
{ logLevel: 1, htmlLabels: true, layout: 'elk' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('ELK: should render multiple classes with same text labels', () => {
|
it('ELK: should render multiple classes with same text labels', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
|
|||||||
@@ -661,6 +661,19 @@ class Class10
|
|||||||
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('HD: should render a class with a text label, membersand multiple annotations', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`classDiagram
|
||||||
|
class C1["Class 1 with text label"] {
|
||||||
|
<<interface>> <<injected>>
|
||||||
|
+member1
|
||||||
|
}
|
||||||
|
C1 --> C2`,
|
||||||
|
{ logLevel: 1, htmlLabels: true, look: 'handDrawn' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('HD: should render multiple classes with same text labels', () => {
|
it('HD: should render multiple classes with same text labels', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
|
|||||||
@@ -510,6 +510,16 @@ class Class10
|
|||||||
C1 --> C2`
|
C1 --> C2`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('should render a class with a text label, members and multiple annotations', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`classDiagram
|
||||||
|
class C1["Class 1 with text label"] {
|
||||||
|
<<interface>> <<injected>>
|
||||||
|
+member1
|
||||||
|
}
|
||||||
|
C1 --> C2`
|
||||||
|
);
|
||||||
|
});
|
||||||
it('should render multiple classes with same text labels', () => {
|
it('should render multiple classes with same text labels', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
|
|||||||
@@ -657,6 +657,17 @@ class Class10
|
|||||||
C1 --> C2`
|
C1 --> C2`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('should render a class with a text label, members and multiple annotations', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`classDiagram
|
||||||
|
class C1["Class 1 with text label"] {
|
||||||
|
<<interface>> <<injected>>
|
||||||
|
+member1
|
||||||
|
}
|
||||||
|
C1 --> C2`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should render multiple classes with same text labels', () => {
|
it('should render multiple classes with same text labels', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`classDiagram
|
`classDiagram
|
||||||
|
|||||||
@@ -452,6 +452,20 @@ describe('Class diagram', () => {
|
|||||||
<<Interface>> \`This\nTitle\nHas\nMany\nNewlines\`
|
<<Interface>> \`This\nTitle\nHas\nMany\nNewlines\`
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
it('should render with newlines in title and multiple annotations', () => {
|
||||||
|
imgSnapshotTest(`
|
||||||
|
classDiagram
|
||||||
|
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||||
|
+String Also
|
||||||
|
-String Many
|
||||||
|
#int Members
|
||||||
|
+And()
|
||||||
|
-Many()
|
||||||
|
#Methods()
|
||||||
|
}
|
||||||
|
<<Interface>> <<Service>> \`This\nTitle\nHas\nMany\nNewlines\`
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle newline title in namespace', () => {
|
it('should handle newline title in namespace', () => {
|
||||||
imgSnapshotTest(`
|
imgSnapshotTest(`
|
||||||
|
|||||||
93
demos/class-multiple-stereotypes.html
Normal file
93
demos/class-multiple-stereotypes.html
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>Class Diagram - Multiple Stereotypes Test</title>
|
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
|
<style>
|
||||||
|
div.mermaid {
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Multiple Stereotypes Test</h1>
|
||||||
|
|
||||||
|
<h2>Test 1: Inline with class definition (single)</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape <<interface>>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Test 2: Inline with class definition (multiple)</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape <<interface>> <<injected>>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Test 3: Separate line (single)</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
<<interface>> Shape
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Test 4: Separate line (multiple)</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
<<interface>> <<injected>> Shape
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Test 5: Inside class body (single)</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape{
|
||||||
|
<<interface>>
|
||||||
|
noOfVertices
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Test 6: Inside class body (multiple on same line)</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape{
|
||||||
|
<<interface>> <<injected>>
|
||||||
|
noOfVertices
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Test 7: Combined example</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape{
|
||||||
|
<<interface>> <<injected>>
|
||||||
|
noOfVertices
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
class Color{
|
||||||
|
<<enumeration>>
|
||||||
|
RED
|
||||||
|
BLUE
|
||||||
|
GREEN
|
||||||
|
WHITE
|
||||||
|
BLACK
|
||||||
|
}
|
||||||
|
Shape <|-- Color
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'default',
|
||||||
|
logLevel: 3,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
+quack()
|
+quack()
|
||||||
}
|
}
|
||||||
class Fish{
|
class Fish{
|
||||||
-Listint sizeInFeet
|
-int sizeInFeet
|
||||||
-canEat()
|
-canEat()
|
||||||
}
|
}
|
||||||
class Zebra{
|
class Zebra{
|
||||||
@@ -143,21 +143,7 @@
|
|||||||
Pineapple : -int leafCount()
|
Pineapple : -int leafCount()
|
||||||
Pineapple : -int spikeCount()
|
Pineapple : -int spikeCount()
|
||||||
</pre>
|
</pre>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre class="mermaid">
|
|
||||||
classDiagram
|
|
||||||
class Person {
|
|
||||||
+ID : Guid
|
|
||||||
+FirstName : string
|
|
||||||
+LastName : string
|
|
||||||
-privateProperty : string
|
|
||||||
#ProtectedProperty : string
|
|
||||||
~InternalProperty : string
|
|
||||||
~AnotherInternalProperty : List~List~string~~
|
|
||||||
}
|
|
||||||
class People List~List~Person~~
|
|
||||||
</pre>
|
|
||||||
<hr />
|
<hr />
|
||||||
<pre class="mermaid">
|
<pre class="mermaid">
|
||||||
classDiagram
|
classDiagram
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
%x acc_descr_multiline
|
%x acc_descr_multiline
|
||||||
%x class
|
%x class
|
||||||
%x class-body
|
%x class-body
|
||||||
|
%x class-body-annotation
|
||||||
%x namespace
|
%x namespace
|
||||||
%x namespace-body
|
%x namespace-body
|
||||||
%%
|
%%
|
||||||
@@ -82,9 +83,16 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
|||||||
<class-body><<EOF>> return "EOF_IN_STRUCT";
|
<class-body><<EOF>> return "EOF_IN_STRUCT";
|
||||||
<class-body>"[*]" { return 'EDGE_STATE';}
|
<class-body>"[*]" { return 'EDGE_STATE';}
|
||||||
<class-body>[{] return "OPEN_IN_STRUCT";
|
<class-body>[{] return "OPEN_IN_STRUCT";
|
||||||
|
<class-body>"<<" { this.begin("class-body-annotation"); return 'ANNOTATION_START';}
|
||||||
<class-body>[\n] /* nothing */
|
<class-body>[\n] /* nothing */
|
||||||
|
<class-body>[ \t]+ /* skip whitespace in class body */
|
||||||
<class-body>[^{}\n]* { return "MEMBER";}
|
<class-body>[^{}\n]* { return "MEMBER";}
|
||||||
|
|
||||||
|
<class-body-annotation>">>" { this.popState(); return 'ANNOTATION_END';}
|
||||||
|
<class-body-annotation>[0-9]+ return 'NUM';
|
||||||
|
<class-body-annotation>\w+ return 'ALPHA';
|
||||||
|
<class-body-annotation>[\s]+ /* ignore whitespace */
|
||||||
|
|
||||||
<*>"cssClass" return 'CSSCLASS';
|
<*>"cssClass" return 'CSSCLASS';
|
||||||
<*>"callback" return 'CALLBACK';
|
<*>"callback" return 'CALLBACK';
|
||||||
<*>"link" return 'LINK';
|
<*>"link" return 'LINK';
|
||||||
@@ -294,12 +302,26 @@ classStatement
|
|||||||
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
||||||
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
|
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
|
||||||
| classIdentifier STRUCT_START STRUCT_STOP {}
|
| classIdentifier STRUCT_START STRUCT_STOP {}
|
||||||
|
| classIdentifier STRUCT_START NEWLINE members STRUCT_STOP {yy.addMembers($1,$4);}
|
||||||
|
| classIdentifier STRUCT_START annotationList members STRUCT_STOP {for(const annotation of $3) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$4);}
|
||||||
|
| classIdentifier STRUCT_START annotationList STRUCT_STOP {for(const annotation of $3) { yy.addAnnotation($1, annotation); }}
|
||||||
|
| classIdentifier STRUCT_START NEWLINE annotationList members STRUCT_STOP {for(const annotation of $4) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$5);}
|
||||||
|
| classIdentifier STRUCT_START NEWLINE annotationList STRUCT_STOP {for(const annotation of $4) { yy.addAnnotation($1, annotation); }}
|
||||||
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
|
||||||
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START NEWLINE members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$6);}
|
||||||
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START annotationList members STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $5) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$6);}
|
||||||
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START annotationList STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $5) { yy.addAnnotation($1, annotation); }}
|
||||||
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START NEWLINE annotationList members STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $6) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$7);}
|
||||||
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START NEWLINE annotationList STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $6) { yy.addAnnotation($1, annotation); }}
|
||||||
;
|
;
|
||||||
|
|
||||||
classIdentifier
|
classIdentifier
|
||||||
: CLASS className {$$=$2; yy.addClass($2);}
|
: CLASS className {$$=$2; yy.addClass($2);}
|
||||||
|
| CLASS className ANNOTATION_START alphaNumToken ANNOTATION_END {$$=$2; yy.addClass($2); yy.addAnnotation($2,$4);}
|
||||||
|
| CLASS className annotationList {$$=$2; yy.addClass($2); for(const annotation of $3) { yy.addAnnotation($2, annotation); }}
|
||||||
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
|
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
|
||||||
|
| CLASS className classLabel ANNOTATION_START alphaNumToken ANNOTATION_END {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3); yy.addAnnotation($2,$5);}
|
||||||
|
| CLASS className classLabel annotationList {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3); for(const annotation of $4) { yy.addAnnotation($2, annotation); }}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
@@ -311,6 +333,12 @@ emptyBody
|
|||||||
|
|
||||||
annotationStatement
|
annotationStatement
|
||||||
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
|
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
|
||||||
|
| annotationList className { for(const annotation of $1) { yy.addAnnotation($2, annotation); } }
|
||||||
|
;
|
||||||
|
|
||||||
|
annotationList
|
||||||
|
: ANNOTATION_START alphaNumToken ANNOTATION_END { $$ = [$2]; }
|
||||||
|
| annotationList ANNOTATION_START alphaNumToken ANNOTATION_END { $1.push($3); $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
members
|
members
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export async function textHelper<T extends SVGGraphicsElement>(
|
|||||||
|
|
||||||
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) {
|
||||||
const annotation = node.annotations[0];
|
const annotationText = node.annotations.map((a: string) => `«${a}»`).join('\n');
|
||||||
await addText(annotationGroup, { text: `«${annotation}»` } as unknown as ClassMember, 0);
|
await addText(annotationGroup, { text: annotationText } as unknown as ClassMember, 0);
|
||||||
|
|
||||||
const annotationGroupBBox = annotationGroup.node()!.getBBox();
|
const annotationGroupBBox = annotationGroup.node()!.getBBox();
|
||||||
annotationGroupHeight = annotationGroupBBox.height;
|
annotationGroupHeight = annotationGroupBBox.height;
|
||||||
|
|||||||
@@ -358,17 +358,19 @@ It is possible to annotate classes with markers to provide additional metadata a
|
|||||||
- `<<Service>>` To represent a service class
|
- `<<Service>>` To represent a service class
|
||||||
- `<<Enumeration>>` To represent an enum
|
- `<<Enumeration>>` To represent an enum
|
||||||
|
|
||||||
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class, and either way the output will be same:
|
Annotations are defined within the opening `<<` and closing `>>`. There are multiple ways to add an annotation to a class, which all result in the same output, and you can add multiple annotations by adding others on the same line:
|
||||||
|
|
||||||
> **Tip:**
|
|
||||||
> In Mermaid class diagrams, annotations like `<<interface>>` can be attached in two ways:
|
|
||||||
>
|
|
||||||
> - **Inline with the class definition** (Recommended for consistency):
|
> - **Inline with the class definition** (Recommended for consistency):
|
||||||
>
|
>
|
||||||
> ```mermaid-example
|
> ```mermaid-example
|
||||||
> classDiagram
|
> classDiagram
|
||||||
> class Shape <<interface>>
|
> class Shape <<interface>>
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
|
> ```mermaid-example
|
||||||
|
> classDiagram
|
||||||
|
> class Shape <<interface>> <<injected>>
|
||||||
|
> ```
|
||||||
>
|
>
|
||||||
> - **Separate line after the class definition**:
|
> - **Separate line after the class definition**:
|
||||||
>
|
>
|
||||||
@@ -378,25 +380,18 @@ Annotations are defined within the opening `<<` and closing `>>`. There are two
|
|||||||
> <<interface>> Shape
|
> <<interface>> Shape
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
> Both methods are fully supported and produce identical diagrams.
|
> ```mermaid-example
|
||||||
> However, it is recommended to use the **inline style** for better readability and consistent formatting across diagrams.
|
> classDiagram
|
||||||
|
> class Shape
|
||||||
|
> <<interface>> <<injected>> Shape
|
||||||
|
> ```
|
||||||
|
|
||||||
- In a **_separate line_** after a class is defined:
|
> In a **_nested structure_** along with the class definition:
|
||||||
|
|
||||||
```mermaid-example
|
|
||||||
classDiagram
|
|
||||||
class Shape
|
|
||||||
<<interface>> Shape
|
|
||||||
Shape : noOfVertices
|
|
||||||
Shape : draw()
|
|
||||||
```
|
|
||||||
|
|
||||||
- In a **_nested structure_** along with the class definition:
|
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
classDiagram
|
classDiagram
|
||||||
class Shape{
|
class Shape{
|
||||||
<<interface>>
|
<<interface>> <<injected>>
|
||||||
noOfVertices
|
noOfVertices
|
||||||
draw()
|
draw()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user