Merge branch 'release/8.13.0'

This commit is contained in:
Knut Sveidqvist 2021-09-23 18:59:51 +02:00
commit 83d3b31a1a
66 changed files with 3289 additions and 62911 deletions

View File

@ -1,4 +1,3 @@
'Type: Bug / Error': 'bug/*'
'Type: Enhancement': 'feature/*'
'Type: Other': 'other/*'
'Type: Dependabot': 'dependabot/*'

View File

@ -11,4 +11,4 @@ jobs:
- uses: andymckay/labeler@1.0.3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
labels: "Status: Triage"
add-labels: "Status: Triage"

View File

@ -1,6 +1,6 @@
name: Apply labels to PR
on:
pull_request:
pull_request_target:
types: [opened]
jobs:

View File

@ -370,7 +370,21 @@ describe('Class diagram V2', () => {
);
cy.get('svg');
});
it('16: should handle the direction statemment with TB', () => {
it('16a: should render a simple class diagram with static field', () => {
imgSnapshotTest(
`
classDiagram-v2
Foo {
+String bar$
}
`,
{logLevel : 1, flowchart: { "htmlLabels": false },}
);
cy.get('svg');
});
it('16b: should handle the direction statemnent with TB', () => {
imgSnapshotTest(
`
classDiagram
@ -394,55 +408,8 @@ describe('Class diagram V2', () => {
);
cy.get('svg');
});
it('17: should handle the direction statemment with BT', () => {
imgSnapshotTest(
`
classDiagram
direction BT
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
`,
{logLevel : 1, flowchart: { "htmlLabels": false },}
);
cy.get('svg');
});
it('17: should handle the direction statemment with RL', () => {
imgSnapshotTest(
`
classDiagram
direction RL
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
`,
{logLevel : 1, flowchart: { "htmlLabels": false },}
);
cy.get('svg');
});
it('18: should handle the direction statemment with LR', () => {
it('18: should handle the direction statemnent with LR', () => {
imgSnapshotTest(
`
classDiagram
@ -466,4 +433,52 @@ describe('Class diagram V2', () => {
);
cy.get('svg');
});
it('17a: should handle the direction statemnent with BT', () => {
imgSnapshotTest(
`
classDiagram
direction BT
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
`,
{logLevel : 1, flowchart: { "htmlLabels": false },}
);
cy.get('svg');
});
it('17b: should handle the direction statemment with RL', () => {
imgSnapshotTest(
`
classDiagram
direction RL
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
`,
{logLevel : 1, flowchart: { "htmlLabels": false },}
);
cy.get('svg');
});
});

View File

@ -186,4 +186,15 @@ describe('Entity Relationship Diagram', () => {
cy.get('svg');
});
it('should render entities with keys and comments', () => {
renderGraph(
`
erDiagram
BOOK { string title PK "comment"}
`,
{ logLevel : 1 }
);
cy.get('svg');
});
});

View File

@ -173,6 +173,18 @@ context('Sequence diagram', () => {
{}
);
});
it('should be possible to use actor symbols instead of boxes', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
`,
{}
);
});
it('should render long notes left of actor', () => {
imgSnapshotTest(
`

View File

@ -10,9 +10,10 @@
<style>
body {
/* background: rgb(221, 208, 208); */
background:#333;
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
width: 100%;
}
h1 { color: grey;}
.mermaid2,.mermaid3 {
@ -25,7 +26,7 @@
</head>
<body>
<div>info below</div>
<div class="flex">
<div class="flex flex-wrap">
<div class="mermaid2" style="width: 100%; height: 20%;">
classDiagram
@ -53,28 +54,56 @@ stateDiagram
</div>
<div class="mermaid" style="width: 100%; height: 20%;">
flowchart LR
one --> two
three -.-> four[whoa, big arrowhead nine o'clock]
sequenceDiagram
%%{init: {'config': {'wrap': true }}}%%
actor Alice as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
actor Bob
participant John as John2
participant Mandy
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
Alice->>John: Hi John
John->>Mandy: Hi Mandy
Mandy ->>Joan: Hi Joan
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
%%{init: { "apa":"b", "theme":"forest"}}%%
<div class="mermaid" style="width: 100%; height: 20%;">
%%{int: { "apa":"b", "theme":"forest"}}%%
sequenceDiagram
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
</div>
<div class="mermaid2">
%%{init: { 'theme':'base', '__proto__': {'polluted': 'asdf'}} }%%
%%{init: { 'theme':'base', '__proto__': {'polluted': 'asdf'}} }%%
graph LR
A --> B
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart TD
Link --> b
click Link href "&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29" "Tooltip for
Amet"
<div class="mermaid">
sequenceDiagram
autonumber
par Action 1
Alice->>John: Hello John, how are you?
and Action 2
Alice->>Bob: Hello Bob, how are you?
end
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
Note right of John: John is perceptive
John-->>-Alice: I feel great!
loop Every minute
John-->Alice: Great!
end
</div>
<div class="mermaid">
sequenceDiagram
%%{init: {'config': {'wrap': true }}}%%
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
</div>
<div class="mermaid" style="width: 100%; height: 20%;">
%%{init: {'config': {'wrap': true }}}%%
sequenceDiagram
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
A->>Bob: Hola
Bob-->A: Pasten !
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
stateDiagram-v2
@ -112,8 +141,8 @@ YourState
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'dark',
// theme: 'forest',
// theme: 'dark',
theme: 'forest',
arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 2,

670
dist/mermaid.core.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

64719
dist/mermaid.js vendored

File diff suppressed because one or more lines are too long

2
dist/mermaid.js.map vendored

File diff suppressed because one or more lines are too long

12
dist/mermaid.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,10 @@
- Introduction 📔
- 📔 Introduction
- [About Mermaid](README.md)
- [Deployment](n00b-gettingStarted.md)
- [Syntax and Configuration](n00b-syntaxReference.md)
- Diagram Syntax 📊
- 📊 Diagram Syntax
- [Flowchart](flowchart.md)
- [Sequence diagram](sequenceDiagram.md)
- [Class Diagram](classDiagram.md)
@ -16,21 +16,21 @@
- [Requirement Diagram](requirementDiagram.md)
- [Other Examples](examples.md)
- Deployment and Configuration ⚙️
- ⚙️ Deployment and Configuration
- [Tutorials](Tutorials.md)
- [API-Usage](usage.md)
- [Mermaid API Configuration](Setup.md)
- [Directives](directives.md)
- [Theming](theming.md)
- [mermaid CLI](mermaidCLI.md)
- [Mermaid CLI](mermaidCLI.md)
- [Advanced usage](n00b-advanced.md)
- Misc 📚
- 📚 Misc
- [Use-Cases and Integrations](integrations.md)
- [FAQ](faq.md)
- Contributions and Community 🙌
- 🙌 Contributions and Community
- [Overview for Beginners](n00b-overview.md)
- [Development and Contribution ](development.md)
- [Changelog](CHANGELOG.md)

View File

@ -228,10 +228,14 @@ To specify the visibility of a class member (i.e. any attribute or method), thes
- `~` Package/Internal
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notations to the end of the method, i.e.: after the `()`:
> - `*` Abstract e.g.: `someAbstractMethod()*`
> - `$` Static e.g.: `someStaticMethod()$`
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notations to the end of the field name:
> - `*` Static e.g.: `String someField$`
## Defining Relationship
A relationship is a general term covering the specific types of logical connections found on class and object diagrams.

View File

@ -726,6 +726,26 @@ defined in the linkStyle statement will belong to the fourth link in the graph:
linkStyle 3 stroke:#ff3,stroke-width:4px,color:red;
```
You can specify default to apply to all links, or you can give a list of link order numbers seperated by a comma.
Instead of giving a styles option, you can also use custom d3 curve types with the following syntax:
```
linkStyle default|numList|num interpolate curveType
```
If you want to add style options too, instead of writing:
```
linkStyle default interpolate cardinal
linkStyle default stroke:#ff3,stroke-width:4px,color:red;
```
You can combine them into:
```
linkStyle default interpolate cardinal stroke:#ff3,stroke-width:4px,color:red;
```
### Styling a node

View File

@ -191,6 +191,28 @@ erDiagram
The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens or underscores. Other than that, there are no restrictions, and there is no implicit set of valid data types.
#### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be "PK" or "FK", for Primary Key or Foreign Key. And a `comment` is defined by quotes at the end of an attribute. Comments themselves cannot have quote characters in them.
```mermaid
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string allowedDriver FK 'The license of the allowed driver'
string registrationNumber
string make
string model
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string driversLicense PK 'The license #'
string firstName
string lastName
int age
}
```
### Other Things
- If you want the relationship label to be more than one word, you must use double quotes around the phrase

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 KiB

After

Width:  |  Height:  |  Size: 809 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 KiB

After

Width:  |  Height:  |  Size: 809 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -7,8 +7,9 @@
<meta name="description" content="Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<!-- <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css"> -->
<link rel="stylesheet" href="theme.css"> <script src="//cdn.jsdelivr.net/npm/mermaid@8.12.0/dist/mermaid.min.js"></script>
<!-- <script src="http://localhost:9000/mermaid.js"></script> -->
<link rel="stylesheet" href="theme.css">
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@8.12.0/dist/mermaid.min.js"></script> -->
<script src="http://localhost:9000/mermaid.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -22,6 +22,17 @@
text-shadow: #7557c9 0px 0px 5px
}
</style>
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-153180559-1', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
</head>
<body class="leading-normal tracking-normal text-white gradient" style="font-family: 'Source Sans Pro', sans-serif;">
<!--Nav-->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -19,7 +19,7 @@ This section talks about the different ways to deploy Mermaid. Learning the [Syn
> More in depth information can be found on [Usage](./usage.md).
## 1. Using [The Live Editor](https://mermaidjs.github.io/mermaid-live-editor/edit).
## 1. Using [The Live Editor](https://mermaid-js.github.io/mermaid-live-editor/edit).
![EditingProcess](./img/Editing-process.png)

View File

@ -36,18 +36,53 @@ appearance by doing the following:
```
sequenceDiagram
participant John
participant Alice
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
participant Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```mermaid
sequenceDiagram
participant Alice
participant Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
### Actors
If you specifically want to use the actor symbol instead of a rectangle with text you can do so by using actor statements as per below.
```
sequenceDiagram
participant John
participant Alice
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```mermaid
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```mermaid
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
### Aliases

View File

@ -292,17 +292,16 @@ module.exports = (options) ->
## Advanced usage
**Error handling**
**Syntax validation without rendering (Work in Progress)**
The **mermaid.parse(txt)** function validates graph definitions without rendering a graph. **[This function is still a work in progress](https://github.com/mermaid-js/mermaid/issues/1066), find alternatives below.**
The function **mermaid.parse(txt)**, takes a text string as an argument and returns true if the definition follows mermaid's syntax and
false if it does not. The parseError function will be called when the parse function returns false.
When the parser encounters invalid syntax the **mermaid.parseError** function is called. It is possible to override this
function in order to handle the error in an application-specific way.
**Parsing text without rendering**
It is also possible to validate the syntax before rendering in order to streamline the user experience. The function
**mermaid.parse(txt)** takes a text string as an argument and returns true if the text is syntactically correct and
false if it is not. The parseError function will be called when the parse function returns false.
The code-example below in meta code illustrates how this could work:
```javascript
@ -320,6 +319,8 @@ var textFieldUpdated = function(){
bindEventHandler('change', 'code', textFieldUpdated);
```
**Alternative to mermaid.parse():**
One effective and more future-proof method of validating your graph deinitions, is to paste and render them via the [Mermaid Live Editor](https://mermaid-js.github.io/mermaid-live-editor/). This will ensure that your code is compliant with the syntax of Mermaid's most recent version.
## Configuration
@ -395,9 +396,7 @@ This way of setting the configuration is deprecated. Instead the preferred way i
## Using the mermaid.init call
#
Is it possible to set some configuration via the mermaid object. The two parameters that are supported using this
approach are:
To set some configuration via the mermaid object. The two parameters that are supported using this approach are:
* mermaid_config.startOnLoad
* mermaid_config.htmlLabels

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "8.12.0",
"version": "8.13.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.js",
"keywords": [

View File

@ -672,13 +672,21 @@ const class_box = (parent, node) => {
}
const classAttributes = [];
node.classData.members.forEach((str) => {
let parsedText = parseMember(str).displayText;
const parsedInfo = parseMember(str);
let parsedText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) {
parsedText = parsedText.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
const lbl = labelContainer
.node()
.appendChild(createLabel(parsedText, node.labelStyle, true, true));
.appendChild(
createLabel(
parsedText,
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
true,
true
)
);
let bbox = lbl.getBBox();
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = lbl.children[0];

View File

@ -274,7 +274,7 @@ export const drawClass = function (elem, classDef, conf) {
};
export const parseMember = function (text) {
const fieldRegEx = /(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+)/;
const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+) *(\*|\$)?$/;
const methodRegEx = /^([+|\-|~|#])?(\w+) *\( *(.*)\) *(\*|\$)? *(\w*[~|[\]]*\s*\w*~?)$/;
let fieldMatch = text.match(fieldRegEx);
@ -290,6 +290,7 @@ export const parseMember = function (text) {
};
const buildFieldDisplay = function (parsedText) {
let cssStyle = '';
let displayText = '';
try {
@ -297,15 +298,17 @@ const buildFieldDisplay = function (parsedText) {
let fieldType = parsedText[2] ? parsedText[2].trim() : '';
let genericType = parsedText[3] ? parseGenericTypes(parsedText[3].trim()) : '';
let fieldName = parsedText[4] ? parsedText[4].trim() : '';
let classifier = parsedText[5] ? parsedText[5].trim() : '';
displayText = visibility + fieldType + genericType + ' ' + fieldName;
cssStyle = parseClassifier(classifier);
} catch (err) {
displayText = parsedText;
}
return {
displayText: displayText,
cssStyle: '',
cssStyle: cssStyle,
};
};
@ -321,7 +324,6 @@ const buildMethodDisplay = function (parsedText) {
let returnType = parsedText[5] ? ' : ' + parseGenericTypes(parsedText[5]).trim() : '';
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
cssStyle = parseClassifier(classifier);
} catch (err) {
displayText = parsedText;

View File

@ -51,7 +51,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle abstract classifier', function () {
it('should handle abstract method classifier', function () {
const str = 'foo()*';
let actual = svgDraw.parseMember(str);
@ -59,7 +59,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle static classifier', function () {
it('should handle static method classifier', function () {
const str = 'foo()$';
let actual = svgDraw.parseMember(str);
@ -156,5 +156,13 @@ describe('class member Renderer, ', function () {
expect(actual.displayText).toBe('List<int> ids');
expect(actual.cssStyle).toBe('');
});
it('should handle static field classifier', function () {
const str = 'String foo$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('String foo');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
});
});

View File

@ -35,13 +35,20 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
const attrFontSize = conf.fontSize * 0.85;
const labelBBox = entityTextNode.node().getBBox();
const attributeNodes = []; // Intermediate storage for attribute nodes created so that we can do a second pass
let hasKeyType = false;
let hasComment = false;
let maxWidth = 0;
let maxTypeWidth = 0;
let maxNameWidth = 0;
let maxKeyWidth = 0;
let maxCommentWidth = 0;
let cumulativeHeight = labelBBox.height + heightPadding * 2;
let attrNum = 1;
attributes.forEach((item) => {
const attrPrefix = `${entityTextNode.node().id}-attr-${attrNum}`;
let nodeWidth = 0;
let nodeHeight = 0;
// Add a text node for the attribute type
const typeNode = groupNode
@ -73,16 +80,70 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
)
.text(item.attributeName);
// Keep a reference to the nodes so that we can iterate through them later
attributeNodes.push({ tn: typeNode, nn: nameNode });
const attributeNode = {};
attributeNode.tn = typeNode;
attributeNode.nn = nameNode;
const typeBBox = typeNode.node().getBBox();
const nameBBox = nameNode.node().getBBox();
maxTypeWidth = Math.max(maxTypeWidth, typeBBox.width);
maxNameWidth = Math.max(maxNameWidth, nameBBox.width);
nodeWidth += typeBBox.width;
nodeWidth += nameBBox.width;
cumulativeHeight += Math.max(typeBBox.height, nameBBox.height) + heightPadding * 2;
nodeHeight = Math.max(typeBBox.height, nameBBox.height);
if (hasKeyType || item.attributeKeyType !== undefined) {
const keyTypeNode = groupNode
.append('text')
.attr('class', 'er entityLabel')
.attr('id', `${attrPrefix}-name`)
.attr('x', 0)
.attr('y', 0)
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'left')
.attr(
'style',
'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
)
.text(item.attributeKeyType || '');
attributeNode.kn = keyTypeNode;
const keyTypeBBox = keyTypeNode.node().getBBox();
nodeWidth += keyTypeBBox.width;
maxKeyWidth = Math.max(maxKeyWidth, nodeWidth);
nodeHeight = Math.max(nodeHeight, keyTypeBBox.height);
hasKeyType = true;
}
if (hasComment || item.attributeComment !== undefined) {
const commentNode = groupNode
.append('text')
.attr('class', 'er entityLabel')
.attr('id', `${attrPrefix}-name`)
.attr('x', 0)
.attr('y', 0)
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'left')
.attr(
'style',
'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
)
.text(item.attributeComment || '');
attributeNode.cn = commentNode;
const commentNodeBBox = commentNode.node().getBBox();
nodeWidth += commentNodeBBox.width;
maxCommentWidth = Math.max(nodeWidth, nameBBox.width);
nodeHeight = Math.max(nodeHeight, commentNodeBBox.height);
hasComment = true;
}
attributeNode.height = nodeHeight;
// Keep a reference to the nodes so that we can iterate through them later
attributeNodes.push(attributeNode);
maxWidth = Math.max(maxWidth, nodeWidth);
cumulativeHeight += nodeHeight + heightPadding * 2;
attrNum += 1;
});
@ -90,10 +151,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
const bBox = {
width: Math.max(
conf.minEntityWidth,
Math.max(
labelBBox.width + conf.entityPadding * 2,
maxTypeWidth + maxNameWidth + widthPadding * 4
)
Math.max(labelBBox.width + conf.entityPadding * 2, maxWidth + widthPadding * 4)
),
height:
attributes.length > 0
@ -102,7 +160,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
};
// There might be some spare width for padding out attributes if the entity name is very long
const spareWidth = Math.max(0, bBox.width - (maxTypeWidth + maxNameWidth) - widthPadding * 4);
const spareWidth = Math.max(0, bBox.width - maxWidth - widthPadding * 4);
if (attributes.length > 0) {
// Position the entity label near the top of the entity bounding box
@ -115,51 +173,85 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
let heightOffset = labelBBox.height + heightPadding * 2; // Start at the bottom of the entity label
let attribStyle = 'attributeBoxOdd'; // We will flip the style on alternate rows to achieve a banded effect
attributeNodes.forEach((nodePair) => {
attributeNodes.forEach((attributeNode) => {
// Calculate the alignment y co-ordinate for the type/name of the attribute
const alignY =
heightOffset +
heightPadding +
Math.max(nodePair.tn.node().getBBox().height, nodePair.nn.node().getBBox().height) / 2;
const alignY = heightOffset + heightPadding + attributeNode.height / 2;
// Position the type of the attribute
nodePair.tn.attr('transform', 'translate(' + widthPadding + ',' + alignY + ')');
attributeNode.tn.attr('transform', 'translate(' + widthPadding + ',' + alignY + ')');
// Insert a rectangle for the type
const typeRect = groupNode
.insert('rect', '#' + nodePair.tn.node().id)
.insert('rect', '#' + attributeNode.tn.node().id)
.attr('class', `er ${attribStyle}`)
.attr('fill', conf.fill)
.attr('fill-opacity', '100%')
.attr('stroke', conf.stroke)
.attr('x', 0)
.attr('y', heightOffset)
.attr('width', maxTypeWidth + widthPadding * 2 + spareWidth / 2)
.attr('height', nodePair.tn.node().getBBox().height + heightPadding * 2);
.attr('width', maxTypeWidth * 2 + spareWidth / 2)
.attr('height', attributeNode.tn.node().getBBox().height + heightPadding * 2);
// Position the name of the attribute
nodePair.nn.attr(
attributeNode.nn.attr(
'transform',
'translate(' + (parseFloat(typeRect.attr('width')) + widthPadding) + ',' + alignY + ')'
);
// Insert a rectangle for the name
groupNode
.insert('rect', '#' + nodePair.nn.node().id)
.insert('rect', '#' + attributeNode.nn.node().id)
.attr('class', `er ${attribStyle}`)
.attr('fill', conf.fill)
.attr('fill-opacity', '100%')
.attr('stroke', conf.stroke)
.attr('x', `${typeRect.attr('x') + typeRect.attr('width')}`)
//.attr('x', maxTypeWidth + (widthPadding * 2))
.attr('y', heightOffset)
.attr('width', maxNameWidth + widthPadding * 2 + spareWidth / 2)
.attr('height', nodePair.nn.node().getBBox().height + heightPadding * 2);
.attr('height', attributeNode.nn.node().getBBox().height + heightPadding * 2);
if (hasKeyType) {
// Position the name of the attribute
attributeNode.kn.attr(
'transform',
'translate(' + (parseFloat(typeRect.attr('width')) + widthPadding) + ',' + alignY + ')'
);
// Insert a rectangle for the name
groupNode
.insert('rect', '#' + attributeNode.kn.node().id)
.attr('class', `er ${attribStyle}`)
.attr('fill', conf.fill)
.attr('fill-opacity', '100%')
.attr('stroke', conf.stroke)
.attr('x', `${typeRect.attr('x') + typeRect.attr('width')}`)
.attr('y', heightOffset)
.attr('width', maxKeyWidth + widthPadding * 2 + spareWidth / 2)
.attr('height', attributeNode.kn.node().getBBox().height + heightPadding * 2);
}
if (hasComment) {
// Position the name of the attribute
attributeNode.cn.attr(
'transform',
'translate(' + (parseFloat(typeRect.attr('width')) + widthPadding) + ',' + alignY + ')'
);
// Insert a rectangle for the name
groupNode
.insert('rect', '#' + attributeNode.cn.node().id)
.attr('class', `er ${attribStyle}`)
.attr('fill', conf.fill)
.attr('fill-opacity', '100%')
.attr('stroke', conf.stroke)
.attr('x', `${typeRect.attr('x') + typeRect.attr('width')}`)
.attr('y', heightOffset)
.attr('width', maxCommentWidth + widthPadding * 2 + spareWidth / 2)
.attr('height', attributeNode.cn.node().getBBox().height + heightPadding * 2);
}
// Increment the height offset to move to the next row
heightOffset +=
Math.max(nodePair.tn.node().getBBox().height, nodePair.nn.node().getBBox().height) +
heightPadding * 2;
heightOffset += attributeNode.height + heightPadding * 2;
// Flip the attribute style for row banding
attribStyle = attribStyle == 'attributeBoxOdd' ? 'attributeBoxEven' : 'attributeBoxOdd';

View File

@ -18,7 +18,9 @@
"erDiagram" return 'ER_DIAGRAM';
"{" { this.begin("block"); return 'BLOCK_START'; }
<block>\s+ /* skip whitespace in block */
<block>[A-Za-z][A-Za-z0-9\-_]* { return 'ATTRIBUTE_WORD'; }
<block>(?:PK)|(?:FK) return 'ATTRIBUTE_KEY'
<block>[A-Za-z][A-Za-z0-9\-_]* return 'ATTRIBUTE_WORD'
<block>\"[^"]*\" return 'COMMENT';
<block>[\n]+ /* nothing */
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
<block>. return yytext[0];
@ -95,6 +97,9 @@ attributes
attribute
: attributeType attributeName { $$ = { attributeType: $1, attributeName: $2 }; }
| attributeType attributeName attributeKeyType { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3 }; }
| attributeType attributeName COMMENT { $$ = { attributeType: $1, attributeName: $2, attributeComment: $3 }; }
| attributeType attributeName attributeKeyType COMMENT { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3, attributeComment: $4 }; }
;
attributeType
@ -105,6 +110,10 @@ attributeName
: ATTRIBUTE_WORD { $$=$1; }
;
attributeKeyType
: ATTRIBUTE_KEY { $$=$1; }
;
relSpec
: cardinality relType cardinality
{

View File

@ -42,6 +42,36 @@ describe('when parsing ER diagram it...', function () {
expect(entities[entity].attributes.length).toBe(1);
});
it('should allow an entity with a single attribute to be defined with a key', function () {
const entity = 'BOOK';
const attribute = 'string title PK';
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(1);
});
it('should allow an entity with a single attribute to be defined with a comment', function () {
const entity = 'BOOK';
const attribute = `string title "comment"`;
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(1);
});
it('should allow an entity with a single attribute to be defined with a key and a comment', function () {
const entity = 'BOOK';
const attribute = `string title PK "comment"`;
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(1);
});
it('should allow an entity with multiple attributes to be defined', function () {
const entity = 'BOOK';
const attribute1 = 'string title';

View File

@ -33,6 +33,7 @@
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
@ -103,8 +104,10 @@ directive
;
statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$$=$2;}
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
| signal 'NEWLINE'
| autonumber {yy.enableSequenceNumbers()}
| 'activate' actor 'NEWLINE' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};}
@ -197,9 +200,13 @@ signal
{ $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
;
actor
: ACTOR {$$={type: 'addActor', actor:$1}}
;
// actor
// : actor_participant
// | actor_actor
// ;
actor: ACTOR {$$={ type: 'addParticipant', actor:$1}};
// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}};
signaltype
: SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; }

View File

@ -15,14 +15,17 @@ export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
export const addActor = function (id, name, description) {
export const addActor = function (id, name, description, type) {
// Don't allow description nulling
const old = actors[id];
if (old && name === old.name && description == null) return;
// Don't allow null descriptions, either
if (description == null || description.text == null) {
description = { text: name, wrap: null };
description = { text: name, wrap: null, type };
}
if (type == null || description.text == null) {
description = { text: name, wrap: null, type };
}
actors[id] = {
@ -30,6 +33,7 @@ export const addActor = function (id, name, description) {
description: description.text,
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
prevActor: prevActor,
type: type || 'participant',
};
if (prevActor && actors[prevActor]) {
actors[prevActor].nextActor = id;
@ -218,8 +222,11 @@ export const apply = function (param) {
});
} else {
switch (param.type) {
case 'addParticipant':
addActor(param.actor, param.actor, param.description, 'participant');
break;
case 'addActor':
addActor(param.actor, param.actor, param.description);
addActor(param.actor, param.actor, param.description, 'actor');
break;
case 'activeStart':
addSignal(param.actor, undefined, undefined, param.signalType);

View File

@ -121,6 +121,55 @@ B-->A: I am good thanks!`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['A', 'B']);
expect(actors.A.description).toBe('Alice');
expect(actors.B.description).toBe('Bob');
const messages = parser.yy.getMessages();
expect(messages.length).toBe(2);
expect(messages[0].from).toBe('A');
expect(messages[1].from).toBe('B');
});
it('it should alias a mix of actors and participants apa12', function() {
const str = `
sequenceDiagram
actor Alice as Alice2
actor Bob
participant John as John2
participant Mandy
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
Alice->>John: Hi John
John->>Mandy: Hi Mandy
Mandy ->>Joan: Hi Joan`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['Alice', 'Bob', 'John', 'Mandy', 'Joan']);
expect(actors.Alice.description).toBe('Alice2');
expect(actors.Alice.type).toBe('actor');
expect(actors.Bob.description).toBe('Bob');
expect(actors.John.type).toBe('participant');
expect(actors.Joan.type).toBe('participant');
const messages = parser.yy.getMessages();
expect(messages.length).toBe(5);
expect(messages[0].from).toBe('Alice');
expect(messages[4].to).toBe('Joan');
});
it('it should alias actors apa13', function() {
const str = `
sequenceDiagram
actor A as Alice
actor B as Bob
A->B:Hello Bob, how are you?
B-->A: I am good thanks!`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['A', 'B']);
expect(actors.A.description).toBe('Alice');
@ -1452,7 +1501,7 @@ participant Alice`;
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + conf.boxMargin);
});
});
});
@ -1501,7 +1550,7 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin);
});
it('it should handle one actor, when logLevel is 3', function() {
const str = `
@ -1519,6 +1568,6 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin);
});
});

View File

@ -1,5 +1,5 @@
import { select, selectAll } from 'd3';
import svgDraw, { drawText } from './svgDraw';
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw';
import { log } from '../../logger';
import { parser } from './parser/sequenceDiagram';
import common from '../common/common';
@ -421,7 +421,7 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
// Draw the actors
let prevWidth = 0;
let prevMargin = 0;
let maxHeight = 0;
for (let i = 0; i < actorKeys.length; i++) {
const actor = actors[actorKeys[i]];
@ -434,7 +434,8 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
actor.y = verticalPos;
// Draw the box with the attached line
svgDraw.drawActor(diagram, actor, conf);
const height = svgDraw.drawActor(diagram, actor, conf);
maxHeight = Math.max(maxHeight, height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width;
@ -443,7 +444,7 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
}
// Add a margin between the actor boxes and the first arrow
bounds.bumpVerticalPos(conf.height);
bounds.bumpVerticalPos(maxHeight);
};
export const setConf = function (cnf) {
@ -688,6 +689,8 @@ export const draw = function (text, id) {
// Draw actors below diagram
bounds.bumpVerticalPos(conf.boxMargin * 2);
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos());
bounds.bumpVerticalPos(conf.boxMargin);
fixLifeLineHeights(diagram, bounds.getVerticalPos());
}
const { bounds: box } = bounds.getBounds();

View File

@ -95,6 +95,15 @@ const getStyles = (options) =>
fill: ${options.activationBkgColor};
stroke: ${options.activationBorderColor};
}
.actor-man line {
stroke: ${options.actorBorder};
fill: ${options.actorBkg};
}
.actor-man circle, line {
stroke: ${options.actorBorder};
fill: ${options.actorBkg};
stroke-width: 2px;
}
`;
export default getStyles;

View File

@ -181,13 +181,22 @@ export const drawLabel = function (elem, txtObject) {
};
let actorCnt = -1;
export const fixLifeLineHeights = (diagram, bounds) => {
if (!diagram.selectAll) return;
diagram
.selectAll('.actor-line')
.attr('class', '200')
.attr('y2', bounds - 55);
};
/**
* Draws an actor in the diagram with the attached line
* @param elem - The diagram we'll draw to.
* @param actor - The actor to draw.
* @param conf - drawText implementation discriminator object
*/
export const drawActor = function (elem, actor, conf) {
const drawActorTypeParticipant = function (elem, actor, conf) {
const center = actor.x + actor.width / 2;
const g = elem.append('g');
@ -213,7 +222,7 @@ export const drawActor = function (elem, actor, conf) {
rect.class = 'actor';
rect.rx = 3;
rect.ry = 3;
drawRect(g, rect);
const rectElem = drawRect(g, rect);
_drawTextCandidateFunc(conf)(
actor.description,
@ -225,6 +234,105 @@ export const drawActor = function (elem, actor, conf) {
{ class: 'actor' },
conf
);
let height = actor.height;
if (rectElem.node) {
const bounds = rectElem.node().getBBox();
actor.height = bounds.height;
height = bounds.height;
}
return height;
};
const drawActorTypeActor = function (elem, actor, conf) {
const center = actor.x + actor.width / 2;
if (actor.y === 0) {
actorCnt++;
elem
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', 80)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999');
}
const actElem = elem.append('g');
actElem.attr('class', 'actor-man');
const rect = getNoteRect();
rect.x = actor.x;
rect.y = actor.y;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
rect.rx = 3;
rect.ry = 3;
// drawRect(actElem, rect);
actElem
.append('line')
.attr('id', 'actor-man-torso' + actorCnt)
.attr('x1', center)
.attr('y1', actor.y + 25)
.attr('x2', center)
.attr('y2', actor.y + 45);
actElem
.append('line')
.attr('id', 'actor-man-arms' + actorCnt)
.attr('x1', center - 18)
.attr('y1', actor.y + 33)
.attr('x2', center + 18)
.attr('y2', actor.y + 33);
actElem
.append('line')
.attr('x1', center - 18)
.attr('y1', actor.y + 60)
.attr('x2', center)
.attr('y2', actor.y + 45);
actElem
.append('line')
.attr('x1', center)
.attr('y1', actor.y + 45)
.attr('x2', center + 16)
.attr('y2', actor.y + 60);
const circle = actElem.append('circle');
circle.attr('cx', actor.x + actor.width / 2);
circle.attr('cy', actor.y + 10);
circle.attr('r', 15);
circle.attr('width', actor.width);
circle.attr('height', actor.height);
const bounds = actElem.node().getBBox();
actor.height = bounds.height;
_drawTextCandidateFunc(conf)(
actor.description,
actElem,
rect.x,
rect.y + 35,
rect.width,
rect.height,
{ class: 'actor' },
conf
);
return actor.height;
};
export const drawActor = function (elem, actor, conf) {
switch (actor.type) {
case 'actor':
return drawActorTypeActor(elem, actor, conf);
case 'participant':
return drawActorTypeParticipant(elem, actor, conf);
}
};
export const anchorElement = function (elem) {
@ -576,4 +684,5 @@ export default {
insertArrowCrossHead,
getTextObj,
getNoteRect,
fixLifeLineHeights,
};