Compare commits

..

1 Commits

Author SHA1 Message Date
Sidharth Vinod
b79ea6c0fe Add no-var rule. 2022-09-05 22:31:57 +05:30
139 changed files with 1243 additions and 3888 deletions

View File

@@ -23,9 +23,9 @@
],
"plugins": ["@typescript-eslint", "html", "jest", "jsdoc", "json"],
"rules": {
"no-console": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off",
"no-var": "error",
"jsdoc/check-indentation": "off",
"jsdoc/check-alignment": "off",
"jsdoc/check-line-alignment": "off",
@@ -57,12 +57,6 @@
"no-undef": "off",
"jsdoc/require-jsdoc": "off"
}
},
{
"files": ["./cypress/**", "./demos/**"],
"rules": {
"no-console": "off"
}
}
]
}

View File

@@ -40,18 +40,3 @@ jobs:
- name: Verify Docs
run: yarn docs:verify
- name: Check no `console.log()` in .jison files
# ESLint can't parse .jison files directly
# In the future, it might be worth making a `eslint-plugin-jison`, so
# that this will be built into the `yarn lint` command.
run: |
shopt -s globstar
mkdir -p tmp/
for jison_file in src/**/*.jison; do
outfile="tmp/$(basename -- "$jison_file" .jison)-jison.js"
echo "Converting $jison_file to $outfile"
# default module-type (CJS) always adds a console.log()
yarn jison "$jison_file" --outfile "$outfile" --module-type "amd"
done
yarn eslint --no-eslintrc --rule no-console:error --parser "@babel/eslint-parser" "./tmp/*-jison.js"

View File

@@ -20,8 +20,8 @@ yarn test
We make all changes via pull requests. As we have many pull requests from developers new to mermaid, the current approach is to have _knsv, Knut Sveidqvist_ as a main reviewer of changes and merging pull requests. More precisely like this:
- Large changes reviewed by knsv or other developer asked to review by knsv
- Smaller low-risk changes like dependencies, documentation, etc. can be merged by active collaborators
- Documentation (updates to the `src/docs` folder is also allowed via direct commits)
- Smaller low-risk changes like dependencies, documentation etc can be merged by active collaborators
- documentation (updates to the docs folder is also allowed via direct commits)
To commit code, create a branch, let it start with the type like feature or bug followed by the issue number for reference and some describing text.
@@ -37,28 +37,12 @@ Another:
Less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator.
The documentation is written in **Markdown**. For more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).
The documentation is located in the `docs` directory and published using GitHub Pages.
The documentation site is powered by [Docsify](https://docsify.js.org), a simple documentation site generator.
### Documentation source files are in /src/docs
The documentation is written in Markdown, for more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).
The source files for the project documentation are located in the `/src/docs` directory. This is where you should make changes.
The files under `/src/docs` are processed to generate the published documentation, and the resulting files are put into the `/docs` directory.
```mermaid
flowchart LR
classDef default fill:#fff,color:black,stroke:black
source["files in /src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"]
```
**_DO NOT CHANGE FILES IN `/docs`_**
### The official documentation site
**[The mermaid documentation site](https://mermaid-js.github.io/mermaid/) is powered by [Docsify](https://docsify.js.org), a simple documentation site generator.**
If you want to preview the whole documentation site on your machine, you need to install `docsify-cli`:
If you want to preview the documentation site on your machine, you need to install `docsify-cli`:
```sh
$ npm i docsify-cli -g
@@ -137,13 +121,9 @@ it('should render forks and joins', () => {
Finally, if it is not in the documentation, no one will know about it and then **no one will use it**. Wouldn't that be sad? With all the effort that was put into the feature?
The source files for documentation are in `/src/docs` and are written in markdown. Just pick the right section and start typing. See the [Committing Documentation](#committing-documentation) section for more about how the documentation is generated.
The docs are located in the docs folder and are ofc written in markdown. Just pick the right section and start typing. If you want to add to the structure as in adding a new section and new file you do that via the \_navbar.md.
#### Adding to or changing the documentation organization
If you want to add a new section or change the organization (structure), then you need to make sure to **change the side navigation** in `src/docs/_sidebar.md`.
When changes are committed and then released, they become part of the `master` branch and become part of the published documentation on https://mermaid-js.github.io/mermaid/
The changes in master is reflected in https://mermaid-js.github.io/mermaid/ once released the updates are committed to https://mermaid-js.github.io/#/
## Last words

View File

@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { defineConfig } = require('cypress');
const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin');
require('@applitools/eyes-cypress')(module);

View File

@@ -1 +0,0 @@
Cr24

View File

@@ -1,75 +0,0 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
describe('Mindmap', () => {
it('square shape', () => {
imgSnapshotTest(
`
mindmap
root[
The root
]
`,
{}
);
cy.get('svg');
});
it('rounded rect shape', () => {
imgSnapshotTest(
`
mindmap
root((
The root
))
`,
{}
);
cy.get('svg');
});
it('circle shape', () => {
imgSnapshotTest(
`
mindmap
root(
The root
)
`,
{}
);
cy.get('svg');
});
it('default shape', () => {
imgSnapshotTest(
`
mindmap
The root
`,
{}
);
cy.get('svg');
});
it('adding children', () => {
imgSnapshotTest(
`
mindmap
The root
child1
child2
`,
{}
);
cy.get('svg');
});
it('adding grand children', () => {
imgSnapshotTest(
`
mindmap
The root
child1
child2
child3
`,
{}
);
cy.get('svg');
});
});

View File

@@ -1,115 +0,0 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
describe('Mindmaps', () => {
it('Only a root', () => {
imgSnapshotTest(
`mindmap
root
`,
{}
);
});
it('a root with a shape', () => {
imgSnapshotTest(
`mindmap
root[root]
`,
{}
);
});
it('a root with wrapping text and a shape', () => {
imgSnapshotTest(
`mindmap
root[A root with a long text that wraps to keep the node size in check]
`,
{}
);
});
it('a root with an icon', () => {
imgSnapshotTest(
`mindmap
root[root]
::icon(mdi mdi-fire)
`,
{}
);
});
it('Blang and cloud shape', () => {
imgSnapshotTest(
`mindmap
root))bang((
::icon(mdi mdi-fire)
a))Another bang((
::icon(mdi mdi-fire)
a)A cloud(
::icon(mdi mdi-fire)
`,
{}
);
});
it('Blang and cloud shape with icons', () => {
imgSnapshotTest(
`mindmap
root))bang((
a))Another bang((
a)A cloud(
`,
{}
);
});
it('braches', () => {
imgSnapshotTest(
`mindmap
root
child1
grandchild 1
grandchild 2
child2
grandchild 3
grandchild 4
child3
grandchild 5
grandchild 6
`,
{}
);
});
it('braches with shapes and labels', () => {
imgSnapshotTest(
`mindmap
root
child1((Circle))
grandchild 1
grandchild 2
child2(Round rectangle)
grandchild 3
grandchild 4
child3[Square]
grandchild 5
::icon(mdi mdi-fire)
gc6((grand<br/>child 6))
::icon(mdi mdi-fire)
`,
{}
);
});
it('text shouhld wrap with icon', () => {
imgSnapshotTest(
`mindmap
root
Child3(A node with an icon and with a long text that wraps to keep the node size in check)
`,
{}
);
});
/* The end */
});

View File

@@ -38,7 +38,7 @@
</style>
</head>
<body>
<pre class="mermaid" style="width: 50%">
<pre class="mermaid2" style="width: 50%">
flowchart LR
classDef aPID stroke:#4e4403,fill:#fdde29,color:#4e4403,rx:5px,ry:5px;
classDef crm stroke:#333333,fill:#DCDCDC,color:#333333,rx:5px,ry:5px;
@@ -99,7 +99,7 @@ flowchart TD
class A someclass;
class C someclass;
</pre>
<pre class="mermaid" style="width: 50%">
<pre class="mermaid2" style="width: 50%">
sequenceDiagram
title: My Sequence Diagram Title
accTitle: My Acc Sequence Diagram
@@ -109,14 +109,14 @@ flowchart TD
John-->>Alice: Great!
Alice-)John: See you later!
</pre>
<pre class="mermaid" style="width: 50%">
<pre class="mermaid2" style="width: 50%">
graph TD
A -->|000| B
B -->|111| C
linkStyle 1 stroke:#ff3,stroke-width:4px,color:red;
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
journey
accTitle: My User Journey Diagram
accDescr: My User Journey Diagram Description
@@ -130,10 +130,10 @@ graph TD
Go downstairs: 5: Me
Sit down: 5: Me
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
info
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
requirementDiagram
accTitle: My req Diagram
accDescr: My req Diagram Description
@@ -174,7 +174,7 @@ requirementDiagram
test_req - contains -> test_req3
test_req <- copies - test_entity2
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
@@ -206,7 +206,7 @@ gantt
Add gantt diagram to demo page :20h
Add another diagram to demo page :48h
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
stateDiagram
state Active {
Idle
@@ -234,7 +234,7 @@ stateDiagram
end
B ->> A: Return
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
classDiagram
accTitle: My class diagram
accDescr: My class diagram Description
@@ -259,7 +259,7 @@ class Class10 {
A->>Bob: Hola
Bob-->A: Pasten !
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
gitGraph
commit id: "ZERO"
branch develop
@@ -288,7 +288,7 @@ flowchart TD
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
classDiagram
Animal "1" <|-- Duck
Animal <|-- Fish
@@ -311,7 +311,7 @@ flowchart TD
+run()
}
</pre>
<pre class="mermaid" style="width: 100%">
<pre class="mermaid2" style="width: 100%">
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {

View File

@@ -17,9 +17,9 @@
rerender('XMas');
function rerender(text) {
var graphText = `graph TD
let graphText = `graph TD
A[${text}] -->|Get money| B(Go shopping)`;
var graph = mermaid.mermaidAPI.render('id', graphText);
let graph = mermaid.mermaidAPI.render('id', graphText);
console.log('\x1b[35m%s\x1b[0m', '>> graph', graph);
document.getElementById('graph').innerHTML = graph;
}

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'classDiagram\n';
let diagram = 'classDiagram\n';
diagram += 'class Square~<img/src';
diagram += "='1'/onerror=xssAttack()>~{\n";
diagram += 'id A\n';

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'stateDiagram-v2\n';
let diagram = 'stateDiagram-v2\n';
diagram += 's2 : This is a state description<img/src';
diagram += "='1'/onerror=xssAttack()>";

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'stateDiagram-v2\n';
let diagram = 'stateDiagram-v2\n';
diagram += 's2 : A<img/src';
diagram += "='1'/onerror=xssAttack()>";

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'stateDiagram-v2\n';
let diagram = 'stateDiagram-v2\n';
diagram += 'if_state --> False: if n < 0<img/src';
diagram += "='1'/onerror=xssAttack()>";

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'classDiagram\n';
let diagram = 'classDiagram\n';
diagram += 'classA <-- classB : <ifr';
diagram += "ame/srcdoc='<scr";
diagram += 'ipt>parent.xssAttack(`XSS`)</';

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = `sequenceDiagram
let diagram = `sequenceDiagram
participant John
links John: {"XSS": "javas`;
diagram += `cript:alert('AudioParam')"}`;

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = `sequenceDiagram
let diagram = `sequenceDiagram
participant Alice
links Alice: { "Click me!" : "javasjavascript:cript:alert('goose')" }`;

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = `sequenceDiagram
let diagram = `sequenceDiagram
participant Alice
link Alice: Click Me!@javasjavascript:cript:alert("goose")`;

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = `classDiagram
let diagram = `classDiagram
Class "<img/src='x'/onerror=xssAttack(1)>" <--> "<img/src='x'/onerror=xssAttack(2)>" C2: Cool label`;
// // var diagram = "stateDiagram-v2\n";

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = `classDiagram
let diagram = `classDiagram
class Shape{
<<<img/src='1'/`;

View File

@@ -54,9 +54,9 @@
startOnLoad: true,
useMaxWidth: true,
});
var cnt = 0;
var a;
var handler = setInterval(() => {
let cnt = 0;
let a;
let handler = setInterval(() => {
cnt++;
a = {};
if (typeof a.polluted !== 'undefined') {

View File

@@ -96,7 +96,7 @@
// var diagram = ` graph TD
// A --> B["&lt;a href='javasc`;
// diagram += `ript#colon;xssAttack()'&gt;AAA&lt;/a&gt;"]`;
var diagram = ` graph TD
let diagram = ` graph TD
A --> B["<a href='javasc`;
diagram += `ript#colon;xssAttack()'>AAA</a>"]`;
// diagram += '//via.placeholder.com/64\' width=64 />"]';

View File

@@ -96,7 +96,7 @@
// var diagram = ` graph TD
// A --> B["&lt;a href='javasc`;
// diagram += `ript#colon;xssAttack()'&gt;AAA&lt;/a&gt;"]`;
var diagram = ` graph TD
let diagram = ` graph TD
A --> B["<a href='javasc`;
diagram += `ript#9;t#colon;xssAttack()'>AAA</a>"]`;
// diagram += '//via.placeholder.com/64\' width=64 />"]';

View File

@@ -42,9 +42,9 @@
startOnLoad: true,
useMaxWidth: true,
});
var cnt = 0;
var a;
var handler = setInterval(() => {
let cnt = 0;
let a;
let handler = setInterval(() => {
cnt++;
a = {};
if (typeof a.polluted !== 'undefined') {

View File

@@ -85,7 +85,7 @@
alert('It worked');
}
var diagram = '%%{init: {"flowchart": {"htmlLabels": "true"}} }%%\n';
let diagram = '%%{init: {"flowchart": {"htmlLabels": "true"}} }%%\n';
diagram += 'flowchart\n';
diagram += 'A["<ifra';
diagram += "me srcdoc='<scrip";

View File

@@ -92,7 +92,7 @@
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
var diagram = 'graph LR\n';
let diagram = 'graph LR\n';
diagram += 'B-->D("<img onerror=location=`java';
// diagram += "script\u003aalert\u0028document.domain\u0029\` src=x>\"\);\n";
diagram += 'script\x3a;xssAttack\u0028\u0029` src=x>");\n';

View File

@@ -92,7 +92,7 @@
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
var diagram = 'graph LR\n';
let diagram = 'graph LR\n';
diagram += 'A(<img/src/onerror=xssAttack`1`>)';
// diagram += "script\u003aalert\u0028document.domain\u0029\` src=x>\"\);\n";
console.log(diagram);

View File

@@ -92,7 +92,7 @@
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
var diagram = 'graph LR\n';
let diagram = 'graph LR\n';
diagram += " B(<a href='<";
diagram += 'script></';
diagram += "script>Javascript:xssAttack`1`'>Click)";

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'stateDiagram-v2\n';
let diagram = 'stateDiagram-v2\n';
diagram += "<img/src='1'/onerror=xssAttack()> --> B";
// diagram += "script\u003aalert\u0028document.domain\u0029\` src=x>\"\);\n";
console.log(diagram);

View File

@@ -93,7 +93,7 @@
throw new Error('XSS Succeeded');
}
var diagram = 'stateDiagram-v2\n';
let diagram = 'stateDiagram-v2\n';
diagram += "<img/src='1'/onerror=xssAttack()> --> B";
// diagram += "script\u003aalert\u0028document.domain\u0029\` src=x>\"\);\n";
console.log(diagram);

View File

@@ -42,7 +42,7 @@
<script>
function testClick(nodeId) {
console.log('clicked', nodeId);
var originalBgColor = document.querySelector('body').style.backgroundColor;
let originalBgColor = document.querySelector('body').style.backgroundColor;
document.querySelector('body').style.backgroundColor = 'yellow';
setTimeout(function () {
document.querySelector('body').style.backgroundColor = originalBgColor;

View File

@@ -1101,7 +1101,7 @@
<script>
function testClick(nodeId) {
console.log('clicked', nodeId);
var originalBgColor = document.querySelector('body').style.backgroundColor;
let originalBgColor = document.querySelector('body').style.backgroundColor;
document.querySelector('body').style.backgroundColor = 'yellow';
setTimeout(function () {
document.querySelector('body').style.backgroundColor = originalBgColor;

View File

@@ -1073,7 +1073,7 @@ Enterprise_Boundary(b0, "BankBoundary0") {
}
function testClick(nodeId) {
console.log('clicked', nodeId);
var originalBgColor = document.querySelector('body').style.backgroundColor;
let originalBgColor = document.querySelector('body').style.backgroundColor;
document.querySelector('body').style.backgroundColor = 'yellow';
setTimeout(function () {
document.querySelector('body').style.backgroundColor = originalBgColor;

View File

@@ -1407,15 +1407,6 @@ This sets the auto-wrap padding for the diagram (sides only)
**Notes:** Default value: 0.
## parse
### Parameters
- `text` **[string][5]**
- `parseError` **[Function][6]?**
Returns **[boolean][7]**
## setSiteConfig
## setSiteConfig
@@ -1433,7 +1424,7 @@ function _Default value: At default, will mirror Global Config_
- `conf` **MermaidConfig** The base currentConfig to use as siteConfig
Returns **[object][8]** The siteConfig
Returns **[object][5]** The siteConfig
## getSiteConfig
@@ -1445,7 +1436,7 @@ Returns **[object][8]** The siteConfig
**Notes**: Returns **any** values in siteConfig.
Returns **[object][8]** The siteConfig
Returns **[object][5]** The siteConfig
## setConfig
@@ -1484,10 +1475,10 @@ $(function () {
### Parameters
- `id` **[string][5]** The id of the element to be rendered
- `text` **[string][5]** The graph definition
- `cb` **function (svgCode: [string][5], bindFunctions: function (element: [Element][9]): void): void**
- `container` **[Element][9]** Selector to element in which a div with the graph temporarily will be
- `id` **[string][6]** The id of the element to be rendered
- `text` **[string][6]** The graph definition
- `cb` **function (svgCode: [string][6], bindFunctions: function (element: [Element][7]): void): void**
- `container` **[Element][7]** Selector to element in which a div with the graph temporarily will be
inserted. If one is provided a hidden div will be inserted in the body of the page instead. The
element will be removed when rendering is completed.
@@ -1526,7 +1517,7 @@ Pushes in a directive to the configuration
### Parameters
- `directive` **[object][8]** The directive to push in
- `directive` **[object][5]** The directive to push in
## reset
@@ -1562,7 +1553,7 @@ Returns **void**
```html
<script>
var config = {
let config = {
theme: 'default',
logLevel: 'fatal',
securityLevel: 'strict',
@@ -1624,8 +1615,6 @@ Returns **void**
[2]: Setup.md?id=render
[3]: 8.6.0_docs.md
[4]: #mermaidapi-configuration-defaults
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[9]: https://developer.mozilla.org/docs/Web/API/Element
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[7]: https://developer.mozilla.org/docs/Web/API/Element

View File

@@ -19,7 +19,6 @@
- [Requirement Diagram](requirementDiagram.md)
- [Gitgraph (Git) Diagram 🔥](gitgraph.md)
- [C4C Diagram (Context) Diagram 🦺⚠️](c4c.md)
- [Mindmaps 🦺⚠️](mindmap.md)
- [Other Examples](examples.md)
- ⚙️ Deployment and Configuration

View File

@@ -558,7 +558,7 @@ click Shape2 call callbackFunction() "This is a tooltip for a callback"
```html
<script>
var callbackFunction = function () {
let callbackFunction = function () {
alert('A callback was triggered');
};
</script>
@@ -622,10 +622,10 @@ Beginner's tip—a full example using interactive links in an HTML page:
</pre>
<script>
var callback = function () {
let callback = function () {
alert('A callback was triggered');
};
var config = {
let config = {
startOnLoad: true,
securityLevel: 'loose',
};

View File

@@ -8,7 +8,7 @@ So you want to help? That's great!
Here are a few things to get you started on the right path.
**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**
**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**
**Note: Commits and Pull Requests should be directed to the develop branch.**
@@ -46,9 +46,9 @@ Start with the type, such as **feature** or **bug**, followed by the issue numbe
If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature?
The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**.
The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**.
> **All the documents displayed in the GitHub.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**.
> **All the documents displayed in the github.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**.
The contents of <https://mermaid-js.github.io/mermaid/> are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released.
@@ -60,7 +60,7 @@ The documentation is located in the `src/docs` directory and organized according
The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually.
We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s)
We encourage contributions to the documentation at [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s)
### Add Unit Tests for Parsing
@@ -73,7 +73,7 @@ This tests the rendering and visual appearance of the diagrams. This ensures tha
To start working with the e2e tests:
1. Run `yarn dev` to start the dev server
2. Start **Cypress** by running `cypress open` in the **mermaid** folder.
2. Start **Cypress** by running `cypress open` in the **mermaid** folder.\
(Make sure you have path to Cypress in order, the binary is located in `node_modules/.bin`).
The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress.
@@ -114,7 +114,7 @@ Markdown is used to format the text, for more information about Markdown [see th
To edit Docs on your computer:
1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs) directory in the `develop` branch.
1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs) directory in the `develop` branch.
2. Create a fork of the develop branch.
3. Make changes or add new documentation.
4. Commit changes to your fork and push it to GitHub.
@@ -123,7 +123,7 @@ To edit Docs on your computer:
To edit Docs on GitHub:
1. Login to [GitHub.com](https://www.github.com).
2. Navigate to [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs).
2. Navigate to [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs).
3. To edit a file, click the pencil icon at the top-right of the file contents panel.
4. Describe what you changed in the **Propose file change** section, located at the bottom of the page.
5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch).

View File

@@ -641,7 +641,7 @@ A node can have click events bound that lead to either a JavaScript callback or
```html
<script>
var callback = function (nodeId) {
let callback = function (nodeId) {
alert('A callback was triggered on ' + nodeId);
};
</script>
@@ -719,10 +719,10 @@ Beginner's tip—here's a full example of using interactive links in HTML:
</pre>
<script>
var callback = function () {
let callback = function () {
alert('A callback was triggered');
};
var config = {
let config = {
startOnLoad: true,
flowchart: {
useMaxWidth: true,

View File

@@ -144,7 +144,7 @@ A --> C[End]
### Changing fontFamily via directive
The following code snippet changes fontFamily to rebuchet MS, Verdana, Arial, Sans-Serif:
The following code snippet changes theme to forest:
`%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%`
@@ -176,7 +176,7 @@ A --> C[End]
### Changing logLevel via directive
The following code snippet changes logLevel to 2:
The following code snippet changes theme to forest:
`%%{init: { "logLevel": 2 } }%%`

View File

@@ -685,7 +685,7 @@ Examples of tooltip usage below:
```html
<script>
var callback = function () {
let callback = function () {
alert('A callback was triggered');
};
</script>
@@ -761,10 +761,10 @@ Beginner's tip—a full example using interactive links in a html context:
</pre>
<script>
var callback = function () {
let callback = function () {
alert('A callback was triggered');
};
var config = {
let config = {
startOnLoad: true,
flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
securityLevel: 'loose',

View File

@@ -377,13 +377,13 @@ Beginner's tip—a full example using interactive links in an html context:
</pre>
<script>
var printArguments = function (arg1, arg2, arg3) {
let printArguments = function (arg1, arg2, arg3) {
alert('printArguments called with arguments: ' + arg1 + ', ' + arg2 + ', ' + arg3);
};
var printTask = function (taskId) {
let printTask = function (taskId) {
alert('taskId: ' + taskId);
};
var config = {
let config = {
startOnLoad: true,
securityLevel: 'loose',
};

View File

@@ -38,7 +38,7 @@
}
</script>
<script>
var require = {
let require = {
paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.29.1/min/vs' },
};
</script>
@@ -60,16 +60,16 @@
<body>
<div id="app"></div>
<script>
var initEditor = exports.default;
var parser = new DOMParser();
var currentCodeExample = 0;
var colorize = [];
let initEditor = exports.default;
let parser = new DOMParser();
let currentCodeExample = 0;
let colorize = [];
function colorizeEverything(html) {
initEditor(monaco);
return new Promise((resolve, reject) => {
monaco.editor.setTheme('mermaid');
var parsed = parser.parseFromString(html, 'text/html').body;
let parsed = parser.parseFromString(html, 'text/html').body;
Promise.all(
[...parsed.querySelectorAll('pre[id*="code"]')].map((codeBlock) =>
monaco.editor.colorize(codeBlock.innerText, 'mermaid')
@@ -126,7 +126,7 @@
function (hook, vm) {
hook.beforeEach(function (html) {
url = 'https://github.com/mermaid-js/mermaid/blob/develop/src/docs/' + vm.route.file;
var editHtml = '[:memo: Edit this Page](' + url + ')\n';
let editHtml = '[:memo: Edit this Page](' + url + ')\n';
return editHtml + html;
});
@@ -145,7 +145,7 @@
],
};
var num = 0;
let num = 0;
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const conf = {

View File

@@ -1,239 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. Please edit corresponding file in src/docs.
# Mindmap
> Mindmap: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stabel except for the icon integration which is the experimental part.
"A mind map is a diagram used to visually organize information into a hierarchy, showing relationships among pieces of the whole. It is often created around a single concept, drawn as an image in the center of a blank page, to which associated representations of ideas such as images, words and parts of words are added. Major ideas are connected directly to the central concept, and other ideas branch out from those major ideas." Wikipedia
### An example of a mindmap.
```mermaid-example
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectivness<br/>and eatures
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
```
```mermaid
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectivness<br/>and eatures
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
```
## Syntax
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
In the following example you can see how there are 3 dufferent levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the prevoius lines defining the nodes B and C.
mindmap
Root
A
B
C
In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
```mermaid-example
mindmap
Root
A
B
C
```
```mermaid
mindmap
Root
A
B
C
```
In this way we can use a text outline to generate a hierarchical mindmap.
## Different shapes
Mermaids mindmaps can show node using different shapes. When specifying a shape for a node the syntax for the is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts even though they are not all supported from the start.
Mindmap can show the following shapes:
### Square
```mermaid-example
mindmap
id[I am a square]
```
```mermaid
mindmap
id[I am a square]
```
### Rounded square
```mermaid-example
mindmap
id(I am a rounded square)
```
```mermaid
mindmap
id(I am a rounded square)
```
### Circle
```mermaid-example
mindmap
id((I am a circle))
```
```mermaid
mindmap
id((I am a circle))
```
### Bang
```mermaid-example
mindmap
id))I am a bang((
```
```mermaid
mindmap
id))I am a bang((
```
### Cloud
```mermaid-example
mindmap
id)I am a cloud(
```
```mermaid
mindmap
id)I am a cloud(
```
### Default
```mermaid-example
mindmap
I am the default shape
```
```mermaid
mindmap
I am the default shape
```
More shapes will be added, beginning with the shapes available in flowcharts.
# Icons and classes
## icons
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
```mermaid-example
mindmap
Root
A
::icon(fa fa-book)
B(B)
::icon(mdi mdi-skull-outline)
```
```mermaid
mindmap
Root
A
::icon(fa fa-book)
B(B)
::icon(mdi mdi-skull-outline)
```
## Classes
Again the syntax for adding classes is similar to flowcharts. You can add classes using a triple colon following a number of css classes separated by space. In the following example one of the nodes has two custom classes attached urgent turning the background red and the text white and large increasing the font size:
```mermaid-example
mindmap
Root
A[A]
:::urgent large
B(B)
C
```
```mermaid
mindmap
Root
A[A]
:::urgent large
B(B)
C
```
_These classes needs top be supplied by the site administrator._
## Unclear indentation
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can se how the calculations are performed. Let us start with placing C with a smaller indentation than `B`but larger then `A`.
mindmap
Root
A
B
C
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
```mermaid-example
mindmap
Root
A
B
C
```
```mermaid
mindmap
Root
A
B
C
```

View File

@@ -211,11 +211,11 @@ The example below show an outline of how this could be used. The example just lo
$(function () {
// Example of using the API var
element = document.querySelector('#graphDiv');
var insertSvg = function (svgCode, bindFunctions) {
let insertSvg = function (svgCode, bindFunctions) {
element.innerHTML = svgCode;
};
var graphDefinition = 'graph TB\na-->b';
var graph = mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
let graphDefinition = 'graph TB\na-->b';
let graph = mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
});
</script>
```
@@ -339,7 +339,7 @@ on what kind of integration you use.
```html
<script src="../dist/mermaid.js"></script>
<script>
var config = { startOnLoad: true, flowchart: { useMaxWidth: false, htmlLabels: true } };
let config = { startOnLoad: true, flowchart: { useMaxWidth: false, htmlLabels: true } };
mermaid.initialize(config);
</script>
```

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "9.2.0-rc1",
"version": "9.1.6",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.min.js",
"module": "dist/mermaid.esm.min.mjs",
@@ -28,11 +28,11 @@
"build": "concurrently \"yarn build:dev\" \"yarn build:prod\"",
"docs:build": "ts-node-esm src/docs.mts",
"docs:verify": "ts-node-esm src/docs.mts --verify",
"postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md && prettier --write src/docs/Setup.md && yarn docs:build",
"postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md; yarn docs:build",
"build:watch": "yarn build:dev --watch",
"release": "yarn build",
"lint": "eslint --cache --ignore-path .gitignore . && prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"lint": "eslint --cache --ignore-path .gitignore .; prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore .; prettier --write .",
"e2e:depr": "yarn lint && jest e2e --config e2e/jest.config.js",
"cypress": "cypress run",
"cypress:open": "cypress open",
@@ -43,7 +43,7 @@
"test": "yarn lint && jest src/.*",
"test:watch": "jest --watch src",
"prepublishOnly": "yarn build && yarn test",
"prepare": "concurrently \"husky install\" \"yarn build:prod\" \"yarn postbuild\"",
"prepare": "concurrently \"husky install\" \"yarn build:prod\"",
"pre-commit": "lint-staged"
},
"repository": {
@@ -67,12 +67,10 @@
"d3": "^7.0.0",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
"dompurify": "2.3.10",
"fast-clone": "^1.5.13",
"dompurify": "2.4.0",
"graphlib": "^2.1.8",
"khroma": "^2.0.0",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"moment-mini": "2.24.0",
"stylis": "^4.0.10"
},
"devDependencies": {
@@ -84,12 +82,12 @@
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.0.0",
"@types/d3": "^7.4.0",
"@types/dompurify": "^2.3.4",
"@types/dompurify": "^2.3.3",
"@types/jest": "^28.1.7",
"@types/stylis": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"babel-jest": "^29.0.2",
"babel-jest": "^29.0.1",
"babel-loader": "^8.2.2",
"concurrently": "^7.0.0",
"coveralls": "^3.0.2",
@@ -109,21 +107,21 @@
"husky": "^8.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.0.3",
"jest-environment-jsdom": "^29.0.2",
"jest-environment-jsdom": "^29.0.1",
"jison": "^0.4.18",
"js-base64": "3.7.2",
"lint-staged": "^13.0.0",
"moment": "^2.23.0",
"path-browserify": "^1.0.1",
"prettier": "^2.7.1",
"prettier-plugin-jsdoc": "^0.4.2",
"prettier": "^2.3.2",
"prettier-plugin-jsdoc": "^0.3.30",
"remark": "^14.0.2",
"start-server-and-test": "^1.12.6",
"terser-webpack-plugin": "^5.3.6",
"ts-jest": "^28.0.8",
"ts-loader": "^9.3.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.2",
"typescript": "^4.7.4",
"unist-util-flatmap": "^1.0.0",
"webpack": "^5.53.0",
"webpack-cli": "^4.7.2",

View File

@@ -17,6 +17,15 @@
* Traditional Object.assign would have clobbered foo in config_0 with foo in config_1. If src is a
* destructured array of objects and dst is not an array, assignWithDepth will apply each element
* of src to dst in order.
* @param dst
* @param src
* @param config
* @param dst
* @param src
* @param config
* @param dst
* @param src
* @param config
* @param {any} dst - The destination of the merge
* @param {any} src - The source object(s) to merge into destination
* @param {{ depth: number; clobber: boolean }} [config={ depth: 2, clobber: false }] - Depth: depth

View File

@@ -27,12 +27,10 @@ export interface MermaidConfig {
er?: ErDiagramConfig;
pie?: PieDiagramConfig;
requirement?: RequirementDiagramConfig;
mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
c4?: C4DiagramConfig;
dompurifyConfig?: DOMPurify.Config;
wrap?: boolean;
fontSize?: number;
}
// TODO: More configs needs to be moved in here
@@ -214,12 +212,6 @@ export interface RequirementDiagramConfig extends BaseDiagramConfig {
line_height?: number;
}
export interface MindmapDiagramConfig extends BaseDiagramConfig {
useMaxWidth: boolean;
padding: number;
maxNodeWidth: number;
}
export type PieDiagramConfig = BaseDiagramConfig;
export interface ErDiagramConfig extends BaseDiagramConfig {

View File

@@ -348,7 +348,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
let points = edge.points;
let pointsHasChanged = false;
const tail = graph.node(e.v);
var head = graph.node(e.w);
let head = graph.node(e.w);
log.info('abc88 InsertEdge: ', edge);
if (head.intersect && tail.intersect) {

View File

@@ -7,19 +7,19 @@
function intersectEllipse(node, rx, ry, point) {
// Formulae from: https://mathworld.wolfram.com/Ellipse-LineIntersection.html
var cx = node.x;
var cy = node.y;
let cx = node.x;
let cy = node.y;
var px = cx - point.x;
var py = cy - point.y;
let px = cx - point.x;
let py = cy - point.y;
var det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
let det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
var dx = Math.abs((rx * ry * px) / det);
let dx = Math.abs((rx * ry * px) / det);
if (point.x < cx) {
dx = -dx;
}
var dy = Math.abs((rx * ry * py) / det);
let dy = Math.abs((rx * ry * py) / det);
if (point.y < cy) {
dy = -dy;
}

View File

@@ -10,10 +10,10 @@ function intersectLine(p1, p2, q1, q2) {
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
// p7 and p473.
var a1, a2, b1, b2, c1, c2;
var r1, r2, r3, r4;
var denom, offset, num;
var x, y;
let a1, a2, b1, b2, c1, c2;
let r1, r2, r3, r4;
let denom, offset, num;
let x, y;
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
// b1 y + c1 = 0.

View File

@@ -13,13 +13,13 @@ export default intersectPolygon;
* @param point
*/
function intersectPolygon(node, polyPoints, point) {
var x1 = node.x;
var y1 = node.y;
let x1 = node.x;
let y1 = node.y;
var intersections = [];
let intersections = [];
var minX = Number.POSITIVE_INFINITY;
var minY = Number.POSITIVE_INFINITY;
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
if (typeof polyPoints.forEach === 'function') {
polyPoints.forEach(function (entry) {
minX = Math.min(minX, entry.x);
@@ -30,13 +30,13 @@ function intersectPolygon(node, polyPoints, point) {
minY = Math.min(minY, polyPoints.y);
}
var left = x1 - node.width / 2 - minX;
var top = y1 - node.height / 2 - minY;
let left = x1 - node.width / 2 - minX;
let top = y1 - node.height / 2 - minY;
for (var i = 0; i < polyPoints.length; i++) {
var p1 = polyPoints[i];
var p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
var intersect = intersectLine(
for (let i = 0; i < polyPoints.length; i++) {
let p1 = polyPoints[i];
let p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
let intersect = intersectLine(
node,
point,
{ x: left + p1.x, y: top + p1.y },
@@ -55,13 +55,13 @@ function intersectPolygon(node, polyPoints, point) {
if (intersections.length > 1) {
// More intersections, find the one nearest to edge end point
intersections.sort(function (p, q) {
var pdx = p.x - point.x;
var pdy = p.y - point.y;
var distp = Math.sqrt(pdx * pdx + pdy * pdy);
let pdx = p.x - point.x;
let pdy = p.y - point.y;
let distp = Math.sqrt(pdx * pdx + pdy * pdy);
var qdx = q.x - point.x;
var qdy = q.y - point.y;
var distq = Math.sqrt(qdx * qdx + qdy * qdy);
let qdx = q.x - point.x;
let qdy = q.y - point.y;
let distq = Math.sqrt(qdx * qdx + qdy * qdy);
return distp < distq ? -1 : distp === distq ? 0 : 1;
});

View File

@@ -1,15 +1,15 @@
const intersectRect = (node, point) => {
var x = node.x;
var y = node.y;
let x = node.x;
let y = node.y;
// Rectangle intersection algorithm from:
// https://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
var dx = point.x - x;
var dy = point.y - y;
var w = node.width / 2;
var h = node.height / 2;
let dx = point.x - x;
let dy = point.y - y;
let w = node.width / 2;
let h = node.height / 2;
var sx, sy;
let sx, sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {

View File

@@ -163,9 +163,9 @@ export const validate = (graph) => {
};
/**
* Finds a child that is not a cluster. When faking an edge between a node and a cluster.
* Finds a child that is not a cluster. When faking a edge between a node and a cluster.
*
* @param id
* @param {Finds a} id
* @param {any} graph
*/
export const findNonClusterChild = (id, graph) => {
@@ -235,7 +235,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
edges.forEach((edge) => {
// log.debug('Edge, decendants: ', edge, decendants[id]);
// Check if any edge leaves the cluster (not the actual cluster, that's a link from the box)
// Check if any edge leaves the cluster (not the actual cluster, thats a link from the box)
if (edge.v !== id && edge.w !== id) {
// Any edge where either the one of the nodes is decending to the cluster but not the other
// if (decendants[id].indexOf(edge.v) < 0 && decendants[id].indexOf(edge.w) < 0) {

View File

@@ -1823,12 +1823,6 @@ const config: Partial<MermaidConfig> = {
external_component_queue_bg_color: '#CCCCCC',
external_component_queue_border_color: '#BFBFBF',
},
mindmap: {
useMaxWidth: true,
padding: 10,
maxNodeWidth: 200,
},
fontSize: 16,
};
if (config.class) config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;

View File

@@ -1,12 +1,26 @@
import { MermaidConfig } from '../config.type';
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
export type DiagramDetector = (text: string) => boolean;
const directive =
/[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
const anyComment = /\s*%%.*\n/gm;
const detectors: Record<string, DiagramDetector> = {};
const diagramMatchers: Record<string, RegExp> = {
c4: /^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/,
sequence: /^\s*sequenceDiagram/,
gantt: /^\s*gantt/,
classDiagram: /^\s*classDiagram-v2/,
stateDiagram: /^\s*stateDiagram-v2/,
'flowchart-v2': /^\s*flowchart/, // Might need to add |graph to fix #3391
info: /^\s*info/,
pie: /^\s*pie/,
er: /^\s*erDiagram/,
journey: /^\s*journey/,
// gitGraph: /^\s*gitGraph/,
requirement: /^\s*requirement(Diagram)?/,
};
/**
* @function detectType Detects the type of the graph text. Takes into consideration the possible
@@ -33,9 +47,28 @@ const detectors: Record<string, DiagramDetector> = {};
*/
export const detectType = function (text: string, config?: MermaidConfig): string {
text = text.replace(directive, '').replace(anyComment, '\n');
for (const [diagram, matcher] of Object.entries(diagramMatchers)) {
if (text.match(matcher)) {
return diagram;
}
}
if (text.match(/^\s*classDiagram/)) {
if (config?.class?.defaultRenderer === 'dagre-wrapper') return 'classDiagram';
return 'class';
}
if (text.match(/^\s*stateDiagram/)) {
if (config?.state?.defaultRenderer === 'dagre-wrapper') return 'stateDiagram';
return 'state';
}
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
return 'flowchart-v2';
}
for (const [key, detector] of Object.entries(detectors)) {
if (detector(text, config)) {
if (detector(text)) {
return key;
}
}

View File

@@ -1,318 +1,29 @@
import { registerDiagram } from './diagramAPI';
// import mindmapDb from '../diagrams/mindmap/mindmapDb';
// import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
// import mindmapParser from '../diagrams/mindmap/parser/mindmapDiagram';
// import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
// @ts-ignore: TODO Fix ts errors
import mindmapParser from '../diagrams/mindmap/parser/mindmap';
import * as mindmapDb from '../diagrams/mindmap/mindmapDb';
import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
import mindmapStyles from '../diagrams/mindmap/styles';
import gitGraphDb from '../diagrams/git/gitGraphAst';
import gitGraphRenderer from '../diagrams/git/gitGraphRenderer';
// @ts-ignore: TODO Fix ts errors
import gitGraphParser from '../diagrams/git/parser/gitGraph';
import { gitGraphDetector } from '../diagrams/git/gitGraphDetector';
import gitGraphDb from '../diagrams/git/gitGraphAst';
import gitGraphRenderer from '../diagrams/git/gitGraphRenderer';
import gitGraphStyles from '../diagrams/git/styles';
// @ts-ignore: TODO Fix ts errors
import c4Parser from '../diagrams/c4/parser/c4Diagram';
import { c4Detector } from '../diagrams/c4/c4Detector';
import c4Db from '../diagrams/c4/c4Db';
import c4Renderer from '../diagrams/c4/c4Renderer';
import c4Styles from '../diagrams/c4/styles';
// @ts-ignore: TODO Fix ts errors
import classParser from '../diagrams/class/parser/classDiagram';
import { classDetector } from '../diagrams/class/classDetector';
import { classDetectorV2 } from '../diagrams/class/classDetector-V2';
import classDb from '../diagrams/class/classDb';
import classRenderer from '../diagrams/class/classRenderer';
import classRendererV2 from '../diagrams/class/classRenderer-v2';
import classStyles from '../diagrams/class/styles';
// @ts-ignore: TODO Fix ts errors
import erParser from '../diagrams/er/parser/erDiagram';
import { erDetector } from '../diagrams/er/erDetector';
import erDb from '../diagrams/er/erDb';
import erRenderer from '../diagrams/er/erRenderer';
import erStyles from '../diagrams/er/styles';
// @ts-ignore: TODO Fix ts errors
import flowParser from '../diagrams/flowchart/parser/flow';
import { flowDetector } from '../diagrams/flowchart/flowDetector';
import { flowDetectorV2 } from '../diagrams/flowchart/flowDetector-v2';
import flowDb from '../diagrams/flowchart/flowDb';
import flowRenderer from '../diagrams/flowchart/flowRenderer';
import flowRendererV2 from '../diagrams/flowchart/flowRenderer-v2';
import flowStyles from '../diagrams/flowchart/styles';
// @ts-ignore: TODO Fix ts errors
import ganttParser from '../diagrams/gantt/parser/gantt';
import { ganttDetector } from '../diagrams/gantt/ganttDetector';
import ganttDb from '../diagrams/gantt/ganttDb';
import ganttRenderer from '../diagrams/gantt/ganttRenderer';
import ganttStyles from '../diagrams/gantt/styles';
// @ts-ignore: TODO Fix ts errors
import infoParser from '../diagrams/info/parser/info';
import infoDb from '../diagrams/info/infoDb';
import infoRenderer from '../diagrams/info/infoRenderer';
import { infoDetector } from '../diagrams/info/infoDetector';
import infoStyles from '../diagrams/info/styles';
// @ts-ignore: TODO Fix ts errors
import pieParser from '../diagrams/pie/parser/pie';
import { pieDetector } from '../diagrams/pie/pieDetector';
import pieDb from '../diagrams/pie/pieDb';
import pieRenderer from '../diagrams/pie/pieRenderer';
import pieStyles from '../diagrams/pie/styles';
// @ts-ignore: TODO Fix ts errors
import requirementParser from '../diagrams/requirement/parser/requirementDiagram';
import { requirementDetector } from '../diagrams/requirement/requirementDetector';
import requirementDb from '../diagrams/requirement/requirementDb';
import requirementRenderer from '../diagrams/requirement/requirementRenderer';
import requirementStyles from '../diagrams/requirement/styles';
// @ts-ignore: TODO Fix ts errors
import sequenceParser from '../diagrams/sequence/parser/sequenceDiagram';
import { sequenceDetector } from '../diagrams/sequence/sequenceDetector';
import sequenceDb from '../diagrams/sequence/sequenceDb';
import sequenceRenderer from '../diagrams/sequence/sequenceRenderer';
import sequenceStyles from '../diagrams/sequence/styles';
// @ts-ignore: TODO Fix ts errors
import stateParser from '../diagrams/state/parser/stateDiagram';
import { stateDetector } from '../diagrams/state/stateDetector';
import { stateDetectorV2 } from '../diagrams/state/stateDetector-V2';
import stateDb from '../diagrams/state/stateDb';
import stateRenderer from '../diagrams/state/stateRenderer';
import stateRendererV2 from '../diagrams/state/stateRenderer-v2';
import stateStyles from '../diagrams/state/styles';
// @ts-ignore: TODO Fix ts errors
import journeyParser from '../diagrams/user-journey/parser/journey';
import { journeyDetector } from '../diagrams/user-journey/journeyDetector';
import journeyDb from '../diagrams/user-journey/journeyDb';
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
import journeyStyles from '../diagrams/user-journey/styles';
// Register mindmap and other built-in diagrams
// registerDiagram(
// 'mindmap',
// mindmapParser,
// mindmapDb,
// mindmapRenderer,
// undefined,
// mindmapRenderer,
// mindmapDetector
// );
export const addDiagrams = () => {
registerDiagram(
'c4',
{
parser: c4Parser,
db: c4Db,
renderer: c4Renderer,
styles: c4Styles,
init: (cnf) => {
c4Renderer.setConf(cnf.c4);
},
},
c4Detector
);
registerDiagram(
'class',
{
parser: classParser,
db: classDb,
renderer: classRenderer,
styles: classStyles,
init: (cnf) => {
if (!cnf.class) {
cnf.class = {};
}
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classDb.clear();
},
},
classDetector
);
registerDiagram(
'classDiagram',
{
parser: classParser,
db: classDb,
renderer: classRendererV2,
styles: classStyles,
init: (cnf) => {
if (!cnf.class) {
cnf.class = {};
}
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classDb.clear();
},
},
classDetectorV2
);
registerDiagram(
'er',
{
parser: erParser,
db: erDb,
renderer: erRenderer,
styles: erStyles,
},
erDetector
);
registerDiagram(
'gantt',
{
parser: ganttParser,
db: ganttDb,
renderer: ganttRenderer,
styles: ganttStyles,
},
ganttDetector
);
registerDiagram(
'info',
{
parser: infoParser,
db: infoDb,
renderer: infoRenderer,
styles: infoStyles,
},
infoDetector
);
registerDiagram(
'pie',
{
parser: pieParser,
db: pieDb,
renderer: pieRenderer,
styles: pieStyles,
},
pieDetector
);
registerDiagram(
'requirement',
{
parser: requirementParser,
db: requirementDb,
renderer: requirementRenderer,
styles: requirementStyles,
},
requirementDetector
);
registerDiagram(
'sequence',
{
parser: sequenceParser,
db: sequenceDb,
renderer: sequenceRenderer,
styles: sequenceStyles,
init: (cnf) => {
if (!cnf.sequence) {
cnf.sequence = {};
}
cnf.sequence.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
if ('sequenceDiagram' in cnf) {
throw new Error(
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
);
}
sequenceDb.setWrap(cnf.wrap);
sequenceRenderer.setConf(cnf.sequence);
},
},
sequenceDetector
);
registerDiagram(
'state',
{
parser: stateParser,
db: stateDb,
renderer: stateRenderer,
styles: stateStyles,
init: (cnf) => {
if (!cnf.state) {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateDb.clear();
},
},
stateDetector
);
registerDiagram(
'stateDiagram',
{
parser: stateParser,
db: stateDb,
renderer: stateRendererV2,
styles: stateStyles,
init: (cnf) => {
if (!cnf.state) {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateDb.clear();
},
},
stateDetectorV2
);
registerDiagram(
'journey',
{
parser: journeyParser,
db: journeyDb,
renderer: journeyRenderer,
styles: journeyStyles,
init: (cnf) => {
journeyRenderer.setConf(cnf.journey);
journeyDb.clear();
},
},
journeyDetector
);
registerDiagram(
'flowchart',
{
parser: flowParser,
db: flowDb,
renderer: flowRendererV2,
styles: flowStyles,
init: (cnf) => {
flowRenderer.setConf(cnf.flowchart);
if (!cnf.flowchart) {
cnf.flowchart = {};
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowDb.clear();
flowDb.setGen('gen-1');
},
},
flowDetector
);
registerDiagram(
'flowchart-v2',
{
parser: flowParser,
db: flowDb,
renderer: flowRendererV2,
styles: flowStyles,
init: (cnf) => {
flowRendererV2.setConf(cnf.flowchart);
if (!cnf.flowchart) {
cnf.flowchart = {};
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowDb.clear();
flowDb.setGen('gen-2');
},
},
flowDetectorV2
);
registerDiagram(
'gitGraph',
{ parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer, styles: gitGraphStyles },
{ parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer },
gitGraphDetector
);
registerDiagram(
'mindmap',
{ parser: mindmapParser, db: mindmapDb, renderer: mindmapRenderer, styles: mindmapStyles },
mindmapDetector
);
};

View File

@@ -1,8 +1,5 @@
import { detectType } from './detectType';
import { getDiagram, registerDiagram } from './diagramAPI';
import { addDiagrams } from './diagram-orchestration';
addDiagrams();
describe('DiagramAPI', () => {
it('should return default diagrams', () => {
@@ -22,7 +19,6 @@ describe('DiagramAPI', () => {
db: {},
parser: {},
renderer: {},
styles: {},
},
(text: string) => text.includes('loki')
);

View File

@@ -1,32 +1,199 @@
import { addDetector, DiagramDetector as _DiagramDetector } from './detectType';
import { log as _log, setLogLevel as _setLogLevel } from '../logger';
import { getConfig as _getConfig } from '../config';
import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
import c4Db from '../diagrams/c4/c4Db';
import c4Renderer from '../diagrams/c4/c4Renderer';
// @ts-ignore: TODO Fix ts errors
import c4Parser from '../diagrams/c4/parser/c4Diagram';
import classDb from '../diagrams/class/classDb';
import classRenderer from '../diagrams/class/classRenderer';
import classRendererV2 from '../diagrams/class/classRenderer-v2';
// @ts-ignore: TODO Fix ts errors
import classParser from '../diagrams/class/parser/classDiagram';
import erDb from '../diagrams/er/erDb';
import erRenderer from '../diagrams/er/erRenderer';
// @ts-ignore: TODO Fix ts errors
import erParser from '../diagrams/er/parser/erDiagram';
import flowDb from '../diagrams/flowchart/flowDb';
import flowRenderer from '../diagrams/flowchart/flowRenderer';
import flowRendererV2 from '../diagrams/flowchart/flowRenderer-v2';
// @ts-ignore: TODO Fix ts errors
import flowParser from '../diagrams/flowchart/parser/flow';
import ganttDb from '../diagrams/gantt/ganttDb';
import ganttRenderer from '../diagrams/gantt/ganttRenderer';
// @ts-ignore: TODO Fix ts errors
import ganttParser from '../diagrams/gantt/parser/gantt';
import infoDb from '../diagrams/info/infoDb';
import infoRenderer from '../diagrams/info/infoRenderer';
// @ts-ignore: TODO Fix ts errors
import infoParser from '../diagrams/info/parser/info';
// @ts-ignore: TODO Fix ts errors
import pieParser from '../diagrams/pie/parser/pie';
import pieDb from '../diagrams/pie/pieDb';
import pieRenderer from '../diagrams/pie/pieRenderer';
// @ts-ignore: TODO Fix ts errors
import requirementParser from '../diagrams/requirement/parser/requirementDiagram';
import requirementDb from '../diagrams/requirement/requirementDb';
import requirementRenderer from '../diagrams/requirement/requirementRenderer';
// @ts-ignore: TODO Fix ts errors
import sequenceParser from '../diagrams/sequence/parser/sequenceDiagram';
import sequenceDb from '../diagrams/sequence/sequenceDb';
import sequenceRenderer from '../diagrams/sequence/sequenceRenderer';
// @ts-ignore: TODO Fix ts errors
import stateParser from '../diagrams/state/parser/stateDiagram';
import stateDb from '../diagrams/state/stateDb';
import stateRenderer from '../diagrams/state/stateRenderer';
import stateRendererV2 from '../diagrams/state/stateRenderer-v2';
import journeyDb from '../diagrams/user-journey/journeyDb';
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
// @ts-ignore: TODO Fix ts errors
import journeyParser from '../diagrams/user-journey/parser/journey';
import { addDetector, DiagramDetector } from './detectType';
import { log } from '../logger';
import { MermaidConfig } from '../config.type';
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
import { addStylesForDiagram } from '../styles';
/*
Packaging and exposing resources for externa diagrams so that they can import
diagramAPI and have access to selct parts of mermaid common code reqiored to
create diagrams worling like the internal diagrams.
*/
export const log = _log;
export const setLogLevel = _setLogLevel;
export type DiagramDetector = _DiagramDetector;
export const getConfig = _getConfig;
export const sanitizeText = (text: string) => _sanitizeText(text, getConfig());
export const setupGraphViewbox = _setupGraphViewbox;
export interface DiagramDefinition {
db: any;
renderer: any;
parser: any;
styles: any;
init?: (config: MermaidConfig) => void;
}
const diagrams: Record<string, DiagramDefinition> = {};
const diagrams: Record<string, DiagramDefinition> = {
c4: {
db: c4Db,
renderer: c4Renderer,
parser: c4Parser,
init: (cnf) => {
c4Renderer.setConf(cnf.c4);
},
},
class: {
db: classDb,
renderer: classRenderer,
parser: classParser,
init: (cnf) => {
if (!cnf.class) {
cnf.class = {};
}
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classDb.clear();
},
},
classDiagram: {
db: classDb,
renderer: classRendererV2,
parser: classParser,
init: (cnf) => {
if (!cnf.class) {
cnf.class = {};
}
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classDb.clear();
},
},
er: {
db: erDb,
renderer: erRenderer,
parser: erParser,
},
flowchart: {
db: flowDb,
renderer: flowRenderer,
parser: flowParser,
init: (cnf) => {
flowRenderer.setConf(cnf.flowchart);
if (!cnf.flowchart) {
cnf.flowchart = {};
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowDb.clear();
flowDb.setGen('gen-1');
},
},
'flowchart-v2': {
db: flowDb,
renderer: flowRendererV2,
parser: flowParser,
init: (cnf) => {
flowRendererV2.setConf(cnf.flowchart);
if (!cnf.flowchart) {
cnf.flowchart = {};
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowDb.clear();
flowDb.setGen('gen-2');
},
},
gantt: {
db: ganttDb,
renderer: ganttRenderer,
parser: ganttParser,
},
info: {
db: infoDb,
renderer: infoRenderer,
parser: infoParser,
},
pie: {
db: pieDb,
renderer: pieRenderer,
parser: pieParser,
},
requirement: {
db: requirementDb,
renderer: requirementRenderer,
parser: requirementParser,
},
sequence: {
db: sequenceDb,
renderer: sequenceRenderer,
parser: sequenceParser,
init: (cnf) => {
if (!cnf.sequence) {
cnf.sequence = {};
}
cnf.sequence.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
if ('sequenceDiagram' in cnf) {
throw new Error(
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
);
}
sequenceDb.setWrap(cnf.wrap);
sequenceRenderer.setConf(cnf.sequence);
},
},
state: {
db: stateDb,
renderer: stateRenderer,
parser: stateParser,
init: (cnf) => {
if (!cnf.state) {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateDb.clear();
},
},
stateDiagram: {
db: stateDb,
renderer: stateRendererV2,
parser: stateParser,
init: (cnf) => {
if (!cnf.state) {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateDb.clear();
},
},
journey: {
db: journeyDb,
renderer: journeyRenderer,
parser: journeyParser,
init: (cnf) => {
journeyRenderer.setConf(cnf.journey);
journeyDb.clear();
},
},
};
export const registerDiagram = (
id: string,
@@ -38,7 +205,6 @@ export const registerDiagram = (
}
diagrams[id] = diagram;
addDetector(id, detector);
addStylesForDiagram(id, diagram.styles);
};
export const getDiagram = (name: string): DiagramDefinition => {

View File

@@ -1,227 +0,0 @@
export const lineBreakRegex = /<br\s*\/?>/gi;
/**
* Caches results of functions based on input
*
* @param {Function} fn Function to run
* @param {Function} resolver Function that resolves to an ID given arguments the `fn` takes
* @returns {Function} An optimized caching function
*/
const memoize = (fn, resolver) => {
let cache = {};
return (...args) => {
let n = resolver ? resolver.apply(this, args) : args[0];
if (n in cache) {
return cache[n];
} else {
let result = fn(...args);
cache[n] = result;
return result;
}
};
};
/**
* This calculates the width of the given text, font size and family.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the resulting size
* @returns {any} - The width for the given text
*/
export const calculateTextWidth = function (text, config) {
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
return calculateTextDimensions(text, config).width;
};
export const getTextObj = function () {
return {
x: 0,
y: 0,
fill: undefined,
anchor: 'start',
style: '#666',
width: 100,
height: 100,
textMargin: 0,
rx: 0,
ry: 0,
valign: undefined,
};
};
/**
* Adds text to an element
*
* @param {SVGElement} elem Element to add text to
* @param {{
* text: string;
* x: number;
* y: number;
* anchor: 'start' | 'middle' | 'end';
* fontFamily: string;
* fontSize: string | number;
* fontWeight: string | number;
* fill: string;
* class: string | undefined;
* textMargin: number;
* }} textData
* @returns {SVGTextElement} Text element with given styling and content
*/
export const drawSimpleText = function (elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(lineBreakRegex, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.style('font-family', textData.fontFamily);
textElem.style('font-size', textData.fontSize);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
}
const span = textElem.append('tspan');
span.attr('x', textData.x + textData.textMargin * 2);
span.attr('fill', textData.fill);
span.text(nText);
return textElem;
};
/**
* This calculates the dimensions of the given text, font size, font family, font weight, and margins.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
* the resulting size
* @returns - The width for the given text
*/
export const calculateTextDimensions = memoize(
function (text, config) {
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
const { fontSize, fontFamily, fontWeight } = config;
if (!text) {
return { width: 0, height: 0 };
}
// We can't really know if the user supplied font family will render on the user agent;
// thus, we'll take the max width between the user supplied font family, and a default
// of sans-serif.
const fontFamilies = ['sans-serif', fontFamily];
const lines = text.split(common.lineBreakRegex);
let dims = [];
const body = select('body');
// We don't want to leak DOM elements - if a removal operation isn't available
// for any reason, do not continue.
if (!body.remove) {
return { width: 0, height: 0, lineHeight: 0 };
}
const g = body.append('svg');
for (let fontFamily of fontFamilies) {
let cheight = 0;
let dim = { width: 0, height: 0, lineHeight: 0 };
for (let line of lines) {
const textObj = getTextObj();
textObj.text = line;
const textElem = drawSimpleText(g, textObj)
.style('font-size', fontSize)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
let bBox = (textElem._groups || textElem)[0][0].getBBox();
dim.width = Math.round(Math.max(dim.width, bBox.width));
cheight = Math.round(bBox.height);
dim.height += cheight;
dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
}
dims.push(dim);
}
g.remove();
let index =
isNaN(dims[1].height) ||
isNaN(dims[1].width) ||
isNaN(dims[1].lineHeight) ||
(dims[0].height > dims[1].height &&
dims[0].width > dims[1].width &&
dims[0].lineHeight > dims[1].lineHeight)
? 0
: 1;
return dims[index];
},
(text, config) => `${text}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
);
const breakString = memoize(
(word, maxWidth, hyphenCharacter = '-', config) => {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 },
config
);
const characters = word.split('');
const lines = [];
let currentLine = '';
characters.forEach((character, index) => {
const nextLine = `${currentLine}${character}`;
const lineWidth = calculateTextWidth(nextLine, config);
if (lineWidth >= maxWidth) {
const currentCharacter = index + 1;
const isLastLine = characters.length === currentCharacter;
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
currentLine = '';
} else {
currentLine = nextLine;
}
});
return { hyphenatedStrings: lines, remainingWord: currentLine };
},
(word, maxWidth, hyphenCharacter = '-', config) =>
`${word}-${maxWidth}-${hyphenCharacter}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
);
export const wrapLabel = memoize(
(label, maxWidth, config) => {
if (!label) {
return label;
}
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '<br/>' },
config
);
if (lineBreakRegex.test(label)) {
return label;
}
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `, config);
const nextLineLength = calculateTextWidth(nextLine, config);
if (wordLength > maxWidth) {
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
completedLines.push(nextLine, ...hyphenatedStrings);
nextLine = remainingWord;
} else if (nextLineLength + wordLength >= maxWidth) {
completedLines.push(nextLine);
nextLine = word;
} else {
nextLine = [nextLine, word].filter(Boolean).join(' ');
}
const currentWord = index + 1;
const isLastWord = currentWord === words.length;
if (isLastWord) {
completedLines.push(nextLine);
}
});
return completedLines.filter((line) => line !== '').join(config.joinWith);
},
(label, maxWidth, config) =>
`${label}-${maxWidth}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}-${config.joinWith}`
);

View File

@@ -24,7 +24,7 @@ let wrapEnabled = false;
let description = '';
let c4ShapeInRow = 4;
let c4BoundaryInRow = 2;
var c4Type;
let c4Type;
export const getC4Type = function () {
return c4Type;

View File

@@ -1,5 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const c4Detector: DiagramDetector = (txt) => {
return txt.match(/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/) !== null;
};

View File

@@ -6,8 +6,7 @@ import common from '../common/common';
import c4Db from './c4Db';
import * as configApi from '../../config';
import assignWithDepth from '../../assignWithDepth';
import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils';
import { configureSvgSize } from '../../setupGraphViewbox';
import { wrapLabel, calculateTextWidth, calculateTextHeight, configureSvgSize } from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
let globalBoundaryMaxX = 0,
@@ -568,9 +567,10 @@ function drawInsideBoundary(diagram, parentBoundaryAlias, parentBounds, currentB
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
*
* @param {any} _text
* @param {any} text
* @param _text
* @param {any} id
* @param {any} _version
* @param _version
* @param diagObj
*/
export const draw = function (_text, id, _version, diagObj) {

View File

@@ -1,110 +0,0 @@
import c4Db from '../c4Db';
import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe.each(['Boundary'])('parsing a C4 %s', function (macroName) {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
});
it('should parse a C4 diagram with one Boundary correctly', function () {
c4.parser.parse(`C4Context
title System Context diagram for Internet Banking System
${macroName}(b1, "BankBoundary") {
System(SystemAA, "Internet Banking System")
}`);
const yy = c4.parser.yy;
const boundaries = yy.getBoundarys();
expect(boundaries.length).toBe(2);
const boundary = boundaries[1];
expect(boundary).toEqual({
alias: 'b1',
label: {
text: 'BankBoundary',
},
// TODO: Why are link, and tags undefined instead of not appearing at all?
// Compare to Person where they don't show up.
link: undefined,
tags: undefined,
parentBoundary: 'global',
type: {
// TODO: Why is this `system` instead of `boundary`?
text: 'system',
},
wrap: false,
});
});
it('should parse the alias', function () {
c4.parser.parse(`C4Context
${macroName}(b1, "BankBoundary") {
System(SystemAA, "Internet Banking System")
}`);
expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
alias: 'b1',
});
});
it('should parse the label', function () {
c4.parser.parse(`C4Context
${macroName}(b1, "BankBoundary") {
System(SystemAA, "Internet Banking System")
}`);
expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
label: {
text: 'BankBoundary',
},
});
});
it('should parse the type', function () {
c4.parser.parse(`C4Context
${macroName}(b1, "", "company") {
System(SystemAA, "Internet Banking System")
}`);
expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
type: { text: 'company' },
});
});
it('should parse a link', function () {
c4.parser.parse(`C4Context
${macroName}(b1, $link="https://github.com/mermaidjs") {
System(SystemAA, "Internet Banking System")
}`);
expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
label: {
text: {
link: 'https://github.com/mermaidjs',
},
},
});
});
it('should parse tags', function () {
c4.parser.parse(`C4Context
${macroName}(b1, $tags="tag1,tag2") {
System(SystemAA, "Internet Banking System")
}`);
expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
label: {
text: {
tags: 'tag1,tag2',
},
},
});
});
});

View File

@@ -115,80 +115,80 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
"C4Dynamic" return 'C4_DYNAMIC';
"C4Deployment" return 'C4_DEPLOYMENT';
"Person_Ext" { this.begin("person_ext"); return 'PERSON_EXT';}
"Person" { this.begin("person"); return 'PERSON';}
"SystemQueue_Ext" { this.begin("system_ext_queue"); return 'SYSTEM_EXT_QUEUE';}
"SystemDb_Ext" { this.begin("system_ext_db"); return 'SYSTEM_EXT_DB';}
"System_Ext" { this.begin("system_ext"); return 'SYSTEM_EXT';}
"SystemQueue" { this.begin("system_queue"); return 'SYSTEM_QUEUE';}
"SystemDb" { this.begin("system_db"); return 'SYSTEM_DB';}
"System" { this.begin("system"); return 'SYSTEM';}
"Person_Ext" { this.begin("person_ext"); console.log('begin person_ext'); return 'PERSON_EXT';}
"Person" { this.begin("person"); console.log('begin person'); return 'PERSON';}
"SystemQueue_Ext" { this.begin("system_ext_queue"); console.log('begin system_ext_queue'); return 'SYSTEM_EXT_QUEUE';}
"SystemDb_Ext" { this.begin("system_ext_db"); console.log('begin system_ext_db'); return 'SYSTEM_EXT_DB';}
"System_Ext" { this.begin("system_ext"); console.log('begin system_ext'); return 'SYSTEM_EXT';}
"SystemQueue" { this.begin("system_queue"); console.log('begin system_queue'); return 'SYSTEM_QUEUE';}
"SystemDb" { this.begin("system_db"); console.log('begin system_db'); return 'SYSTEM_DB';}
"System" { this.begin("system"); console.log('begin system'); return 'SYSTEM';}
"Boundary" { this.begin("boundary"); return 'BOUNDARY';}
"Enterprise_Boundary" { this.begin("enterprise_boundary"); return 'ENTERPRISE_BOUNDARY';}
"System_Boundary" { this.begin("system_boundary"); return 'SYSTEM_BOUNDARY';}
"Boundary" { this.begin("boundary"); console.log('begin boundary'); return 'BOUNDARY';}
"Enterprise_Boundary" { this.begin("enterprise_boundary"); console.log('begin enterprise_boundary'); return 'ENTERPRISE_BOUNDARY';}
"System_Boundary" { this.begin("system_boundary"); console.log('begin system_boundary'); return 'SYSTEM_BOUNDARY';}
"ContainerQueue_Ext" { this.begin("container_ext_queue"); return 'CONTAINER_EXT_QUEUE';}
"ContainerDb_Ext" { this.begin("container_ext_db"); return 'CONTAINER_EXT_DB';}
"Container_Ext" { this.begin("container_ext"); return 'CONTAINER_EXT';}
"ContainerQueue" { this.begin("container_queue"); return 'CONTAINER_QUEUE';}
"ContainerDb" { this.begin("container_db"); return 'CONTAINER_DB';}
"Container" { this.begin("container"); return 'CONTAINER';}
"ContainerQueue_Ext" { this.begin("container_ext_queue"); console.log('begin container_ext_queue'); return 'CONTAINER_EXT_QUEUE';}
"ContainerDb_Ext" { this.begin("container_ext_db"); console.log('begin container_ext_db'); return 'CONTAINER_EXT_DB';}
"Container_Ext" { this.begin("container_ext"); console.log('begin container_ext'); return 'CONTAINER_EXT';}
"ContainerQueue" { this.begin("container_queue"); console.log('begin container_queue'); return 'CONTAINER_QUEUE';}
"ContainerDb" { this.begin("container_db"); console.log('begin container_db'); return 'CONTAINER_DB';}
"Container" { this.begin("container"); console.log('begin container'); return 'CONTAINER';}
"Container_Boundary" { this.begin("container_boundary"); return 'CONTAINER_BOUNDARY';}
"Container_Boundary" { this.begin("container_boundary"); console.log('begin container_boundary'); return 'CONTAINER_BOUNDARY';}
"ComponentQueue_Ext" { this.begin("component_ext_queue"); return 'COMPONENT_EXT_QUEUE';}
"ComponentDb_Ext" { this.begin("component_ext_db"); return 'COMPONENT_EXT_DB';}
"Component_Ext" { this.begin("component_ext"); return 'COMPONENT_EXT';}
"ComponentQueue" { this.begin("component_queue"); return 'COMPONENT_QUEUE';}
"ComponentDb" { this.begin("component_db"); return 'COMPONENT_DB';}
"Component" { this.begin("component"); return 'COMPONENT';}
"ComponentQueue_Ext" { this.begin("component_ext_queue"); console.log('begin component_ext_queue'); return 'COMPONENT_EXT_QUEUE';}
"ComponentDb_Ext" { this.begin("component_ext_db"); console.log('begin component_ext_db'); return 'COMPONENT_EXT_DB';}
"Component_Ext" { this.begin("component_ext"); console.log('begin component_ext'); return 'COMPONENT_EXT';}
"ComponentQueue" { this.begin("component_queue"); console.log('begin component_queue'); return 'COMPONENT_QUEUE';}
"ComponentDb" { this.begin("component_db"); console.log('begin component_db'); return 'COMPONENT_DB';}
"Component" { this.begin("component"); console.log('begin component'); return 'COMPONENT';}
"Deployment_Node" { this.begin("node"); return 'NODE';}
"Node" { this.begin("node"); return 'NODE';}
"Node_L" { this.begin("node_l"); return 'NODE_L';}
"Node_R" { this.begin("node_r"); return 'NODE_R';}
"Deployment_Node" { this.begin("node"); console.log('begin node'); return 'NODE';}
"Node" { this.begin("node"); console.log('begin node'); return 'NODE';}
"Node_L" { this.begin("node_l"); console.log('begin node_l'); return 'NODE_L';}
"Node_R" { this.begin("node_r"); console.log('begin node_r'); return 'NODE_R';}
"Rel" { this.begin("rel"); return 'REL';}
"BiRel" { this.begin("birel"); return 'BIREL';}
"Rel_Up" { this.begin("rel_u"); return 'REL_U';}
"Rel_U" { this.begin("rel_u"); return 'REL_U';}
"Rel_Down" { this.begin("rel_d"); return 'REL_D';}
"Rel_D" { this.begin("rel_d"); return 'REL_D';}
"Rel_Left" { this.begin("rel_l"); return 'REL_L';}
"Rel_L" { this.begin("rel_l"); return 'REL_L';}
"Rel_Right" { this.begin("rel_r"); return 'REL_R';}
"Rel_R" { this.begin("rel_r"); return 'REL_R';}
"Rel_Back" { this.begin("rel_b"); return 'REL_B';}
"RelIndex" { this.begin("rel_index"); return 'REL_INDEX';}
"Rel" { this.begin("rel"); console.log('begin rel'); return 'REL';}
"BiRel" { this.begin("birel"); console.log('begin birel'); return 'BIREL';}
"Rel_Up" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
"Rel_U" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
"Rel_Down" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
"Rel_D" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
"Rel_Left" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
"Rel_L" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
"Rel_Right" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
"Rel_R" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
"Rel_Back" { this.begin("rel_b"); console.log('begin rel_b'); return 'REL_B';}
"RelIndex" { this.begin("rel_index"); console.log('begin rel_index'); return 'REL_INDEX';}
"UpdateElementStyle" { this.begin("update_el_style"); return 'UPDATE_EL_STYLE';}
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
"UpdateElementStyle" { this.begin("update_el_style"); console.log('begin update_el_style'); return 'UPDATE_EL_STYLE';}
"UpdateRelStyle" { this.begin("update_rel_style"); console.log('begin update_rel_style'); return 'UPDATE_REL_STYLE';}
"UpdateLayoutConfig" { this.begin("update_layout_config"); console.log('begin update_layout_config'); return 'UPDATE_LAYOUT_CONFIG';}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { console.log('begin attribute with ATTRIBUTE_EMPTY'); this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { console.log('begin attribute'); this.begin("attribute"); }
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { console.log('STOP attribute'); this.popState();console.log('STOP diagram'); this.popState();}
<attribute>",," { return 'ATTRIBUTE_EMPTY';}
<attribute>"," { }
<attribute>[ ]*["]["] { return 'ATTRIBUTE_EMPTY';}
<attribute>[ ]*["] { this.begin("string");}
<string>["] { this.popState(); }
<string>[^"]* { return "STR";}
<attribute>",," { console.log(',,'); return 'ATTRIBUTE_EMPTY';}
<attribute>"," { console.log(','); }
<attribute>[ ]*["]["] { console.log('ATTRIBUTE_EMPTY'); return 'ATTRIBUTE_EMPTY';}
<attribute>[ ]*["] { console.log('begin string'); this.begin("string");}
<string>["] { console.log('STOP string'); this.popState(); }
<string>[^"]* { console.log('STR'); return "STR";}
<attribute>[ ]*[\$] { this.begin("string_kv");}
<string_kv>[^=]* { this.begin("string_kv_key"); return "STR_KEY";}
<string_kv_key>[=][ ]*["] { this.popState(); this.begin("string_kv_value"); }
<string_kv_value>[^"]+ { return "STR_VALUE";}
<string_kv_value>["] { this.popState(); this.popState(); }
<attribute>[ ]*[\$] { console.log('begin string_kv'); this.begin("string_kv");}
<string_kv>[^=]* { console.log('STR_KEY'); this.begin("string_kv_key"); return "STR_KEY";}
<string_kv_key>[=][ ]*["] { console.log('begin string_kv_value'); this.popState(); this.begin("string_kv_value"); }
<string_kv_value>[^"]+ { console.log('STR_VALUE'); return "STR_VALUE";}
<string_kv_value>["] { console.log('STOP string_kv_value'); this.popState(); this.popState(); }
<attribute>[^,]+ { return "STR";}
<attribute>[^,]+ { console.log('not STR'); return "STR";}
'{' { /* this.begin("lbrace"); */ return "LBRACE";}
'}' { /* this.popState(); */ return "RBRACE";}
'{' { /* this.begin("lbrace"); */ console.log('begin boundary block'); return "LBRACE";}
'}' { /* this.popState(); */ console.log('STOP boundary block'); return "RBRACE";}
[\s]+ return 'SPACE';
[\n\r]+ return 'EOL';
@@ -231,7 +231,7 @@ directive
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
: open_directive { console.log("open_directive: ", $1); yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
@@ -239,11 +239,11 @@ typeDirective
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); console.log("arg_directive: ", $1); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
: close_directive { console.log("close_directive: ", $1); yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
;
graphConfig
@@ -285,13 +285,13 @@ boundaryStartStatement
;
boundaryStart
: ENTERPRISE_BOUNDARY attributes {$2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| SYSTEM_BOUNDARY attributes {$2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| BOUNDARY attributes {yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| CONTAINER_BOUNDARY attributes {$2.splice(2, 0, 'CONTAINER'); yy.addContainerBoundary(...$2); $$=$2;}
| NODE attributes {yy.addDeploymentNode('node', ...$2); $$=$2;}
| NODE_L attributes {yy.addDeploymentNode('nodeL', ...$2); $$=$2;}
| NODE_R attributes {yy.addDeploymentNode('nodeR', ...$2); $$=$2;}
: ENTERPRISE_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| SYSTEM_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| BOUNDARY attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| CONTAINER_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'CONTAINER'); yy.addContainerBoundary(...$2); $$=$2;}
| NODE attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('node', ...$2); $$=$2;}
| NODE_L attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('nodeL', ...$2); $$=$2;}
| NODE_R attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('nodeR', ...$2); $$=$2;}
;
boundaryStopStatement
@@ -305,48 +305,48 @@ diagramStatements
;
diagramStatement
: PERSON attributes {yy.addPersonOrSystem('person', ...$2); $$=$2;}
| PERSON_EXT attributes {yy.addPersonOrSystem('external_person', ...$2); $$=$2;}
| SYSTEM attributes {yy.addPersonOrSystem('system', ...$2); $$=$2;}
| SYSTEM_DB attributes {yy.addPersonOrSystem('system_db', ...$2); $$=$2;}
| SYSTEM_QUEUE attributes {yy.addPersonOrSystem('system_queue', ...$2); $$=$2;}
| SYSTEM_EXT attributes {yy.addPersonOrSystem('external_system', ...$2); $$=$2;}
| SYSTEM_EXT_DB attributes {yy.addPersonOrSystem('external_system_db', ...$2); $$=$2;}
| SYSTEM_EXT_QUEUE attributes {yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
| CONTAINER attributes {yy.addContainer('container', ...$2); $$=$2;}
| CONTAINER_DB attributes {yy.addContainer('container_db', ...$2); $$=$2;}
| CONTAINER_QUEUE attributes {yy.addContainer('container_queue', ...$2); $$=$2;}
| CONTAINER_EXT attributes {yy.addContainer('external_container', ...$2); $$=$2;}
| CONTAINER_EXT_DB attributes {yy.addContainer('external_container_db', ...$2); $$=$2;}
| CONTAINER_EXT_QUEUE attributes {yy.addContainer('external_container_queue', ...$2); $$=$2;}
| COMPONENT attributes {yy.addComponent('component', ...$2); $$=$2;}
| COMPONENT_DB attributes {yy.addComponent('component_db', ...$2); $$=$2;}
| COMPONENT_QUEUE attributes {yy.addComponent('component_queue', ...$2); $$=$2;}
| COMPONENT_EXT attributes {yy.addComponent('external_component', ...$2); $$=$2;}
| COMPONENT_EXT_DB attributes {yy.addComponent('external_component_db', ...$2); $$=$2;}
| COMPONENT_EXT_QUEUE attributes {yy.addComponent('external_component_queue', ...$2); $$=$2;}
: PERSON attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('person', ...$2); $$=$2;}
| PERSON_EXT attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_person', ...$2); $$=$2;}
| SYSTEM attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system', ...$2); $$=$2;}
| SYSTEM_DB attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system_db', ...$2); $$=$2;}
| SYSTEM_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system_queue', ...$2); $$=$2;}
| SYSTEM_EXT attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system', ...$2); $$=$2;}
| SYSTEM_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system_db', ...$2); $$=$2;}
| SYSTEM_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
| CONTAINER attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container', ...$2); $$=$2;}
| CONTAINER_DB attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container_db', ...$2); $$=$2;}
| CONTAINER_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container_queue', ...$2); $$=$2;}
| CONTAINER_EXT attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container', ...$2); $$=$2;}
| CONTAINER_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container_db', ...$2); $$=$2;}
| CONTAINER_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container_queue', ...$2); $$=$2;}
| COMPONENT attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component', ...$2); $$=$2;}
| COMPONENT_DB attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component_db', ...$2); $$=$2;}
| COMPONENT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component_queue', ...$2); $$=$2;}
| COMPONENT_EXT attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component', ...$2); $$=$2;}
| COMPONENT_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component_db', ...$2); $$=$2;}
| COMPONENT_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component_queue', ...$2); $$=$2;}
| boundaryStatement
| REL attributes {yy.addRel('rel', ...$2); $$=$2;}
| BIREL attributes {yy.addRel('birel', ...$2); $$=$2;}
| REL_U attributes {yy.addRel('rel_u', ...$2); $$=$2;}
| REL_D attributes {yy.addRel('rel_d', ...$2); $$=$2;}
| REL_L attributes {yy.addRel('rel_l', ...$2); $$=$2;}
| REL_R attributes {yy.addRel('rel_r', ...$2); $$=$2;}
| REL_B attributes {yy.addRel('rel_b', ...$2); $$=$2;}
| REL_INDEX attributes {$2.splice(0, 1); yy.addRel('rel', ...$2); $$=$2;}
| UPDATE_EL_STYLE attributes {yy.updateElStyle('update_el_style', ...$2); $$=$2;}
| UPDATE_REL_STYLE attributes {yy.updateRelStyle('update_rel_style', ...$2); $$=$2;}
| UPDATE_LAYOUT_CONFIG attributes {yy.updateLayoutConfig('update_layout_config', ...$2); $$=$2;}
| REL attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel', ...$2); $$=$2;}
| BIREL attributes {console.log($1,JSON.stringify($2)); yy.addRel('birel', ...$2); $$=$2;}
| REL_U attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_u', ...$2); $$=$2;}
| REL_D attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_d', ...$2); $$=$2;}
| REL_L attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_l', ...$2); $$=$2;}
| REL_R attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_r', ...$2); $$=$2;}
| REL_B attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_b', ...$2); $$=$2;}
| REL_INDEX attributes {console.log($1,JSON.stringify($2)); $2.splice(0, 1); yy.addRel('rel', ...$2); $$=$2;}
| UPDATE_EL_STYLE attributes {console.log($1,JSON.stringify($2)); yy.updateElStyle('update_el_style', ...$2); $$=$2;}
| UPDATE_REL_STYLE attributes {console.log($1,JSON.stringify($2)); yy.updateRelStyle('update_rel_style', ...$2); $$=$2;}
| UPDATE_LAYOUT_CONFIG attributes {console.log($1,JSON.stringify($2)); yy.updateLayoutConfig('update_layout_config', ...$2); $$=$2;}
;
attributes
: attribute { $$ = [$1]; }
| attribute attributes { $2.unshift($1); $$=$2;}
: attribute { console.log('PUSH ATTRIBUTE: ', $1); $$ = [$1]; }
| attribute attributes { console.log('PUSH ATTRIBUTE: ', $1); $2.unshift($1); $$=$2;}
;
attribute
: STR { $$ = $1.trim(); }
| STR_KEY STR_VALUE { let kv={}; kv[$1.trim()]=$2.trim(); $$=kv; }
| STR_KEY STR_VALUE { console.log('kv: ', $1, $2); let kv={}; kv[$1.trim()]=$2.trim(); $$=kv; }
| ATTRIBUTE { $$ = $1.trim(); }
| ATTRIBUTE_EMPTY { $$ = ""; }
;

View File

@@ -1,111 +0,0 @@
import c4Db from '../c4Db';
import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe('parsing a C4 Person', function () {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
});
it('should parse a C4 diagram with one Person correctly', function () {
c4.parser.parse(`C4Context
title System Context diagram for Internet Banking System
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")`);
const yy = c4.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
const onlyShape = shapes[0];
expect(onlyShape).toEqual({
alias: 'customerA',
descr: {
text: 'A customer of the bank, with personal bank accounts.',
},
label: {
text: 'Banking Customer A',
},
parentBoundary: 'global',
typeC4Shape: {
text: 'person',
},
wrap: false,
});
});
it('should parse the alias', function () {
c4.parser.parse(`C4Context
Person(customerA, "Banking Customer A")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
alias: 'customerA',
});
});
it('should parse the label', function () {
c4.parser.parse(`C4Context
Person(customerA, "Banking Customer A")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: 'Banking Customer A',
},
});
});
it('should parse the description', function () {
c4.parser.parse(`C4Context
Person(customerA, "", "A customer of the bank, with personal bank accounts.")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
descr: {
text: 'A customer of the bank, with personal bank accounts.',
},
});
});
it('should parse a sprite', function () {
c4.parser.parse(`C4Context
Person(customerA, $sprite="users")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
sprite: 'users',
},
},
});
});
it('should parse a link', function () {
c4.parser.parse(`C4Context
Person(customerA, $link="https://github.com/mermaidjs")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
link: 'https://github.com/mermaidjs',
},
},
});
});
it('should parse tags', function () {
c4.parser.parse(`C4Context
Person(customerA, $tags="tag1,tag2")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
tags: 'tag1,tag2',
},
},
});
});
});

View File

@@ -1,116 +0,0 @@
import c4Db from '../c4Db';
import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe('parsing a C4 Person_Ext', function () {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
});
it('should parse a C4 diagram with one Person_Ext correctly', function () {
c4.parser.parse(`C4Context
title System Context diagram for Internet Banking System
Person_Ext(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")`);
const yy = c4.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
const onlyShape = shapes[0];
expect(onlyShape).toEqual({
alias: 'customerA',
descr: {
text: 'A customer of the bank, with personal bank accounts.',
},
label: {
text: 'Banking Customer A',
},
// TODO: Why are link, sprite, and tags undefined instead of not appearing at all?
// Compare to Person where they don't show up.
link: undefined,
sprite: undefined,
tags: undefined,
parentBoundary: 'global',
typeC4Shape: {
text: 'external_person',
},
wrap: false,
});
});
it('should parse the alias', function () {
c4.parser.parse(`C4Context
Person_Ext(customerA, "Banking Customer A")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
alias: 'customerA',
});
});
it('should parse the label', function () {
c4.parser.parse(`C4Context
Person_Ext(customerA, "Banking Customer A")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: 'Banking Customer A',
},
});
});
it('should parse the description', function () {
c4.parser.parse(`C4Context
Person_Ext(customerA, "", "A customer of the bank, with personal bank accounts.")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
descr: {
text: 'A customer of the bank, with personal bank accounts.',
},
});
});
it('should parse a sprite', function () {
c4.parser.parse(`C4Context
Person_Ext(customerA, $sprite="users")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
sprite: 'users',
},
},
});
});
it('should parse a link', function () {
c4.parser.parse(`C4Context
Person_Ext(customerA, $link="https://github.com/mermaidjs")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
link: 'https://github.com/mermaidjs',
},
},
});
});
it('should parse tags', function () {
c4.parser.parse(`C4Context
Person_Ext(customerA, $tags="tag1,tag2")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
tags: 'tag1,tag2',
},
},
});
});
});

View File

@@ -1,123 +0,0 @@
import c4Db from '../c4Db';
import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe.each([
['System', 'system'],
['SystemDb', 'system_db'],
['SystemQueue', 'system_queue'],
['System_Ext', 'external_system'],
['SystemDb_Ext', 'external_system_db'],
['SystemQueue_Ext', 'external_system_queue'],
])('parsing a C4 %s', function (macroName, elementName) {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
});
it('should parse a C4 diagram with one System correctly', function () {
c4.parser.parse(`C4Context
title System Context diagram for Internet Banking System
${macroName}(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")`);
const yy = c4.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
const onlyShape = shapes[0];
expect(onlyShape).toEqual({
alias: 'SystemAA',
descr: {
text: 'Allows customers to view information about their bank accounts, and make payments.',
},
label: {
text: 'Internet Banking System',
},
// TODO: Why are link, sprite, and tags undefined instead of not appearing at all?
// Compare to Person where they don't show up.
link: undefined,
sprite: undefined,
tags: undefined,
parentBoundary: 'global',
typeC4Shape: {
text: elementName,
},
wrap: false,
});
});
it('should parse the alias', function () {
c4.parser.parse(`C4Context
${macroName}(SystemAA, "Internet Banking System")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
alias: 'SystemAA',
});
});
it('should parse the label', function () {
c4.parser.parse(`C4Context
${macroName}(SystemAA, "Internet Banking System")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: 'Internet Banking System',
},
});
});
it('should parse the description', function () {
c4.parser.parse(`C4Context
${macroName}(SystemAA, "", "Allows customers to view information about their bank accounts, and make payments.")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
descr: {
text: 'Allows customers to view information about their bank accounts, and make payments.',
},
});
});
it('should parse a sprite', function () {
c4.parser.parse(`C4Context
${macroName}(SystemAA, $sprite="users")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
sprite: 'users',
},
},
});
});
it('should parse a link', function () {
c4.parser.parse(`C4Context
${macroName}(SystemAA, $link="https://github.com/mermaidjs")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
link: 'https://github.com/mermaidjs',
},
},
});
});
it('should parse tags', function () {
c4.parser.parse(`C4Context
${macroName}(SystemAA, $tags="tag1,tag2")`);
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
label: {
text: {
tags: 'tag1,tag2',
},
},
});
});
});

View File

@@ -1,20 +1,49 @@
import c4Db from '../c4Db';
import c4 from './c4Diagram.jison';
import flowDb from '../c4Db';
import flow from './c4Diagram.jison';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe('parsing a C4 diagram', function () {
describe('parsing a flow chart', function () {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('should parse a C4 diagram with one Person correctly', function () {
flow.parser.parse(`C4Context
title System Context diagram for Internet Banking System
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")`);
const yy = flow.parser.yy;
expect(yy.getC4Type()).toBe('C4Context');
expect(yy.getTitle()).toBe('System Context diagram for Internet Banking System');
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
const onlyShape = shapes[0];
expect(onlyShape).toEqual({
alias: 'customerA',
descr: {
text: 'A customer of the bank, with personal bank accounts.',
},
label: {
text: 'Banking Customer A',
},
parentBoundary: 'global',
typeC4Shape: {
text: 'person',
},
wrap: false,
});
});
it('should handle a trailing whitespaces after statements', function () {
const whitespace = ' ';
const rendered = c4.parser.parse(`C4Context${whitespace}
const rendered = flow.parser.parse(`C4Context${whitespace}
title System Context diagram for Internet Banking System${whitespace}
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")${whitespace}`);
@@ -22,11 +51,11 @@ Person(customerA, "Banking Customer A", "A customer of the bank, with personal b
});
it('should handle parameter names that are keywords', function () {
c4.parser.parse(`C4Context
flow.parser.parse(`C4Context
title title
Person(Person, "Person", "Person")`);
const yy = c4.parser.yy;
const yy = flow.parser.yy;
expect(yy.getTitle()).toBe('title');
const shapes = yy.getC4ShapeArray();
@@ -39,10 +68,10 @@ Person(Person, "Person", "Person")`);
});
it('should allow default in the parameters', function () {
c4.parser.parse(`C4Context
flow.parser.parse(`C4Context
Person(default, "default", "default")`);
const yy = c4.parser.yy;
const yy = flow.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);

View File

@@ -38,7 +38,7 @@ export const drawEmbeddedImage = function (elem, x, y, link) {
const imageElem = elem.append('use');
imageElem.attr('x', x);
imageElem.attr('y', y);
var sanitizedLink = sanitizeUrl(link);
let sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', '#' + sanitizedLink);
};

View File

@@ -1,9 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const classDetectorV2: DiagramDetector = (txt, config) => {
// If we have confgured to use dagre-wrapper then we should return true in this function for classDiagram code thus making it use the new class diagram
if (txt.match(/^\s*classDiagram/) !== null && config?.class?.defaultRenderer === 'dagre-wrapper')
return true;
// We have not opted to use the new renderer so we should return true if we detect a class diagram
return txt.match(/^\s*classDiagram-v2/) !== null;
};

View File

@@ -1,8 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const classDetector: DiagramDetector = (txt, config) => {
// If we have confgured to use dagre-wrapper then we should never return true in this function
if (config?.class?.defaultRenderer === 'dagre-wrapper') return false;
// We have not opted to use the new renderer so we should return true if we detect a class diagram
return txt.match(/^\s*classDiagram/) !== null;
};

View File

@@ -5,8 +5,7 @@ import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';
// import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { curveLinear } from 'd3';
import { interpolateToCurve, getStylesFromArray } from '../../utils';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import { interpolateToCurve, getStylesFromArray, setupGraphViewbox } from '../../utils';
import common from '../common/common';
import addSVGAccessibilityFields from '../../accessibility';

View File

@@ -3,7 +3,7 @@ import dagre from 'dagre';
import graphlib from 'graphlib';
import { log } from '../../logger';
import svgDraw from './svgDraw';
import { configureSvgSize } from '../../setupGraphViewbox';
import { configureSvgSize } from '../../utils';
import { getConfig } from '../../config';
import addSVGAccessibilityFields from '../../accessibility';
@@ -142,7 +142,8 @@ const insertMarkers = function (elem) {
*
* @param {string} text
* @param {string} id
* @param {any} _version
* @param version
* @param _version
* @param diagObj
*/
export const draw = function (text, id, _version, diagObj) {

View File

@@ -249,7 +249,7 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
});
const classBox = g.node().getBBox();
var cssClassStr = ' ';
let cssClassStr = ' ';
if (classDef.cssClasses.length > 0) {
cssClassStr = cssClassStr + classDef.cssClasses.join(' ');

View File

@@ -1,5 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const erDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*erDiagram/) !== null;
};

View File

@@ -6,7 +6,7 @@ import dagre from 'dagre';
import { getConfig } from '../../config';
import { log } from '../../logger';
import erMarkers from './erMarkers';
import { configureSvgSize } from '../../setupGraphViewbox';
import { configureSvgSize } from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
import { parseGenericTypes } from '../common/common';
@@ -32,7 +32,7 @@ export const setConf = function (cnf) {
* @param groupNode The svg group node for the entity
* @param entityTextNode The svg node for the entity label text
* @param attributes An array of attributes defined for the entity (each attribute has a type and a name)
* @returns {object} The bounding box of the entity, after attributes have been added. The bounding box has a .width and .height
* @returns The bounding box of the entity, after attributes have been added
*/
const drawAttributes = (groupNode, entityTextNode, attributes) => {
const heightPadding = conf.entityPadding / 3; // Padding internal to attribute boxes
@@ -307,7 +307,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
* @param svgNode The svg node that contains the diagram
* @param entities The entities to be drawn
* @param graph The graph that contains the vertex and edge definitions post-layout
* @returns {object} The first entity that was inserted
* @returns The first entity that was inserted
*/
const drawEntities = function (svgNode, entities, graph) {
const keys = Object.keys(entities);
@@ -545,6 +545,7 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
* @param text The text of the diagram
* @param id The unique id of the DOM node that contains the diagram
* @param _version
* @param diag
* @param diagObj
*/
export const draw = function (text, id, _version, diagObj) {

View File

@@ -1,8 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const flowDetectorV2: DiagramDetector = (txt, config) => {
// If we have confgured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper' && txt.match(/^\s*graph/) !== null)
return true;
return txt.match(/^\s*flowchart/) !== null;
};

View File

@@ -1,8 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const flowDetector: DiagramDetector = (txt, config) => {
// If we have confired to only use new flow charts this function shohuld always return false
// as in not signalling true for a legacy flowchart
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') return false;
return txt.match(/^\s*graph/) !== null;
};

View File

@@ -8,8 +8,7 @@ import { render } from '../../dagre-wrapper/index.js';
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { log } from '../../logger';
import common, { evaluate } from '../common/common';
import { interpolateToCurve, getStylesFromArray } from '../../utils';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import { interpolateToCurve, getStylesFromArray, setupGraphViewbox } from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
const conf = {};
@@ -207,7 +206,7 @@ export const addEdges = function (edges, g, diagObj) {
cnt++;
// Identify Link
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
let linkIdBase = 'L-' + edge.start + '-' + edge.end;
// count the links from+to the same node to give unique id
if (typeof linkIdCnt[linkIdBase] === 'undefined') {
linkIdCnt[linkIdBase] = 0;
@@ -218,8 +217,8 @@ export const addEdges = function (edges, g, diagObj) {
}
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
var linkNameStart = 'LS-' + edge.start;
var linkNameEnd = 'LE-' + edge.end;
let linkNameStart = 'LS-' + edge.start;
let linkNameEnd = 'LE-' + edge.end;
const edgeData = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;

View File

@@ -1,154 +0,0 @@
import flowDb from './flowDb';
import flowParser from './parser/flow';
import flowRenderer from './flowRenderer';
import Diagram from '../../Diagram';
import { addDiagrams } from '../../diagram-api/diagram-orchestration';
addDiagrams();
afterEach(() => {
jest.restoreAllMocks();
});
describe('when using mermaid and ', function () {
describe('when calling addEdges ', function () {
beforeEach(function () {
flowParser.parser.yy = flowDb;
flowDb.clear();
flowDb.setGen('gen-2');
});
it('should handle edges with text', function () {
const diag = new Diagram('graph TD;A-->|text ex|B;');
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('normal');
expect(options.label.match('text ex')).toBeTruthy();
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges without text', function () {
const diag = new Diagram('graph TD;A-->B;');
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('normal');
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle open-ended edges', function () {
const diag = new Diagram('graph TD;A---B;');
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('none');
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges with styles defined', function () {
const diag = new Diagram('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('none');
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges with interpolation defined', function () {
const diag = new Diagram('graph TD;A---B; linkStyle 0 interpolate basis');
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('none');
expect(options.curve).toBe('basis'); // mocked as string
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges with text and styles defined', function () {
const diag = new Diagram(
'graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;'
);
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('none');
expect(options.label.match('the text')).toBeTruthy();
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should set fill to "none" by default when handling edges', function () {
const diag = new Diagram('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B');
expect(options.arrowhead).toBe('none');
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
it('should not set fill to none if fill is set in linkStyle', function () {
const diag = new Diagram(
'graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;'
);
diag.db.getVertices();
const edges = diag.db.getEdges();
const mockG = {
setEdge: function (start, end, options) {
expect(start).toContain('flowchart-A-');
expect(end).toContain('flowchart-B-');
expect(options.arrowhead).toBe('none');
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;');
},
};
flowRenderer.addEdges(edges, mockG, diag);
});
});
});

View File

@@ -5,8 +5,7 @@ import dagreD3 from 'dagre-d3';
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { log } from '../../logger';
import common, { evaluate } from '../common/common';
import { interpolateToCurve, getStylesFromArray } from '../../utils';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import { interpolateToCurve, getStylesFromArray, setupGraphViewbox } from '../../utils';
import flowChartShapes from './flowChartShapes';
import addSVGAccessibilityFields from '../../accessibility';
@@ -25,6 +24,7 @@ export const setConf = function (cnf) {
* @param g The graph that is to be drawn.
* @param svgId
* @param root
* @param doc
* @param _doc
* @param diagObj
*/
@@ -178,9 +178,9 @@ export const addEdges = function (edges, g, diagObj) {
cnt++;
// Identify Link
var linkId = 'L-' + edge.start + '-' + edge.end;
var linkNameStart = 'LS-' + edge.start;
var linkNameEnd = 'LE-' + edge.end;
let linkId = 'L-' + edge.start + '-' + edge.end;
let linkNameStart = 'LS-' + edge.start;
let linkNameEnd = 'LE-' + edge.end;
const edgeData = {};

View File

@@ -1,5 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const ganttDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*gantt/) !== null;
};

View File

@@ -13,7 +13,7 @@ import {
} from 'd3';
import common from '../common/common';
import { getConfig } from '../../config';
import { configureSvgSize } from '../../setupGraphViewbox';
import { configureSvgSize } from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
export const setConf = function () {
@@ -399,10 +399,10 @@ export const draw = function (text, id, version, diagObj) {
return typeof links[d.id] !== 'undefined';
})
.each(function (o) {
var taskRect = doc.querySelector('#' + o.id);
var taskText = doc.querySelector('#' + o.id + '-text');
let taskRect = doc.querySelector('#' + o.id);
let taskText = doc.querySelector('#' + o.id + '-text');
const oldParent = taskRect.parentNode;
var Link = doc.createElement('a');
let Link = doc.createElement('a');
Link.setAttribute('xlink:href', links[o.id]);
Link.setAttribute('target', '_top');
oldParent.appendChild(Link);

View File

@@ -348,49 +348,6 @@ describe('when parsing a gitGraph', function () {
expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
});
it('should allow branch names starting with numbers', function () {
const str = `gitGraph:
commit
%% branch names starting with numbers are not recommended, but are supported by git
branch 1.0.1
`;
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('1.0.1');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
});
it('should allow branch names starting with unusual prefixes', function () {
const str = `gitGraph:
commit
%% branch names starting with numbers are not recommended, but are supported by git
branch branch01
branch checkout02
branch cherry-pick03
branch branch/example-branch
branch merge/test_merge
`;
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('merge/test_merge');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(6);
expect(Object.keys(parser.yy.getBranches())).toEqual(
expect.arrayContaining([
'branch01',
'checkout02',
'cherry-pick03',
'branch/example-branch',
'merge/test_merge',
])
);
});
it('should handle new branch checkout', function () {
const str = `gitGraph:
commit
@@ -578,6 +535,8 @@ describe('when parsing a gitGraph', function () {
testBranch3Merge,
] = Object.values(commits);
console.log(Object.keys(commits));
expect(mainCommit.branch).toBe('main');
expect(mainCommit.parents).toStrictEqual([]);

View File

@@ -1,5 +1,5 @@
import { select } from 'd3';
import { configureSvgSize } from '../../setupGraphViewbox';
import { configureSvgSize } from '../../utils';
import { log } from '../../logger';
import { getConfig } from '../../config';
import addSVGAccessibilityFields from '../../accessibility';
@@ -290,13 +290,13 @@ const drawCommits = (svg, commits, modifyGraph) => {
};
/**
* Detect if there are other commits between commit1's x-position and commit2's x-position on the same
* Detect if there are other commits between commit1s x-position and commit2s x-position on the same
* branch as commit2.
*
* @param {any} commit1
* @param {any} commit2
* @param allCommits
* @returns {boolean} if there are commits between commit1's x-position and commit2's x-position
* @returns
*/
const hasOverlappingCommits = (commit1, commit2, allCommits) => {
const commit1Pos = commitPos[commit2.id];
@@ -322,7 +322,7 @@ const hasOverlappingCommits = (commit1, commit2, allCommits) => {
* @param {any} y1
* @param {any} y2
* @param {any} _depth
* @returns {number} y value between y1 and y2
* @returns
*/
const findLane = (y1, y2, _depth) => {
const depth = _depth || 0;
@@ -347,7 +347,7 @@ const findLane = (y1, y2, _depth) => {
};
/**
* Draw the lines between the commits. They were arrows initially.
* This function draw the lines between the commits. They were arrows initially.
*
* @param {any} svg
* @param {any} commit1
@@ -453,10 +453,12 @@ const drawArrows = (svg, commits) => {
};
/**
* Adds the branches and the branches' labels to the svg.
* This function adds the branches and the branches' labels to the svg.
*
* @param svg
* @param commitid
* @param branches
* @param direction
*/
const drawBranches = (svg, branches) => {
const gitGraphConfig = getConfig().gitGraph;
@@ -508,6 +510,10 @@ const drawBranches = (svg, branches) => {
};
/**
* @param svg
* @param commit
* @param direction
* @param branchColor
* @param txt
* @param id
* @param ver

View File

@@ -33,10 +33,11 @@ accDescr\s*"{"\s* { this.begin("ac
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
(\r?\n)+ /*{console.log('New line');return 'NL';}*/ return 'NL';
\s+ /* skip all whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"gitGraph" return 'GG';
commit(?=\s|$) return 'COMMIT';
"commit" return 'COMMIT';
"id:" return 'COMMIT_ID';
"type:" return 'COMMIT_TYPE';
"msg:" return 'COMMIT_MSG';
@@ -44,12 +45,12 @@ commit(?=\s|$) return 'COMMIT';
"REVERSE" return 'REVERSE';
"HIGHLIGHT" return 'HIGHLIGHT';
"tag:" return 'COMMIT_TAG';
branch(?=\s|$) return 'BRANCH';
"branch" return 'BRANCH';
"order:" return 'ORDER';
merge(?=\s|$) return 'MERGE';
cherry-pick(?=\s|$) return 'CHERRY_PICK';
"merge" return 'MERGE';
"cherry-pick" return 'CHERRY_PICK';
// "reset" return 'RESET';
checkout(?=\s|$) return 'CHECKOUT';
"checkout" return 'CHECKOUT';
"LR" return 'DIR';
"BT" return 'DIR';
":" return ':';
@@ -60,10 +61,9 @@ checkout(?=\s|$) return 'CHECKOUT';
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return 'STR';
[0-9]+(?=\s|$) return 'NUM';
\w[-\./\w]*[-\w] return 'ID'; // only a subset of https://git-scm.com/docs/git-check-ref-format
[0-9]+ return 'NUM';
[a-zA-Z][-_\./a-zA-Z0-9]*[-_a-zA-Z0-9] return 'ID';
<<EOF>> return 'EOF';
\s+ /* skip all whitespace */ // lowest priority so we can use lookaheads in earlier regex
/lex

View File

@@ -1,9 +1,8 @@
/** Created by knut on 15-01-14. */
import { log } from '../../logger';
import { clear } from '../../commonDb';
var message = '';
var info = false;
let message = '';
let info = false;
export const setMessage = (txt) => {
log.debug('Setting message to: ' + txt);
@@ -31,6 +30,5 @@ export default {
getMessage,
setInfo,
getInfo,
clear,
// parseError
};

View File

@@ -1,5 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const infoDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*info/) !== null;
};

View File

@@ -49,6 +49,7 @@ export const draw = (text, id, version, diagObj) => {
svg.attr('width', 400);
// svg.attr('viewBox', '0 0 300 150');
} catch (e) {
console.error(e);
log.error('Error while rendering info diagram');
log.error(e.message);
}

View File

@@ -0,0 +1,15 @@
describe('when parsing an info graph it', function () {
let ex;
beforeEach(function () {
// eslint-disable-next-line @typescript-eslint/no-var-requires
ex = require('./parser/info').parser;
ex.yy = require('./infoDb');
});
it('should handle an info definition', function () {
let str = `info
showInfo`;
ex.parse(str);
});
});

View File

@@ -0,0 +1,34 @@
/** Created by knut on 15-01-14. */
import { log } from '../../logger';
let message = '';
let info = false;
export const setMessage = (txt) => {
log.debug('Setting message to: ' + txt);
message = txt;
};
export const getMessage = () => {
return message;
};
export const setInfo = (inf) => {
info = inf;
};
export const getInfo = () => {
return info;
};
// export const parseError = (err, hash) => {
// global.mermaidAPI.parseError(err, hash)
// }
export default {
setMessage,
getMessage,
setInfo,
getInfo,
// parseError
};

View File

@@ -0,0 +1,59 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log } from '../../logger';
import { getConfig } from '../../config';
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param {any} text
* @param {any} id
* @param {any} version
* @param diagObj
*/
export const draw = (text, id, version, diagObj) => {
try {
// const parser = infoParser.parser;
// parser.yy = db;
log.debug('Renering info diagram\n' + text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sanbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
// Parse the graph definition
// parser.parse(text);
// log.debug('Parsed info diagram');
// Fetch the default direction, use TD if none was found
const svg = root.select('#' + id);
const g = svg.append('g');
g.append('text') // text label for the x axis
.attr('x', 100)
.attr('y', 40)
.attr('class', 'version')
.attr('font-size', '32px')
.style('text-anchor', 'middle')
.text('v ' + version);
svg.attr('height', 100);
svg.attr('width', 400);
// svg.attr('viewBox', '0 0 300 150');
} catch (e) {
log.error('Error while rendering info diagram');
log.error(e.message);
}
};
export default {
draw,
};

View File

@@ -1,328 +0,0 @@
import { parser as mindmap } from './parser/mindmap';
import * as mindmapDB from './mindmapDb';
import { setLogLevel } from '../../diagram-api/diagramAPI';
describe('when parsing a mindmap ', function () {
beforeEach(function () {
mindmap.yy = mindmapDB;
mindmap.yy.clear();
setLogLevel('trace');
});
describe('hiearchy', function () {
it('MMP-1 should handle a simple root definition abc122', function () {
let str = `mindmap
root`;
mindmap.parse(str);
// console.log('Time for checks', mindmap.yy.getMindmap().descr);
expect(mindmap.yy.getMindmap().descr).toEqual('root');
});
it('MMP-2 should handle a hierachial mindmap definition', function () {
let str = `mindmap
root
child1
child2
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('root');
expect(mm.children.length).toEqual(2);
expect(mm.children[0].descr).toEqual('child1');
expect(mm.children[1].descr).toEqual('child2');
});
it('3 should handle a simple root definition with a shape and without an id abc123', function () {
let str = `mindmap
(root)`;
mindmap.parse(str);
// console.log('Time for checks', mindmap.yy.getMindmap().descr);
expect(mindmap.yy.getMindmap().descr).toEqual('root');
});
it('MMP-4 should handle a deeper hierachial mindmap definition', function () {
let str = `mindmap
root
child1
leaf1
child2`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('root');
expect(mm.children.length).toEqual(2);
expect(mm.children[0].descr).toEqual('child1');
expect(mm.children[0].children[0].descr).toEqual('leaf1');
expect(mm.children[1].descr).toEqual('child2');
});
it('5 Multiple roots are illegal', function () {
let str = `mindmap
root
fakeRoot`;
try {
mindmap.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
}
});
it('MMP-6 real root in wrong place', function () {
let str = `mindmap
root
fakeRoot
realRootWrongPlace`;
try {
mindmap.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
}
});
});
describe('nodes', function () {
it('MMP-7 should handle an id and type for a node definition', function () {
let str = `mindmap
root[The root]
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
});
it('MMP-8 should handle an id and type for a node definition', function () {
let str = `mindmap
root
theId(child1)`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.descr).toEqual('child1');
expect(child.nodeId).toEqual('theId');
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('MMP-9 should handle an id and type for a node definition', function () {
let str = `mindmap
root
theId(child1)`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.descr).toEqual('child1');
expect(child.nodeId).toEqual('theId');
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('MMP-10 mutiple types (circle)', function () {
let str = `mindmap
root((the root))
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('the root');
expect(mm.children.length).toEqual(0);
expect(mm.type).toEqual(mindmap.yy.nodeType.CIRCLE);
});
it('MMP-11 mutiple types (cloud)', function () {
let str = `mindmap
root)the root(
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('the root');
expect(mm.children.length).toEqual(0);
expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD);
});
it('MMP-12 mutiple types (bang)', function () {
let str = `mindmap
root))the root((
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.descr).toEqual('the root');
expect(mm.children.length).toEqual(0);
expect(mm.type).toEqual(mindmap.yy.nodeType.BANG);
});
});
describe('decorations', function () {
it('MMP-13 should be possible to set an icon for the node', function () {
let str = `mindmap
root[The root]
::icon(bomb)
`;
// ::class1 class2
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.icon).toEqual('bomb');
});
it('MMP-14 should be possible to set classes for the node', function () {
let str = `mindmap
root[The root]
:::m-4 p-8
`;
// ::class1 class2
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8');
});
it('MMP-15 should be possible to set both classes and icon for the node', function () {
let str = `mindmap
root[The root]
:::m-4 p-8
::icon(bomb)
`;
// ::class1 class2
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8');
expect(mm.icon).toEqual('bomb');
});
it('MMP-16 should be possible to set both classes and icon for the node', function () {
let str = `mindmap
root[The root]
::icon(bomb)
:::m-4 p-8
`;
// ::class1 class2
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8');
expect(mm.icon).toEqual('bomb');
});
});
describe('descriptions', function () {
it('MMP-17 should be possible to use node syntax in the descriptions', function () {
let str = `mindmap
root["String containing []"]
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('String containing []');
});
it('MMP-18 should be possible to use node syntax in the descriptions in children', function () {
let str = `mindmap
root["String containing []"]
child1["String containing ()"]
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('String containing []');
expect(mm.children.length).toEqual(1);
expect(mm.children[0].descr).toEqual('String containing ()');
});
it('MMP-19 should be possible to have a child after a class assignment', function () {
let str = `mindmap
root(Root)
Child(Child)
:::hot
a(a)
b[New Stuff]`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('Root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.nodeId).toEqual('Child');
expect(child.children[0].nodeId).toEqual('a');
expect(child.children.length).toEqual(2);
expect(child.children[1].nodeId).toEqual('b');
});
});
it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () {
let str = `mindmap
root(Root)
Child(Child)
a(a)
b[New Stuff]`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('Root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.nodeId).toEqual('Child');
expect(child.children[0].nodeId).toEqual('a');
expect(child.children.length).toEqual(2);
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-21 should be possible to have comments in a mindmap', function () {
let str = `mindmap
root(Root)
Child(Child)
a(a)
%% This is a comment
b[New Stuff]`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('Root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.nodeId).toEqual('Child');
expect(child.children[0].nodeId).toEqual('a');
expect(child.children.length).toEqual(2);
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-22 should be possible to have comments at the end of a line', function () {
let str = `mindmap
root(Root)
Child(Child)
a(a) %% This is a comment
b[New Stuff]`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('Root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.nodeId).toEqual('Child');
expect(child.children[0].nodeId).toEqual('a');
expect(child.children.length).toEqual(2);
expect(child.children[1].nodeId).toEqual('b');
});
});

View File

@@ -1,151 +0,0 @@
/** Created by knut on 15-01-14. */
import { sanitizeText, getConfig, log as _log } from '../../diagram-api/diagramAPI';
let nodes = [];
let cnt = 0;
let elements = {};
export const clear = () => {
nodes = [];
cnt = 0;
elements = {};
};
const getParent = function (level) {
for (let i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].level < level) {
return nodes[i];
}
}
// No parent found
return null;
};
export const getMindmap = () => {
return nodes.length > 0 ? nodes[0] : null;
};
export const addNode = (level, id, descr, type) => {
log.info('addNode', level, id, descr, type);
const conf = getConfig();
const node = {
id: cnt++,
nodeId: sanitizeText(id),
level,
descr: sanitizeText(descr),
type,
children: [],
width: getConfig().mindmap.maxNodeWidth,
};
switch (node.type) {
case nodeType.ROUNDED_RECT:
node.padding = 2 * conf.mindmap.padding;
break;
case nodeType.RECT:
node.padding = 2 * conf.mindmap.padding;
break;
default:
node.padding = conf.mindmap.padding;
}
const parent = getParent(level);
if (parent) {
parent.children.push(node);
// Keep all nodes in the list
nodes.push(node);
} else {
if (nodes.length === 0) {
// First node, the root
nodes.push(node);
} else {
// Syntax error ... there can only bee one root
let error = new Error(
'There can be only one root. No parent could be found for ("' + node.descr + '")'
);
error.hash = {
text: 'branch ' + name,
token: 'branch ' + name,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['"checkout ' + name + '"'],
};
throw error;
}
}
};
export const nodeType = {
DEFAULT: 0,
NO_BORDER: 0,
ROUNDED_RECT: 1,
RECT: 2,
CIRCLE: 3,
CLOUD: 4,
BANG: 5,
};
export const getType = (startStr, endStr) => {
log.debug('In get type', startStr, endStr);
switch (startStr) {
case '[':
return nodeType.RECT;
case '(':
return endStr === ')' ? nodeType.ROUNDED_RECT : nodeType.CLOUD;
case '((':
return nodeType.CIRCLE;
case ')':
return nodeType.CLOUD;
case '))':
return nodeType.BANG;
default:
return nodeType.DEFAULT;
}
};
export const setElementForId = (id, element) => {
elements[id] = element;
};
export const decorateNode = (decoration) => {
const node = nodes[nodes.length - 1];
if (decoration && decoration.icon) {
node.icon = sanitizeText(decoration.icon);
}
if (decoration && decoration.class) {
node.class = sanitizeText(decoration.class);
}
};
export const type2Str = (type) => {
switch (type) {
case nodeType.DEFAULT:
return 'no-border';
case nodeType.RECT:
return 'rect';
case nodeType.ROUNDED_RECT:
return 'rounded-rect';
case nodeType.CIRCLE:
return 'circle';
case nodeType.CLOUD:
return 'cloud';
case nodeType.BANG:
return 'bang';
default:
return 'no-border';
}
};
// Expose logger to grammar
export const log = _log;
export const getNodeById = (id) => nodes[id];
export const getElementById = (id) => elements[id];
// export default {
// // getMindmap,
// // addNode,
// // clear,
// // nodeType,
// // getType,
// // decorateNode,
// // setElementForId,
// getElementById: (id) => elements[id],
// // getNodeById: (id) => nodes.find((node) => node.id === id),
// getNodeById: (id) => nodes[id],
// // type2Str,
// // parseError
// };

View File

@@ -1,8 +0,0 @@
const detector = function detect(txt) {
if (txt.match(/^\s*mindmap/)) {
return 'mindmap';
}
return null;
};
export default detector;

View File

@@ -1,5 +0,0 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
export const mindmapDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*mindmap/) !== null;
};

View File

@@ -1,271 +0,0 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
import svgDraw from './svgDraw';
import { BoundingBox, Layout, Tree } from 'non-layered-tidy-tree-layout';
import clone from 'fast-clone';
import * as db from './mindmapDb';
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The maindmap data and hierarchy
* @param section
* @param {object} conf The configuration object
*/
function drawNodes(svg, mindmap, section, conf) {
svgDraw.drawNode(svg, mindmap, section, conf);
if (mindmap.children) {
mindmap.children.forEach((child, index) => {
drawNodes(svg, child, section < 0 ? index : section, conf);
});
}
}
/**
* @param edgesElem
* @param mindmap
* @param parent
* @param depth
* @param section
* @param conf
*/
function drawEdges(edgesElem, mindmap, parent, depth, section, conf) {
if (parent) {
svgDraw.drawEdge(edgesElem, mindmap, parent, depth, section, conf);
}
if (mindmap.children) {
mindmap.children.forEach((child, index) => {
drawEdges(edgesElem, child, mindmap, depth + 1, section < 0 ? index : section, conf);
});
}
}
/**
* @param mindmap
* @param callback
*/
function eachNode(mindmap, callback) {
callback(mindmap);
if (mindmap.children) {
mindmap.children.forEach((child) => {
eachNode(child, callback);
});
}
}
/** @param {object} mindmap */
function transpose(mindmap) {
eachNode(mindmap, (node) => {
const orgWidth = node.width;
const orgX = node.x;
node.width = node.height;
node.height = orgWidth;
node.x = node.y;
node.y = orgX;
});
return mindmap;
}
/** @param {object} mindmap */
function bottomToUp(mindmap) {
log.debug('bottomToUp', mindmap);
eachNode(mindmap.result, (node) => {
// node.y = node.y - (node.y - bb.top) * 2 - node.height;
node.y = node.y - (node.y - 0) * 2 - node.height;
});
return mindmap;
}
/** @param {object} mindmap The mindmap hierarchy */
function rightToLeft(mindmap) {
eachNode(mindmap.result, (node) => {
// node.y = node.y - (node.y - bb.top) * 2 - node.height;
node.x = node.x - (node.x - 0) * 2 - node.width;
});
return mindmap;
}
/**
* @param mindmap
* @param dir
* @param conf
*/
function layout(mindmap, dir, conf) {
const bb = new BoundingBox(30, 60);
const layout = new Layout(bb);
switch (dir) {
case 'TB':
return layout.layout(mindmap);
case 'BT':
return bottomToUp(layout.layout(mindmap));
case 'RL': {
transpose(mindmap);
let newRes = layout.layout(mindmap);
transpose(newRes.result);
return rightToLeft(newRes);
}
case 'LR': {
transpose(mindmap);
let newRes = layout.layout(mindmap);
transpose(newRes.result);
return newRes;
}
default:
}
}
const dirFromIndex = (index) => {
const dirNum = (index + 2) % 4;
switch (dirNum) {
case 0:
return 'LR';
case 1:
return 'RL';
case 2:
return 'TB';
case 3:
return 'BT';
default:
return 'TB';
}
};
const mergeTrees = (node, trees) => {
node.x = trees[0].result.x;
node.y = trees[0].result.y;
trees.forEach((tree) => {
tree.result.children.forEach((child) => {
const dx = node.x - tree.result.x;
const dy = node.y - tree.result.y;
eachNode(child, (childNode) => {
const orgNode = db.getNodeById(childNode.id);
if (orgNode) {
orgNode.x = childNode.x + dx;
orgNode.y = childNode.y + dy;
}
});
});
});
return node;
};
/**
* @param node
* @param conf
*/
function layoutMindmap(node, conf) {
// BoundingBox(gap, bottomPadding)
// const bb = new BoundingBox(10, 10);
// const layout = new Layout(bb);
// // const layout = new HorizontalLayout(bb);
if (node.children.length === 0) {
return node;
}
const trees = [];
// node.children.forEach((child, index) => {
// const tree = clone(node);
// tree.children = [tree.children[index]];
// trees.push(layout(tree, dirFromIndex(index), conf));
// });
let cnt = 0;
// For each direction, create a new tree with the same root, and add a ubset of the children to it.
for (let i = 0; i < 4; i++) {
// Calculate the number of the children of the root node that will be used in this direction
const numChildren =
Math.floor(node.children.length / 4) + (node.children.length % 4 > i ? 1 : 0);
// Copy the original root node
const tree = clone(node);
// Setup the new copy with the children to be rendered in this direction
tree.children = [];
for (let j = 0; j < numChildren; j++) {
tree.children.push(node.children[cnt]);
cnt++;
}
if (tree.children.length > 0) {
trees.push(layout(tree, dirFromIndex(i), conf));
}
}
// Let each node know the direct of its tree for when we draw the branches.
trees.forEach((tree, index) => {
tree.result.direction = dirFromIndex(index);
eachNode(tree.result, (node) => {
node.direction = dirFromIndex(index);
});
});
// Merge the trees into a single tree
const result = mergeTrees(node, trees);
eachNode;
return node;
}
/**
* @param node
* @param conf
*/
function positionNodes(node, conf) {
svgDraw.positionNode(node, conf);
if (node.children) {
node.children.forEach((child) => {
positionNodes(child, conf);
});
}
}
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param {any} text
* @param {any} id
* @param {any} version
* @param diagObj
*/
export const draw = (text, id, version, diagObj) => {
const conf = getConfig();
try {
log.debug('Renering info diagram\n' + text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sanbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
// Parse the graph definition
const svg = root.select('#' + id);
const g = svg.append('g');
const mm = diagObj.db.getMindmap();
// Draw the graph and start with drawing the nodes without proper position
// this gives us the size of the nodes and we can set the positions later
const edgesElem = svg.append('g');
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
drawNodes(nodesElem, mm, -1, conf);
// Next step is to layout the mindmap, giving each node a position
const positionedMindmap = layoutMindmap(mm, conf);
// After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, positionedMindmap, null, 0, -1, conf);
positionNodes(positionedMindmap, conf);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
} catch (e) {
log.error('Error while rendering info diagram');
log.error(e.message);
}
};
export default {
draw,
};

View File

@@ -0,0 +1,48 @@
/** mermaid
* https://knsv.github.io/mermaid
* (c) 2015 Knut Sveidqvist
* MIT license.
*/
%lex
%options case-insensitive
%{
// Pre-lexer code can go here
%}
%%
"info" return 'info' ;
[\s\n\r]+ return 'NL' ;
[\s]+ return 'space';
"showInfo" return 'showInfo';
<<EOF>> return 'EOF' ;
. return 'TXT' ;
/lex
%start start
%% /* language grammar */
start
// %{ : info document 'EOF' { return yy; } }
: info document 'EOF' { return yy; }
;
document
: /* empty */
| document line
;
line
: statement { }
| 'NL'
;
statement
: showInfo { yy.setInfo(true); }
;
%%

View File

@@ -1,108 +0,0 @@
/** mermaid
* https://knsv.github.io/mermaid
* (c) 2015 Knut Sveidqvist
* MIT license.
*/
%lex
%options case-insensitive
%{
// Pre-lexer code can go here
%}
%x NODE
%x NSTR
%x ICON
%x CLASS
%%
\s*\%\%.* {yy.log.trace('Found comment',yytext);}
// \%\%[^\n]*\n /* skip comments */
"mindmap" return 'MINDMAP';
":::" { this.begin('CLASS'); }
<CLASS>.+ { this.popState();return 'CLASS'; }
<CLASS>\n { this.popState();}
// [\s]*"::icon(" { this.begin('ICON'); }
"::icon(" { yy.log.trace('Begin icon');this.begin('ICON'); }
[\n]+ return 'NL';
<ICON>[^\)]+ { return 'ICON'; }
<ICON>\) {yy.log.trace('end icon');this.popState();}
"-)" { yy.log.trace('Exploding node'); this.begin('NODE');return 'NODE_DSTART'; }
"(-" { yy.log.trace('Cloud'); this.begin('NODE');return 'NODE_DSTART'; }
"))" { yy.log.trace('Explosion Bang'); this.begin('NODE');return 'NODE_DSTART'; }
")" { yy.log.trace('Cloud Bang'); this.begin('NODE');return 'NODE_DSTART'; }
"((" { this.begin('NODE');return 'NODE_DSTART'; }
"(" { this.begin('NODE');return 'NODE_DSTART'; }
"[" { this.begin('NODE');return 'NODE_DSTART'; }
[\s]+ return 'SPACELIST' /* skip all whitespace */ ;
// !(-\() return 'NODE_ID';
[^\(\[\n\-\)]+ return 'NODE_ID';
<<EOF>> return 'EOF';
<NODE>["] { yy.log.trace('Starting NSTR');this.begin("NSTR");}
<NSTR>[^"]+ { yy.log.trace('description:', yytext); return "NODE_DESCR";}
<NSTR>["] {this.popState();}
<NODE>[\)]\) {this.popState();yy.log.trace('node end ))');return "NODE_DEND";}
<NODE>[\)] {this.popState();yy.log.trace('node end )');return "NODE_DEND";}
<NODE>[\]] {this.popState();yy.log.trace('node end ...',yytext);return "NODE_DEND";}
<NODE>"(-" {this.popState();yy.log.trace('node end (-');return "NODE_DEND";}
<NODE>"-)" {this.popState();yy.log.trace('node end (-');return "NODE_DEND";}
<NODE>"((" {this.popState();yy.log.trace('node end ((');return "NODE_DEND";}
<NODE>"(" {this.popState();yy.log.trace('node end ((');return "NODE_DEND";}
<NODE>[^\)\]\(]+ { yy.log.trace('Long description:', yytext); return 'NODE_DESCR';}
<NODE>.+(?!\(\() { yy.log.trace('Long description:', yytext); return 'NODE_DESCR';}
// [\[] return 'NODE_START';
// .+ return 'TXT' ;
/lex
%start start
%% /* language grammar */
start
// %{ : info document 'EOF' { return yy; } }
: MINDMAP document { return yy; }
| MINDMAP NL document { return yy; }
| SPACELIST MINDMAP document { return yy; }
;
stop
: NL {yy.log.trace('Stop NL ');}
| EOF {yy.log.trace('Stop EOF ');}
| stop NL {yy.log.trace('Stop NL2 ');}
| stop EOF {yy.log.trace('Stop EOF2 ');}
;
document
: document statement stop
| statement stop
;
statement
: SPACELIST node { yy.log.trace('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
| SPACELIST ICON { yy.log.trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
| SPACELIST CLASS { yy.decorateNode({class: $2}); }
| node { yy.log.trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
| ICON { yy.decorateNode({icon: $1}); }
| CLASS { yy.decorateNode({class: $1}); }
| SPACELIST
;
node
:nodeWithId
|nodeWithoutId
;
nodeWithoutId
: NODE_DSTART NODE_DESCR NODE_DEND
{ yy.log.trace("node found ..", $1); $$ = { id: $2, descr: $2, type: yy.getType($1, $3) }; }
;
nodeWithId
: NODE_ID { $$ = { id: $1, descr: $1, type: yy.nodeType.DEFAULT }; }
| NODE_ID NODE_DSTART NODE_DESCR NODE_DEND
{ yy.log.trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; }
;
%%

View File

@@ -1,76 +1,3 @@
import { darken, lighten, adjust, invert, isDark } from 'khroma';
const getStyles = () => ``;
const genSections = (options) => {
let sections = '';
for (let i = 0; i < 8; i++) {
options['lineColor' + i] = options['lineColor' + i] || options['gitInv' + i];
if (isDark(options['lineColor' + i])) {
options['lineColor' + i] = lighten(options['lineColor' + i], 20);
} else {
options['lineColor' + i] = darken(options['lineColor' + i], 20);
}
}
for (let i = 0; i < 8; i++) {
const sw = '' + (17 - 3 * i);
sections += `
.section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${
i - 1
} path {
fill: ${options['git' + i]};
}
.section-${i - 1} text {
fill: ${options['gitBranchLabel' + i]};
// fill: ${options['gitInv' + i]};
}
.node-icon-${i - 1} {
font-size: 40px;
color: ${options['gitBranchLabel' + i]};
// color: ${options['gitInv' + i]};
}
.section-edge-${i - 1}{
stroke: ${options['git' + i]};
}
.edge-depth-${i - 1}{
stroke-width: ${sw};
}
.section-${i - 1} line {
stroke: ${options['lineColor' + i]} ;
stroke-width: 3;
}
.disabled, .disabled circle, .disabled text {
fill: lightgray;
}
.disabled text {
fill: #efefef;
}
`;
}
return sections;
};
const getStyles = (options) =>
`
.edge {
stroke-width: 3;
}
${genSections(options)}
.section-root rect, .section-root path, .section-root circle {
fill: ${options.git0};
}
.section-root text {
fill: ${options.gitBranchLabel0};
}
.icon-container {
height:100%;
display: flex;
justify-content: center;
align-items: center;
}
.edge {
fill: none;
}
`;
export default getStyles;

View File

@@ -1,320 +0,0 @@
import { select } from 'd3';
import * as db from './mindmapDb';
/**
* @param {string} text The text to be wrapped
* @param {number} width The max width of the text
*/
function wrap(text, width) {
text.each(function () {
var text = select(this),
words = text
.text()
.split(/(\s+|<br>)/)
.reverse(),
word,
line = [],
lineHeight = 1.1, // ems
y = text.attr('y'),
dy = parseFloat(text.attr('dy')),
tspan = text
.text(null)
.append('tspan')
.attr('x', 0)
.attr('y', y)
.attr('dy', dy + 'em');
for (let j = 0; j < words.length; j++) {
word = words[words.length - 1 - j];
line.push(word);
tspan.text(line.join(' ').trim());
if (tspan.node().getComputedTextLength() > width || word === '<br>') {
line.pop();
tspan.text(line.join(' ').trim());
if (word === '<br>') {
line = [''];
} else {
line = [word];
}
tspan = text
.append('tspan')
.attr('x', 0)
.attr('y', y)
.attr('dy', lineHeight + 'em')
.text(word);
}
}
});
}
const defaultBkg = function (elem, node, section, conf) {
const rd = 5;
const r = elem
.append('path')
.attr('id', 'node-' + node.id)
.attr('class', 'node-bkg node-' + db.type2Str(node.type))
.attr(
'd',
`M0 ${node.height - rd} v${-node.height + 2 * rd} q0,-5 5,-5 h${
node.width - 2 * rd
} q5,0 5,5 v${node.height - rd} H0 Z`
);
elem
.append('line')
.attr('class', 'node-line-' + section)
.attr('x1', 0)
.attr('y1', node.height)
.attr('x2', node.width)
.attr('y2', node.height);
};
const rectBkg = function (elem, node, section, conf) {
const r = elem
.append('rect')
.attr('id', 'node-' + node.id)
.attr('class', 'node-bkg node-' + db.type2Str(node.type))
.attr('height', node.height)
.attr('width', node.width);
};
const cloudBkg = function (elem, node, section, conf) {
const rd = 5;
const r = elem;
const w = node.width;
const h = node.height;
const r0 = 0.1 * w;
const r1 = 0.15 * w;
const r2 = 0.25 * w;
const r3 = 0.35 * w;
const r4 = 0.2 * w;
const p = elem
.append('path')
.attr('id', 'node-' + node.id)
.attr('class', 'node-bkg node-' + db.type2Str(node.type))
.attr(
'd',
`M0 0 a${r1},${r1} 0 0,1 ${w * 0.25},${-1 * w * 0.1}
a${r3},${r3} 1 0,1 ${w * 0.4},${-1 * w * 0.1}
a${r2},${r2} 1 0,1 ${w * 0.35},${1 * w * 0.2}
a${r1},${r1} 1 0,1 ${w * 0.15},${1 * h * 0.35}
a${r4},${r4} 1 0,1 ${-1 * w * 0.15},${1 * h * 0.65}
a${r2},${r1} 1 0,1 ${-1 * w * 0.25},${w * 0.15}
a${r3},${r3} 1 0,1 ${-1 * w * 0.5},${0}
a${r1},${r1} 1 0,1 ${-1 * w * 0.25},${-1 * w * 0.15}
a${r1},${r1} 1 0,1 ${-1 * w * 0.1},${-1 * h * 0.35}
a${r4},${r4} 1 0,1 ${w * 0.1},${-1 * h * 0.65}
H0 V0 Z`
);
};
const bangBkg = function (elem, node, section, conf) {
const rd = 5;
const w = node.width;
const h = node.height;
const r = 0.15 * w;
const p = elem
.append('path')
.attr('id', 'node-' + node.id)
.attr('class', 'node-bkg node-' + db.type2Str(node.type))
.attr(
'd',
`M0 0 a${r},${r} 1 0,0 ${w * 0.25},${-1 * h * 0.1}
a${r},${r} 1 0,0 ${w * 0.25},${0}
a${r},${r} 1 0,0 ${w * 0.25},${0}
a${r},${r} 1 0,0 ${w * 0.25},${1 * h * 0.1}
a${r},${r} 1 0,0 ${w * 0.15},${1 * h * 0.33}
a${r * 0.8},${r * 0.8} 1 0,0 ${0},${1 * h * 0.34}
a${r},${r} 1 0,0 ${-1 * w * 0.15},${1 * h * 0.33}
a${r},${r} 1 0,0 ${-1 * w * 0.25},${h * 0.15}
a${r},${r} 1 0,0 ${-1 * w * 0.25},${0}
a${r},${r} 1 0,0 ${-1 * w * 0.25},${0}
a${r},${r} 1 0,0 ${-1 * w * 0.25},${-1 * h * 0.15}
a${r},${r} 1 0,0 ${-1 * w * 0.1},${-1 * h * 0.33}
a${r * 0.8},${r * 0.8} 1 0,0 ${0},${-1 * h * 0.34}
a${r},${r} 1 0,0 ${w * 0.1},${-1 * h * 0.33}
H0 V0 Z`
);
};
const circleBkg = function (elem, node, section, conf) {
const r = elem
.append('circle')
.attr('id', 'node-' + node.id)
.attr('class', 'node-bkg node-' + db.type2Str(node.type))
.attr('r', node.width / 2);
// .attr('width', node.width);
};
const roundedRectBkg = function (elem, node, section, conf) {
const r = elem
.append('rect')
.attr('id', 'node-' + node.id)
.attr('class', 'node-bkg node-' + db.type2Str(node.type))
.attr('height', node.height)
.attr('rx', node.padding)
.attr('ry', node.padding)
.attr('width', node.width);
};
/**
* @param {object} elem The D3 dom element in which the node is to be added
* @param {object} node The node to be added
* @param section
* @param {object} conf The configuration object
* @returns {number} The height nodes dom element
*/
export const drawNode = function (elem, node, section, conf) {
const nodeElem = elem.append('g');
nodeElem.attr(
'class',
(node.class ? node.class + ' ' : '') +
'mindmap-node ' +
(section === -1 ? 'section-root' : 'section-' + section)
);
const bkgElem = nodeElem.append('g');
// Create the wrapped text element
const textElem = nodeElem.append('g');
const txt = textElem
.append('text')
.text(node.descr)
.attr('dy', '1em')
// .attr('dy', '0')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.call(wrap, node.width);
const bbox = txt.node().getBBox();
const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
node.width = bbox.width + 2 * node.padding;
if (node.icon) {
if (node.type === db.nodeType.CIRCLE) {
node.height += 50;
const orgWidth = node.width;
node.width += 50;
// node.width = Math.max(orgWidth, 100);
const widthDiff = Math.abs(node.width - orgWidth);
const icon = nodeElem
.append('foreignObject')
.attr('height', '50px')
.attr('width', node.width)
.attr('style', 'text-align: center;');
// .attr('x', 0)
// .attr('y', 0)
// .attr('class', 'node-icon ' + node.icon);
icon
.append('div')
.attr('class', 'icon-container')
.append('i')
.attr('class', 'node-icon-' + section + ' ' + node.icon);
textElem.attr(
'transform',
'translate(' + node.width / 2 + ', ' + (node.height / 2 - 1.5 * node.padding) + ')'
);
} else {
node.width += 50;
const orgHeight = node.height;
node.height = Math.max(orgHeight, 60);
const heightDiff = Math.abs(node.height - orgHeight);
const icon = nodeElem
.append('foreignObject')
.attr('width', '60px')
.attr('height', node.height)
.attr('style', 'text-align: center;margin-top:' + heightDiff / 2 + 'px;');
// .attr('x', 0)
// .attr('y', 0)
// .attr('class', 'node-icon ' + node.icon);
icon
.append('div')
.attr('class', 'icon-container')
.append('i')
.attr('class', 'node-icon-' + section + ' ' + node.icon);
textElem.attr(
'transform',
'translate(' + (25 + node.width / 2) + ', ' + (heightDiff / 2 + node.padding / 2) + ')'
);
}
} else {
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
}
switch (node.type) {
case db.nodeType.DEFAULT:
defaultBkg(bkgElem, node, section, conf);
break;
case db.nodeType.ROUNDED_RECT:
roundedRectBkg(bkgElem, node, section, conf);
break;
case db.nodeType.RECT:
rectBkg(bkgElem, node, section, conf);
break;
case db.nodeType.CIRCLE:
bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
circleBkg(bkgElem, node, section, conf);
break;
case db.nodeType.CLOUD:
// bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
cloudBkg(bkgElem, node, section, conf);
break;
case db.nodeType.BANG:
// bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
bangBkg(bkgElem, node, section, conf);
break;
default:
// defaultBkg(bkgElem, node, section, conf);
}
// Position the node to its coordinate
if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') {
nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
}
db.setElementForId(node.id, nodeElem);
return node.height;
};
export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, section, conf) {
// edgesElem
// .append('line')
// .attr('x1', parent.x + parent.width / 2)
// .attr('y1', parent.y + parent.height / 2)
// .attr('x2', mindmap.x + mindmap.width / 2)
// .attr('y2', mindmap.y + mindmap.height / 2)
// .attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth);
//<path d="M100,250 Q250,100 400,250 T700,250" />
const sx = parent.x + parent.width / 2;
const sy = parent.y + parent.height / 2;
const ex = mindmap.x + mindmap.width / 2;
const ey = mindmap.y + mindmap.height / 2;
const mx = ex > sx ? sx + Math.abs(sx - ex) / 2 : sx - Math.abs(sx - ex) / 2;
const my = ey > sy ? sy + Math.abs(sy - ey) / 2 : sy - Math.abs(sy - ey) / 2;
const qx = ex > sx ? Math.abs(sx - mx) / 2 + sx : -Math.abs(sx - mx) / 2 + sx;
const qy = ey > sy ? Math.abs(sy - my) / 2 + sy : -Math.abs(sy - my) / 2 + sy;
edgesElem
.append('path')
.attr(
'd',
parent.direction === 'TB' || parent.direction === 'BT'
? `M${sx},${sy} Q${sx},${qy} ${mx},${my} T${ex},${ey}`
: `M${sx},${sy} Q${qx},${sy} ${mx},${my} T${ex},${ey}`
)
.attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth);
};
export const positionNode = function (node, conf) {
const nodeElem = db.getElementById(node.id);
const x = node.x || 0;
const y = node.y || 0;
// Position the node to its coordinate
nodeElem.attr('transform', 'translate(' + x + ',' + y + ')');
};
export default { drawNode, positionNode, drawEdge };

Some files were not shown because too many files have changed in this diff Show More