Merge pull request #2 from mermaid-js/develop

Develop
This commit is contained in:
Justin Greywolf 2019-11-19 12:49:39 -08:00 committed by GitHub
commit 3469cfca2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 10304 additions and 4939 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
**/*.spec.js

View File

@ -1,7 +1,8 @@
{
"env": {
"browser": true,
"es6": true
"es6": true,
"node": true
},
"parserOptions": {
"ecmaFeatures": {
@ -10,7 +11,7 @@
},
"sourceType": "module"
},
"extends": ["prettier"],
"extends": ["prettier", "eslint:recommended"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": ["error"]

15
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,15 @@
---
name: Question
about: Get some help from the community.
title: ''
labels: 'Help wanted!, Type: Other'
assignees: ''
---
## Help us help you!
You want an answer. Here are some ways to get it quicker:
* Use a clear and concise title.
* Try to pose a clear and concise question.
* Include as much, or as little, code as necessary.
* Don't be shy to give us some screenshots, if it helps!

50
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x]
steps:
- uses: actions/checkout@v1
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm i yarn --global
- name: Cache Node Modules
uses: actions/cache@v1
with:
path: .cache
key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }}
- name: Install Packages
run: |
yarn config set cache-folder $GITHUB_WORKSPACE/.cache/yarn
yarn install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: ../../.cache/Cypress
- name: Run Build
run: yarn build
- name: Run Unit Tests
run: |
yarn test --coverage
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls
env:
COVERALLS_SERVICE_NAME: ${{ secrets.COVERALLS_SERVICE_NAME }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
- name: Run E2E Tests
run: yarn e2e
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
CYPRESS_CACHE_FOLDER: .cache/Cypress

13
.github/workflows/lock-closed-issue.yml vendored Normal file
View File

@ -0,0 +1,13 @@
name: Lock closed issue
on:
issues:
types: [closed]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: Dunning-Kruger/lock-issues@v1
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@ -0,0 +1,40 @@
name: Publish release preview package
on:
push:
branches:
- 'release/**'
jobs:
publish:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x]
steps:
- uses: actions/checkout@v1
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm i yarn --global
- name: Install Json
run: npm i json --global
- name: Install Packages
run: yarn install
- name: Publish
run: |
PREVIEW_VERSION=$(git rev-list --count --first-parent HEAD)
VERSION=$(echo ${{github.ref}} | tail -c +20)-preview.$PREVIEW_VERSION
echo $VERSION
npm version --no-git-tag-version --allow-same-version $VERSION
npm set //npm.pkg.github.com/:_authToken ${{ secrets.GITHUB_TOKEN }}
npm set registry https://npm.pkg.github.com/mermaid-js
json -I -f package.json -e 'this.name="@mermaid-js/mermaid"' # Package name needs to be set to a scoped one because GitHub registry requires this
json -I -f package.json -e 'this.repository="git://github.com/mermaid-js/mermaid"' # Repo url needs to have a specific format too
npm publish

View File

@ -0,0 +1,13 @@
name: Unlock reopened issue
on:
issues:
types: [reopened]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: Dunning-Kruger/unlock-issues@v1
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

3
.gitignore vendored
View File

@ -2,6 +2,7 @@
node_modules/
coverage/
.idea/
dist/*.js
dist/*.map
@ -9,3 +10,5 @@ dist/*.map
yarn-error.log
.npmrc
token
package-lock.json

View File

@ -4,5 +4,5 @@
"typescript.validate.enable": false,
"javascript.validate.enable": false,
"editor.formatOnSave": false,
"standard.enable": true
"editor.snippetSuggestions": "top"
}

98
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,98 @@
# Contributing
So you want to help? That's great!
![Image of happy people jumping with excitement](https://media.giphy.com/media/BlVnrxJgTGsUw/giphy.gif)
Here are a few things to know to get you started on the right path.
## Committing code
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 dependecies, 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.
One example:
`feature/945_state_diagrams`
Another:
`bug/123_nasty_bug_branch`
## Committing documentation
Less strict here, it is ok to commit directly in the develop branch if you are a collaborator.
## Branching
Going forward we will use a git flow inspired approach to branching. So development is done in develop, to do the development in the develop.
Once development is done we branch a release branch from develop for testing.
Once the release happens we merge the release branch to master and kill the release branch.
This means... **branch off your pull request from develop**
## Content of a pull request
A new feature has been born. Great! But without the steps below it might just ... fade away ...
### **Add unit tests for the parsing part**
This is important so that, if someone else does a change to the grammar that does not know about this great feature, gets notified early on when that change breaks the parser. Another important aspect is that without proper parsing tests refactoring is pretty much impossible.
### **Add e2e tests**
This tests the rendering and visual apearance of the diagram. This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks!
To start working with the e2e tests, run `yarn dev` to start the dev server, after that 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. This function takes a diagram in text form, the mermaid options and renders that diagram in cypress.
When running in ci it will take a snapshot of the rendered diagram and compare it with the snapshot from last build and flag for review it if it differs.
This is what a rendering test looks like:
```
it('should render forks and joins', () => {
imgSnapshotTest(
`
stateDiagram
state fork_state <<fork>>
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state <<join>>
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
```
### **Add documentation for it**
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 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.
The changes in master is reflected in http://mermaid-js.github.io/mermaid/ once released the updates are commited to https://mermaid-js.github.io/#/
## Last words
Don't get daunted if it is hard in the beginning. We have a great community with only encouraging words. So if you get stuck, ask for help and hints in the slack forum. If you want to show off something good, show it off there.
[Join our slack community if you want closer contact!](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
![Image of superhero wishing you good luck](https://media.giphy.com/media/l49JHz7kJvl6MCj3G/giphy.gif)

351
README.md
View File

@ -1,94 +1,89 @@
[![Build Status](https://travis-ci.org/knsv/mermaid.svg?branch=master)](https://travis-ci.org/knsv/mermaid)
[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master)
[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid)
<!-- <Remove this in the future> -->
| :mega: :mega: :mega: |
| :----: |
| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.|
<!-- </Remove this in the future> -->
# mermaid
# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid)
<!-- <Main description> -->
__Generate diagrams, charts, graphs or flows from markdown-like text via javascript.__
See our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play in our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage).
<!-- </Main description> -->
## Special note regarding version 8.2
:trophy: _"The most exciting use of technology"_ - [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees)
In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams.
* **`strict`**: (default) tags in text are encoded, click functionality is disabled
* `loose`: tags in text are allowed, click functionality is enabled
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
If your application is taking responsibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed.
```javascript
mermaidAPI.initialize({
securityLevel: 'loose'
});
```
**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/knsv/mermaid/issues/866)**
![banner](./img/header.png)
Generation of diagrams and flowcharts from text in a similar manner as markdown.
Ever wanted to simplify documentation and avoid heavy tools like Visio when explaining your code?
This is why mermaid was born, a simple markdown-like script language for generating charts from text via javascript.
**Mermaid was nominated and won the JS Open Source Awards (2019) in the category _The most exciting use of technology_!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.**
### Flowchart
```
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
![Flowchart](./img/flow.png)
### Sequence diagram
```
<table>
<!-- <Flowchart> -->
<tr><td colspan=2 align="center">
<b>Flow</b></br>
[<a href="http://mermaid-js.github.io/mermaid/#/flowchart">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0hhcmRdIC0tPnxUZXh0fCBCKFJvdW5kKVxuICAgIEIgLS0-IEN7RGVjaXNpb259XG4gICAgQyAtLT58T25lfCBEW1Jlc3VsdCAxXVxuICAgIEMgLS0-fFR3b3wgRVtSZXN1bHQgMl0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9fQ">live editor</a>]
</td></tr>
<tr>
<td><pre>
graph TD
A[Hard] -->|Text| B(Round)
B --> C{Decision}
C -->|One| D[Result 1]
C -->|Two| E[Result 2]
</pre></td>
<td align="center">
<img src="./img/gray-flow.png" />
</td>
</tr>
<!-- </Flowchart> -->
<!-- <Sequence> -->
<tr><td colspan=2 align="center">
<b>Sequence</b><br />
[<a href="http://mermaid-js.github.io/mermaid/#/sequenceDiagram">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG5BbGljZS0-PkpvaG46IEhlbGxvIEpvaG4sIGhvdyBhcmUgeW91P1xubG9vcCBIZWFsdGhjaGVja1xuICAgIEpvaG4tPj5Kb2huOiBGaWdodCBhZ2FpbnN0IGh5cG9jaG9uZHJpYVxuZW5kXG5Ob3RlIHJpZ2h0IG9mIEpvaG46IFJhdGlvbmFsIHRob3VnaHRzIVxuSm9obi0tPj5BbGljZTogR3JlYXQhXG5Kb2huLT4-Qm9iOiBIb3cgYWJvdXQgeW91P1xuQm9iLS0-PkpvaG46IEpvbGx5IGdvb2QhIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0">live editor</a>]
</td></tr>
<tr>
<td><pre>
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts <br/>prevail!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
![Sequence diagram](./img/sequence.png)
### Gantt diagram
```
Alice->>John: Hello John, how are you?
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
</pre></td>
<td align="center">
<img src="./img/gray-sequence.png" />
</td>
</tr>
<!-- </Sequence> -->
<!-- <Gantt> -->
<tr><td colspan=2 align="center">
<b>Gantt</b><br />
[<a href="http://mermaid-js.github.io/mermaid/#/gantt">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ2FudHRcbnNlY3Rpb24gU2VjdGlvblxuQ29tcGxldGVkIDpkb25lLCAgICBkZXMxLCAyMDE0LTAxLTA2LDIwMTQtMDEtMDhcbkFjdGl2ZSAgICAgICAgOmFjdGl2ZSwgIGRlczIsIDIwMTQtMDEtMDcsIDNkXG5QYXJhbGxlbCAxICAgOiAgICAgICAgIGRlczMsIGFmdGVyIGRlczEsIDFkXG5QYXJhbGxlbCAyICAgOiAgICAgICAgIGRlczQsIGFmdGVyIGRlczEsIDFkXG5QYXJhbGxlbCAzICAgOiAgICAgICAgIGRlczUsIGFmdGVyIGRlczMsIDFkXG5QYXJhbGxlbCA0ICAgOiAgICAgICAgIGRlczYsIGFmdGVyIGRlczQsIDFkIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0">live editor</a>]
</td></tr>
<tr>
<td><pre>
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
```
![Gantt diagram](./img/gantt.png)
### Class diagram - :exclamation: experimental
```
section Section
Completed :done, des1, 2014-01-06,2014-01-08
Active :active, des2, 2014-01-07, 3d
Parallel 1 : des3, after des1, 1d
Parallel 2 : des4, after des1, 1d
Parallel 3 : des5, after des3, 1d
Parallel 4 : des6, after des4, 1d
</pre></td>
<td align="center">
<img src="./img/gray-gantt.png" />
</td>
</tr>
<!-- </Gantt> -->
<!-- <Class> -->
<tr><td colspan=2 align="center">
<b>Class</b><br />
[<a href="http://mermaid-js.github.io/mermaid/#/classDiagram">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5DbGFzczAxIDx8LS0gQXZlcnlMb25nQ2xhc3MgOiBDb29sXG48PGludGVyZmFjZT4-IENsYXNzMDFcbkNsYXNzMDkgLS0-IEMyIDogV2hlcmUgYW0gaT9cbkNsYXNzMDkgLS0qIEMzXG5DbGFzczA5IC0tfD4gQ2xhc3MwN1xuQ2xhc3MwNyA6IGVxdWFscygpXG5DbGFzczA3IDogT2JqZWN0W10gZWxlbWVudERhdGFcbkNsYXNzMDEgOiBzaXplKClcbkNsYXNzMDEgOiBpbnQgY2hpbXBcbkNsYXNzMDEgOiBpbnQgZ29yaWxsYVxuY2xhc3MgQ2xhc3MxMCB7XG4gID4-c2VydmljZT4-XG4gIGludCBpZFxuICBzaXplKClcbn0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9fQ">live editor</a>]
</td></tr>
<tr>
<td><pre>
classDiagram
Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class01 &lt;|-- AveryLongClass : Cool
&lt;&lt;interface>> Class01
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
@ -97,128 +92,84 @@ Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
```
![Class diagram](./img/class.png)
### Git graph - :exclamation: experimental
```
gitGraph:
options
{
"nodeSpacing": 150,
"nodeRadius": 10
class Class10 {
&lt;&lt;service>>
int id
size()
}
end
commit
branch newbranch
checkout newbranch
commit
commit
checkout master
commit
commit
merge newbranch
</pre></td>
<td align="center">
<img src="./img/gray-class.png" />
</td>
</tr>
<!-- </Class> -->
<!-- <State> -->
<tr><td colspan=2 align="center">
<b>State</b><br />
[<a href="http://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic3RhdGVEaWFncmFtXG4gICAgWypdIC0tPiBTdGlsbFxuICAgIFN0aWxsIC0tPiBbKl1cbiAgICBTdGlsbCAtLT4gTW92aW5nXG4gICAgTW92aW5nIC0tPiBTdGlsbFxuICAgIE1vdmluZyAtLT4gQ3Jhc2hcbiAgICBDcmFzaCAtLT4gWypdIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0">live editor</a>]
</td></tr>
<tr>
<td><pre>
stateDiagram
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
</pre></td>
<td align="center">
<img src="./img/gray-state.png" />
</td>
</tr>
<!-- </State> -->
<!-- <Pie> -->
<tr><td colspan=2 align="center">
<b>Pie</b><br />
[<a href="http://mermaid-js.github.io/mermaid/#/pie">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoicGllXG5cIkRvZ3NcIiA6IDQyLjk2XG5cIkNhdHNcIiA6IDUwLjA1XG5cIlJhdHNcIiA6IDEwLjAxIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0">live editor</a>]
</td></tr>
<tr>
<td><pre>
pie
"Dogs" : 386
"Cats" : 85
"Rats" : 15
</pre></td>
<td align="center">
<img src="./img/gray-pie.png" />
</td>
</tr>
<!-- </Pie> -->
<!-- <Git> -->
<tr><td colspan=2 align="center">
<b>Git</b><br />
[experimental - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ2l0R3JhcGg6XG5vcHRpb25zXG57XG4gICAgXCJub2RlU3BhY2luZ1wiOiAxNTAsXG4gICAgXCJub2RlUmFkaXVzXCI6IDEwXG59XG5lbmRcbmNvbW1pdFxuYnJhbmNoIG5ld2JyYW5jaFxuY2hlY2tvdXQgbmV3YnJhbmNoXG5jb21taXRcbmNvbW1pdFxuY2hlY2tvdXQgbWFzdGVyXG5jb21taXRcbmNvbW1pdFxubWVyZ2UgbmV3YnJhbmNoXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9fQ">live editor</a>]
</td></tr>
<tr>
<td colspan="2" align="center"><i>Coming soon!</i></td>
</tr>
<!-- </Git> -->
</table>
```
## Related projects
![Git graph](./img/git.png)
- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli)
- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors)
## Installation
Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out.
### CDN
Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md)
https://unpkg.com/mermaid@<version>/dist/
# Appreciation
A quick note from Knut Sveidqvist:
>*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!*
>*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.*
>*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.*
>
>*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!*
Replace `<version>` with expected version number.
Example: https://unpkg.com/mermaid@7.1.0/dist/
### Node.js
yarn add mermaid
## Documentation
https://mermaidjs.github.io
## Sibling projects
- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli)
- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor)
- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo)
- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo)
# Request for assistance
Things are piling up and I have hard time keeping up. To remedy this
it would be great if we could form a core team of developers to cooperate
with the future development mermaid.
As part of this team you would get write access to the repository and would
represent the project when answering questions and issues.
Together we could continue the work with things like:
* adding more types of diagrams like mindmaps, ert diagrams etc
* improving existing diagrams
Don't hesitate to contact me if you want to get involved.
# For contributors
## Setup
yarn install
## Build
yarn build:watch
## Lint
yarn lint
We use [JavaScript Standard Style](https://github.com/feross/standard).
We recommend you installing [editor plugins](https://github.com/feross/standard#are-there-text-editor-plugins) so you can get real time lint result.
## Test
yarn test
Manual test in browser:
open dist/index.html
## Release
For those who have the permission to do so:
Update version number in `package.json`.
npm publish
Command above generates files into the `dist` folder and publishes them to npmjs.org.
# Credits
Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!
Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.
---
*Mermaid was created by Knut Sveidqvist for easier documentation.*
*[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017.*
Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors).

View File

@ -16,7 +16,7 @@ describe('Interaction', () => {
cy.viewport(1440, 1024);
cy.visit(url);
cy.get('body')
.find('g#s1Function')
.find('g#mermaid-dom-id-1Function')
.click();
cy.get('.created-by-click').should('have.text', 'Clicked By Flow');
@ -38,7 +38,7 @@ describe('Interaction', () => {
cy.viewport(1440, 1024);
cy.visit(url);
cy.get('body')
.find('g#s2URL')
.find('g#mermaid-dom-id-2URL')
.click();
cy.location().should(location => {
@ -108,7 +108,7 @@ describe('Interaction', () => {
cy.viewport(1440, 1024);
cy.visit(url);
cy.get('body')
.find('g#s1Function')
.find('g#mermaid-dom-id-1Function')
.click();
cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow');
@ -130,7 +130,7 @@ describe('Interaction', () => {
cy.viewport(1440, 1024);
cy.visit(url);
cy.get('body')
.find('g#s2URL')
.find('g#mermaid-dom-id-2URL')
.click();
cy.location().should(location => {
@ -200,7 +200,7 @@ describe('Interaction', () => {
cy.viewport(1440, 1024);
cy.visit(url);
cy.get('body')
.find('g#s1Function')
.find('g#mermaid-dom-id-1Function')
.click();
cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow');

View File

@ -0,0 +1,16 @@
/* eslint-env jest */
describe('Rerendering', () => {
it('should be able to render and rerender a graph via API', () => {
const url = 'http://localhost:9000/rerender.html';
cy.viewport(1440, 1024);
cy.visit(url);
cy.get('#graph #A').should('have.text', 'XMas');
cy.get('body')
.find('#rerender')
.click({ force: true });
cy.get('#graph #A').should('have.text', 'Saturday');
});
});

View File

@ -1,12 +1,13 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../../helpers/util';
describe('Sequencediagram', () => {
it('should render a simple class diagrams', () => {
describe('Class diagram', () => {
it('should render a simple class diagram', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
@ -19,9 +20,94 @@ describe('Sequencediagram', () => {
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
`,
{}
);
cy.get('svg');
});
it('should render a simple class diagrams with cardinality', () => {
imgSnapshotTest(
`
classDiagram
Class01 "1" <|--|> "*" AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 "1" <--> "*" C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
`,
{}
);
cy.get('svg');
});
it('should render multiple class diagrams', () => {
imgSnapshotTest(
[
`
classDiagram
Class01 "1" <|--|> "*" AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 "1" <--> "*" C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
`,
`
classDiagram
Class01 "1" <|--|> "*" AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 "1" <--> "*" C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
`,
],
{}
);
cy.get('svg');
});
});

View File

@ -0,0 +1,20 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../../helpers/util';
describe('State diagram', () => {
it('should render a flowchart full of circles', () => {
imgSnapshotTest(
`
stateDiagram
State1: The state with a note
note right of State1
Important information! You\ncan write
notes with multiple lines...
Here is another line...
And another line...
end note
`,
{}
);
});
});

View File

@ -2,7 +2,7 @@
import { imgSnapshotTest } from '../../helpers/util';
describe('Flowcart', () => {
it('should render a simple flowchart', () => {
it('1: should render a simple flowchart no htmlLabels', () => {
imgSnapshotTest(
`graph TD
A[Christmas] -->|Get money| B(Go shopping)
@ -11,10 +11,22 @@ describe('Flowcart', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{}
{ flowchart: { htmlLabels: false } }
);
});
it('should render a simple flowchart with line breaks', () => {
it('2: should render a simple flowchart with htmlLabels', () => {
imgSnapshotTest(
`graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ flowchart: { htmlLabels: true } }
);
});
it('3: should render a simple flowchart with line breaks', () => {
imgSnapshotTest(
`
graph TD
@ -28,7 +40,7 @@ describe('Flowcart', () => {
);
});
it('should render a simple flowchart with trapezoid and inverse trapezoid vertex options.', () => {
it('4: should render a simple flowchart with trapezoid and inverse trapezoid vertex options.', () => {
imgSnapshotTest(
`
graph TD
@ -43,7 +55,7 @@ describe('Flowcart', () => {
);
});
it('should style nodes via a class.', () => {
it('4: should style nodes via a class.', () => {
imgSnapshotTest(
`
graph TD
@ -59,7 +71,7 @@ describe('Flowcart', () => {
);
});
it('should render a flowchart full of circles', () => {
it('5: should render a flowchart full of circles', () => {
imgSnapshotTest(
`
graph LR
@ -87,7 +99,7 @@ describe('Flowcart', () => {
{}
);
});
it('should render a flowchart full of icons', () => {
it('6: should render a flowchart full of icons', () => {
imgSnapshotTest(
`
graph TD
@ -158,7 +170,7 @@ describe('Flowcart', () => {
);
});
it('should render labels with numbers at the start', () => {
it('7: should render labels with numbers at the start', () => {
imgSnapshotTest(
`
graph TB;subgraph "number as labels";1;end;
@ -166,7 +178,7 @@ describe('Flowcart', () => {
{}
);
});
it('should render subgraphs', () => {
it('8: should render subgraphs', () => {
imgSnapshotTest(
`
graph TB
@ -178,7 +190,7 @@ describe('Flowcart', () => {
);
});
it('should render subgraphs with a title startign with a digit', () => {
it('9: should render subgraphs with a title startign with a digit', () => {
imgSnapshotTest(
`
graph TB
@ -190,7 +202,7 @@ describe('Flowcart', () => {
);
});
it('should render styled subgraphs', () => {
it('10: should render styled subgraphs', () => {
imgSnapshotTest(
`
graph TB
@ -225,7 +237,7 @@ describe('Flowcart', () => {
);
});
it('should render a flowchart with ling sames and class definitoins', () => {
it('11: should render a flowchart with ling sames and class definitoins', () => {
imgSnapshotTest(
`graph LR
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
@ -327,7 +339,7 @@ describe('Flowcart', () => {
);
});
it('should render color of styled nodes', () => {
it('12: should render color of styled nodes', () => {
imgSnapshotTest(
`
graph LR
@ -344,4 +356,25 @@ describe('Flowcart', () => {
}
);
});
it('13: should render hexagons', () => {
imgSnapshotTest(
`
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{{Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?}}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
click A "index.html#link-clicked" "link test"
click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;
`,
{
listUrl: false,
listId: 'color styling',
logLevel: 0
}
);
});
});

View File

@ -19,7 +19,7 @@ describe('Sequencediagram', () => {
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d

View File

@ -6,12 +6,6 @@ describe('Sequencediagram', () => {
imgSnapshotTest(
`
gitGraph:
options
{
"nodeSpacing": 150,
"nodeRadius": 10
}
end
commit
branch newbranch
checkout newbranch
@ -20,9 +14,8 @@ describe('Sequencediagram', () => {
checkout master
commit
commit
merge newbranch
`,
{}
merge newbranch`,
{ logLevel: 0 }
);
});
});

View File

@ -12,5 +12,29 @@ describe('Pie Chart', () => {
`,
{}
);
cy.get('svg');
});
it('should render a simple pie diagram with long labels', () => {
imgSnapshotTest(
`
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
`,
{}
);
cy.get('svg');
});
it('should render a simple pie diagram with capital letters for labels', () => {
imgSnapshotTest(
`
pie title What Voldemort doesn't have?
"FRIENDS" : 2
"FAMILY" : 3
"NOSE" : 45
`,
{}
);
cy.get('svg');
});
});

View File

@ -0,0 +1,279 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../../helpers/util';
describe('State diagram', () => {
it('should render a simple state diagrams', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> State1
State1 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a long descriptions instead of id when available', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> S1
state "Some long name" as S1
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a long descriptions with additional descriptions', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> S1
state "Some long name" as S1: The description
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a single state with short descr', () => {
imgSnapshotTest(
`
stateDiagram
state "A long long name" as long1
state "A" as longlonglongid
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a transition descrions with new lines', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> S1
S1 --> S2: long line using<br/>should work
S1 --> S3: long line using <br>should work
S1 --> S4: long line using \\nshould work
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a state with a note', () => {
imgSnapshotTest(
`
stateDiagram
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a state with on the left side when so specified', () => {
imgSnapshotTest(
`
stateDiagram
State1: The state with a note with minus - and plus + in it
note left of State1
Important information! You can write
notes with . and in them.
end note
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a state with a note together with another state', () => {
imgSnapshotTest(
`
stateDiagram
State1: The state with a note +,-
note right of State1
Important information! You can write +,-
notes.
end note
State1 --> State2 : With +,-
note left of State2 : This is the note +,-<br/>
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a states with descriptions including multi-line descriptions', () => {
imgSnapshotTest(
`
stateDiagram
State1: This a a single line description
State2: This a a multi line description
State2: here comes the multi part
[*] --> State1
State1 --> State2
State2 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a simple state diagrams', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> State1
State1 --> State2
State1 --> State3
State1 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render a simple state diagrams with labels', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> State1
State1 --> State2 : Transition 1
State1 --> State3 : Transition 2
State1 --> State4 : Transition 3
State1 --> State5 : Transition 4
State2 --> State3 : Transition 5
State1 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render state descriptions', () => {
imgSnapshotTest(
`
stateDiagram
state "Long state description" as XState1
state "Another Long state description" as XState2
XState2 : New line
XState1 --> XState2
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render composit states', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> NotShooting: Pacifist
NotShooting --> A
NotShooting --> B
NotShooting --> C
state NotShooting {
[*] --> Idle: Yet another long long öong öong öong label
Idle --> Configuring : EvConfig
Configuring --> Idle : EvConfig EvConfig EvConfig EvConfig EvConfig
}
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render multiple composit states', () => {
imgSnapshotTest(
`
stateDiagram
[*]-->TV
state TV {
[*] --> Off: Off to start with
On --> Off : Turn off
Off --> On : Turn on
}
TV--> Console
state Console {
[*] --> Off2: Off to start with
On2--> Off2 : Turn off
Off2 --> On2 : Turn on
On2-->Playing
state Playing {
Alive --> Dead
Dead-->Alive
}
}
`,
{ logLevel: 0 }
);
});
it('should render forks in composit states', () => {
imgSnapshotTest(
`
stateDiagram
[*]-->TV
state TV {
state fork_state &lt;&lt;fork&gt;&gt;
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state &lt;&lt;join&gt;&gt;
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
}
`,
{ logLevel: 0 }
);
});
it('should render forks and joins', () => {
imgSnapshotTest(
`
stateDiagram
state fork_state &lt;&lt;fork&gt;&gt;
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state &lt;&lt;join&gt;&gt;
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render conurrency states', () => {
imgSnapshotTest(
`
stateDiagram
[*] --> Active
state Active {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
--
[*] --> CapsLockOff
CapsLockOff --> CapsLockOn : EvCapsLockPressed
CapsLockOn --> CapsLockOff : EvCapsLockPressed
--
[*] --> ScrollLockOff
ScrollLockOff --> ScrollLockOn : EvCapsLockPressed
ScrollLockOn --> ScrollLockOff : EvCapsLockPressed
}
`,
{ logLevel: 0 }
);
cy.get('svg');
});
});

View File

@ -1,11 +1,26 @@
<html>
<head>
<script src="/e2e.js"></script>
<link
href="https://fonts.googleapis.com/css?family=Montserrat&display=swap"
rel="stylesheet"
/>
<style></style>
<link href="https://fonts.googleapis.com/css?family=Mansalva&display=swap" rel="stylesheet" />
<style>
body {
/* font-family: 'Mansalva', cursive;
font-family: 'Mansalva', cursive; */
font-family: 'times';
}
/* .mermaid-main-font {
font-family: "trebuchet ms", verdana, arial;
font-family: var(--mermaid-font-family);
} */
/* :root {
--mermaid-font-family: '"trebuchet ms", verdana, arial';
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive;
--mermaid-font-family: '"Lucida Console", Monaco, monospace';
} */
svg {
border: 2px solid darkred;
}
</style>
</head>
<body>
<script src="./mermaid.js"></script>
@ -15,6 +30,10 @@
mermaid.initialize({
startOnLoad: false,
useMaxWidth: true,
// "themeCSS": ":root { --mermaid-font-family: \"trebuchet ms\", verdana, arial;}",
// fontFamily: '\"trebuchet ms\", verdana, arial;'
// fontFamily: '"Comic Sans MS", "Comic Sans", cursive'
fontFamily: '"Mansalva", cursive'
});
</script>
</body>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">
</head>
<body>
<div id="graph">
</div>
<script src="./mermaid.js"></script>
<script>
mermaid.init({ startOnLoad: false });
mermaid.mermaidAPI.initialize();
rerender('XMas');
function rerender(text) {
var graphText = `graph TD
A[${text}] -->|Get money| B(Go shopping)`
var graph = mermaid.mermaidAPI.render('id', graphText);
console.log('\x1b[35m%s\x1b[0m', '>> graph', graph)
document.getElementById('graph').innerHTML=graph;
}
</script>
<button id="rerender" onclick="rerender('Saturday')">Rerender</button>
</body>
</html>

View File

@ -1,50 +1,95 @@
import { Base64 } from 'js-base64'
import mermaid2 from '../../src/mermaid'
import { Base64 } from 'js-base64';
import mermaid2 from '../../src/mermaid';
/**
* ##contentLoaded
* Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and
* calls init for rendering the mermaid diagrams on the page.
*/
const contentLoaded = function () {
let pos = document.location.href.indexOf('?graph=')
const contentLoaded = function() {
let pos = document.location.href.indexOf('?graph=');
if (pos > 0) {
pos = pos + 7
const graphBase64 = document.location.href.substr(pos)
const graphObj = JSON.parse(Base64.decode(graphBase64))
pos = pos + 7;
const graphBase64 = document.location.href.substr(pos);
const graphObj = JSON.parse(Base64.decode(graphBase64));
// const graph = 'hello'
console.log(graphObj)
const div = document.createElement('div')
div.id = 'block'
div.className = 'mermaid'
div.innerHTML = graphObj.code
document.getElementsByTagName('body')[0].appendChild(div)
global.mermaid.initialize(graphObj.mermaid)
console.log(graphObj);
if (Array.isArray(graphObj.code)) {
const numCodes = graphObj.code.length;
for (let i = 0; i < numCodes; i++) {
const div = document.createElement('div');
div.id = 'block' + i;
div.className = 'mermaid';
div.innerHTML = graphObj.code[i];
document.getElementsByTagName('body')[0].appendChild(div);
}
} else {
const div = document.createElement('div');
div.id = 'block';
div.className = 'mermaid';
div.innerHTML = graphObj.code;
document.getElementsByTagName('body')[0].appendChild(div);
}
global.mermaid.initialize(graphObj.mermaid);
// console.log('graphObj.mermaid', graphObj.mermaid)
global.mermaid.init()
global.mermaid.init();
}
}
const contentLoadedApi = function () {
let pos = document.location.href.indexOf('?graph=')
};
const contentLoadedApi = function() {
let pos = document.location.href.indexOf('?graph=');
if (pos > 0) {
pos = pos + 7
const graphBase64 = document.location.href.substr(pos)
const graphObj = JSON.parse(Base64.decode(graphBase64))
pos = pos + 7;
const graphBase64 = document.location.href.substr(pos);
const graphObj = JSON.parse(Base64.decode(graphBase64));
// const graph = 'hello'
const div = document.createElement('div')
div.id = 'block'
div.className = 'mermaid'
// div.innerHTML = graphObj.code
document.getElementsByTagName('body')[0].appendChild(div)
global.mermaid.initialize(graphObj.mermaid)
if (Array.isArray(graphObj.code)) {
const numCodes = graphObj.code.length;
const divs = [];
let div;
for (let i = 0; i < numCodes; i++) {
div = document.createElement('div');
div.id = 'block' + i;
div.className = 'mermaid';
// div.innerHTML = graphObj.code
document.getElementsByTagName('body')[0].appendChild(div);
divs[i] = div;
}
mermaid2.render('newid', graphObj.code, (svgCode, bindFunctions) => {
div.innerHTML = svgCode
global.mermaid.initialize(graphObj.mermaid);
bindFunctions(div)
}, div)
for (let i = 0; i < numCodes; i++) {
mermaid2.render(
'newid' + i,
graphObj.code[i],
(svgCode, bindFunctions) => {
div.innerHTML = svgCode;
bindFunctions(div);
},
divs[i]
);
}
} else {
const div = document.createElement('div');
div.id = 'block';
div.className = 'mermaid';
// div.innerHTML = graphObj.code
document.getElementsByTagName('body')[0].appendChild(div);
global.mermaid.initialize(graphObj.mermaid);
mermaid2.render(
'newid',
graphObj.code,
(svgCode, bindFunctions) => {
div.innerHTML = svgCode;
if (bindFunctions) bindFunctions(div);
},
div
);
}
}
}
};
if (typeof document !== 'undefined') {
/*!
@ -52,15 +97,15 @@ if (typeof document !== 'undefined') {
*/
window.addEventListener(
'load',
function () {
function() {
if (this.location.href.match('xss.html')) {
this.console.log('Using api')
contentLoadedApi()
this.console.log('Using api');
contentLoadedApi();
} else {
this.console.log('Not using api')
contentLoaded()
this.console.log('Not using api');
contentLoaded();
}
},
false
)
);
}

47
dist/index.html vendored
View File

@ -291,7 +291,7 @@ graph TB
<div class="mermaid">
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
B --> C{{Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?}}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
@ -399,6 +399,7 @@ merge newbranch
<div class="mermaid">
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
@ -411,7 +412,38 @@ Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
</div>
<div class="mermaid">
stateDiagram
State1
</div>
<hr>
<div class="mermaid">
stateDiagram
[*] --> First
state First {
[*] --> second
second --> [*]
}
</div>
<div class="mermaid">
stateDiagram
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
State1 --> State2
note left of State2 : This is the note to the left.
</div>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
@ -439,5 +471,18 @@ Class08 <--> C2: Cool label
}, 100)
}
</script>
<script>
const testLineEndings = (test, input) => {
try {
mermaid.render(test, input, () => {});
} catch (err) {
console.error("Error in %s:\n\n%s", test, err);
}
};
testLineEndings("CR", "graph LR\rsubgraph CR\rA --> B\rend");
testLineEndings("LF", "graph LR\nsubgraph LF\nA --> B\nend");
testLineEndings("CRLF", "graph LR\r\nsubgraph CRLF\r\nA --> B\r\nend");
</script>
</body>
</html>

View File

@ -4,12 +4,20 @@
# mermaid
## New diagrams in 8.4
With version 8.4 class diagrams has got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram
type, state diagrams.
![Image show the two new diagram types](./img/new-diagrams.png)
## Special note regarding version 8.2
In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced wich sets the level of trust to be used on the parsed diagrams.
* **true**: (default) tags in text are encoded, click functionality is disabled
* false: tags in text are allowed, click functionality is enabledClosed issues:
* false: tags in text are allowed, click functionality is enabledClosed issues:
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
@ -190,8 +198,9 @@ Don't hesitate to contact me if you want to get involved.
yarn lint
We use [JavaScript Standard Style](https://github.com/feross/standard).
We recommend you installing [editor plugins](https://github.com/feross/standard#are-there-text-editor-plugins) so you can get real time lint result.
We use [eslint](https://eslint.org/).
We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result.
## Test

View File

@ -10,6 +10,8 @@
- [Flowchart](flowchart.md)
- [Sequence diagram](sequenceDiagram.md)
- [Class Diagram](classDiagram.md)
- [State Diagram](stateDiagram.md)
- [Gantt](gantt.md)
- [Pie Chart](pie.md)
- Guide

444
docs/classDiagram.md Normal file
View File

@ -0,0 +1,444 @@
# Class diagrams
> "In software engineering, a class diagram in the Unified Modeling Language (UML) is a type of static structure diagram that describes the structure of a system by showing the system's classes, their attributes, operations (or methods), and the relationships among objects."
Wikipedia
The class diagram is the main building block of object-oriented modeling. It is used for general conceptual modeling of the structure of the application, and for detailed modeling translating the models into programming code. Class diagrams can also be used for data modeling. The classes in a class diagram represent both the main elements, interactions in the application, and the classes to be programmed.
Mermaid can render class diagrams.
```
classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
```
```mermaid
classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
```
## Syntax
### Class
UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.
A single instance of a class in the diagram contains three compartments:
- The top compartment contains the name of the class. It is printed in bold and centered, and the first letter is capitalized. It may also contain optional annotation text describing the nature of the class.
- The middle compartment contains the attributes of the class. They are left-aligned and the first letter is lowercase.
- The bottom compartment contains the operations the class can execute. They are also left-aligned and the first letter is lowercase.
```
classDiagram
class BankAccount
BankAccount : +String owner
BankAccount : +Bigdecimal balance
BankAccount : +deposit(amount)
BankAccount : +withdrawl(amount)
```
```mermaid
classDiagram
class BankAccount
BankAccount : +String owner
BankAccount : +BigDecimal balance
BankAccount : +deposit(amount)
BankAccount : +withdrawl(amount)
```
## Define a class
There are two ways to define a class:
- Explicitly defining a class using keyword **class** like `class Animal`. This defines the Animal class
- Define two classes via a **relationship** between them `Vehicle <|-- Car`. This defines two classes Vehicle and Car along with their relationship.
```
classDiagram
class Animal
Vehicle <|-- Car
```
```mermaid
classDiagram
class Animal
Vehicle <|-- Car
```
Naming convention: a class name should be composed of alphanumeric (unicode allowed) and underscore characters.
## Defining Members of a class
UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.
#### Visibility
To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but is it optional:
- `+` Public
- `-` Private
- `#` Protected
- `~` Package
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The one with `()` are treated as functions/methods, and others as attributes.
There are two ways to define the members of a class, and regardless of the whichever syntax is used to define the members, the output will still be same. The two different ways are :
- Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example:
```
class BankAccount
BankAccount : +String owner
BankAccount : +BigDecimal balance
BankAccount : +deposit(amount)
BankAccount : +withdrawl(amount)
```
```mermaid
classDiagram
class BankAccount
BankAccount : +String owner
BankAccount : +BigDecimal balance
BankAccount : +deposit(amount)
BankAccount : +withdrawl(amount)
```
- Associate members of a class using **{}** brackets, where members are grouped within curly brackets. Suitable for defining multiple members at once. For example:
```
class BankAccount{
+String owner
+BigDecimal balance
+deposit(amount)
+withdrawl(amount)
}
```
```mermaid
classDiagram
class BankAccount{
+String owner
+BigDecimal balance
+deposit(amount)
+withdrawl(amount)
}```
## Defining Relationship
A relationship is a general term covering the specific types of logical connections found on class and object diagrams.
```
[classA][Arrow][ClassB]:LabelText
```
There are different types of relations defined for classes under UML which are currently supported:
Type | Description
--- | ---
<\|--| Inheritance
*-- | Composition
o-- | Aggregation
--> | Association
-- | Link
<!--- TODO ..> Dependency--->
```
classDiagram
classA <|-- classB
classC *-- classD
classE o-- classF
classG <-- classH
classI <.. classJ
classK .. classL
```
```mermaid
classDiagram
classA <|-- classB
classC *-- classD
classE o-- classF
classG <-- classH
classI <.. classJ
classK .. classL
```
We can use the arrowheads in opposite directions as well :
```
classDiagram
classA --|> classB
classC --* classD
classE --o classF
classG <--> classH
classI ..> classJ
classK .. classL
```
```mermaid
classDiagram
classA --|> classB
classC --* classD
classE --o classF
classG <--> classH
classI ..> classJ
classK .. classL
```
## Labels on Relations
It is possible to add a label text to a relation:
```
[classA][Arrow][ClassB]:LabelText
```
```
classDiagram
classA <|-- classB : implements
classC *-- classD : composition
classE o-- classF : association
```
```mermaid
classDiagram
classA <|-- classB : implements
classE o-- classF : association
```
## Cardinality / Multiplicity on relations
Multiplicity or cardinality in class diagrams indicates the number of instances of one class linked to one instance of the other class. For example, one company will have one or more employees, but each employee works for just one company.
Multiplicity notations are placed near the ends of an association.
The different cardinality options are :
- `0..1` Zero or one
- `1` Only 1
- `0..1` Zero or One
- `1..*` One or more
- `*` Many
- `n` n {where n>1}
- `0..n` zeor to n {where n>1}
- `1..n` one to n {where n>1}
Cardinality can be easily defined by placing cardinality text within qoutes `"` before(optional) and after(optional) a given arrow.
```
[classA] "cardinality1" [Arrow] "cardinality2" [ClassB]:LabelText
```
```
classDiagram
Customer "1" --> "*" Ticket
Student "1" --> "1..*" Course
Galaxy --> "many" Star : Contains
```
```mermaid
classDiagram
Customer "1" --> "*" Ticket
Student "1" --> "1..*" Course
Galaxy --> "many" Star : Contains
```
## Annotations on classes
It is possible to annotate classes with a specific marker text which is like meta-data for the class, giving a clear indication about its nature. Some common annotations examples could be:
- `<<Interface>>` To represent an Interface class
- `<<abstract>>` To represent an abstract class
- `<<Service>>` To represent a service class
- `<<enumeration>>` To represent an enum
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class and regardless of the syntax used output will be same. The two ways are :
- In a ***separate line*** after a class is defined. For example:
```
classDiagram
class Shape
<<interface>> Shape
```
```mermaid
classDiagram
class Shape
<<interface>> Shape
Shape : noOfVertices
Shape : draw()
```
- In a ***nested structure*** along with class definition. For example:
```
classDiagram
class Shape{
<<interface>>
noOfVertices
draw()
}
class Color{
<<enumeration>>
RED
BLUE
GREEN
WHITE
BLACK
}
```
```mermaid
classDiagram
class Shape{
<<interface>>
noOfVertices
draw()
}
class Color{
<<enumeration>>
RED
BLUE
GREEN
WHITE
BLACK
}
```
## Styling
Styling of the class diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss
### Styling Classes used
Class | Description
--- | ---
g.classGroup text | Styles for general class text
classGroup .title | Styles for general class title
g.classGroup rect | Styles for class diagram rectangle
g.classGroup line | Styles for class diagram line
.classLabel .box | Styles for class label box
.classLabel .label | Styles for class label text
composition | Styles for componsition arrow head and arrow line
aggregation | Styles for aggregation arrow head and arrow line(dashed or solid)
dependency | Styles for dependency arrow head and arrow line
### Sample stylesheet
```css
body {
background: white;
}
g.classGroup text {
fill: $nodeBorder;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 10px;
.title {
font-weight: bolder;
}
}
g.classGroup rect {
fill: $nodeBkg;
stroke: $nodeBorder;
}
g.classGroup line {
stroke: $nodeBorder;
stroke-width: 1;
}
.classLabel .box {
stroke: none;
stroke-width: 0;
fill: $nodeBkg;
opacity: 0.5;
}
.classLabel .label {
fill: $nodeBorder;
font-size: 10px;
}
.relation {
stroke: $nodeBorder;
stroke-width: 1;
fill: none;
}
@mixin composition {
fill: $nodeBorder;
stroke: $nodeBorder;
stroke-width: 1;
}
#compositionStart {
@include composition;
}
#compositionEnd {
@include composition;
}
@mixin aggregation {
fill: $nodeBkg;
stroke: $nodeBorder;
stroke-width: 1;
}
#aggregationStart {
@include aggregation;
}
#aggregationEnd {
@include aggregation;
}
#dependencyStart {
@include composition;
}
#dependencyEnd {
@include composition;
}
#extensionStart {
@include composition;
}
#extensionEnd {
@include composition;
}
```
## Configuration
`Coming soon`

View File

@ -1,3 +1,27 @@
## Basic Pie Chart
```
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
```
``` mermaid
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
```
```
pie title What Voldemort doesn't have?
"FRIENDS" : 2
"FAMILY" : 3
"NOSE" : 45
```
```mermaid
pie title What Voldemort doesn't have?
"FRIENDS" : 2
"FAMILY" : 3
"NOSE" : 45
```
## Basic sequence diagram
```

View File

@ -4,7 +4,7 @@
This statement declares a new graph and the direction of the graph layout.
This declares a graph oriented from top to bottom.
This declares a graph oriented from top to bottom (`TD` or `TB`).
```
graph TD
@ -15,7 +15,16 @@ graph TD
Start --> Stop
```
This declares a graph oriented from left to right.
This declares a graph oriented from left to right (`LR`).
```
graph LR
Start --> Stop
```
```mermaid
graph LR
Start --> Stop
```
Possible directions are:
@ -26,14 +35,6 @@ Possible directions are:
* TD - same as TB
```
graph LR
Start --> Stop
```
```mermaid
graph LR
Start --> Stop
```
## Nodes & shapes
@ -111,14 +112,55 @@ graph LR
id1{This is the text in the box}
```
### A hexagon node
```
graph LR
id1{{This is the text in the box}}
```
```mermaid
graph LR
id1{{This is the text in the box}}
```
### Parallelogram
```
graph TD
id1[/This is the text in the box/]
```
```mermaid
graph TD
id1[/This is the text in the box/]
```
### Parallelogram alt
```
graph TD
id1[\This is the text in the box\]
```
```mermaid
graph TD
id1[\This is the text in the box\]
```
### Trapezoid
```
graph TD
A[/Christmas\]
```
```mermaid
graph TD
A[/Christmas\]
```
### Trapezoid alt
```
graph TD
B[\Go shopping/]
```
```mermaid
graph TD
B[\Go shopping/]
@ -238,6 +280,19 @@ graph LR
A == text ==> B
```
### Chaining of links
It is possible declare many links in the same line as per below:
```
graph LR
A -- text --> B -- text2 --> C
```
```mermaid
graph LR
A -- text --> B -- text2 --> C
```
## Special characters that break syntax
It is possible to put text within quotes in order to render more troublesome characters. As in the example below:
@ -349,7 +404,7 @@ Beginners tip, a full example using interactive links in a html context:
click A callback "Tooltip"
click B "http://www.github.com" "This is a link"
</div>
<script>
var callback = function(){
alert('A callback was triggered');
@ -363,7 +418,7 @@ Beginners tip, a full example using interactive links in a html context:
},
securityLevel:'loose',
};
mermaid.initialize(config);
</script>
</body>

View File

@ -26,8 +26,6 @@ gantt
Task in sec :2014-01-12 , 12d
another task : 24d
```
## Syntax
```
@ -106,7 +104,11 @@ Tbd
### Date format
Tbd
The default date format is YYYY-MM-DD. You can define your ``dateFormat``. For example:
```
dateFormat YYYY MM DD
```
### Diagram definition

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/img/new-diagrams.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -7,8 +7,8 @@
<meta name="description" content="Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
<script src="//cdn.jsdelivr.net/npm/mermaid@8.3.1/dist/mermaid.min.js"></script>
<!-- <script src="//localhost:9000/mermaidq.js"></script> -->
<script src="//cdn.jsdelivr.net/npm/mermaid@8.4.1/dist/mermaid.min.js"></script>
<!-- <script src="//localhost:9000/mermaid.js"></script> -->
<style>
.markdown-section {
max-width: 1200px;

View File

@ -63,6 +63,10 @@ theme , the CSS style sheet
"themeCSS": ".node rect { fill: red; }"
</pre>
## fontFamily
**fontFamily** The font to be used for the rendered diagrams. Default value is \\"trebuchet ms\\", verdana, arial;
## logLevel
This option decides the amount of logging to be used.
@ -77,7 +81,7 @@ This option decides the amount of logging to be used.
Sets the level of trust to be used on the parsed diagrams.
- **strict**: (**default**) tags in text are encoded, click functionality is disabled
- **strict**: (**default**) tags in text are encoded, click functionality is disabeled
- **loose**: tags in text are allowed, click functionality is enabled
## startOnLoad

View File

@ -1,11 +1,12 @@
# Pie chart diagrams
> A pie chart (or a circle chart) is a circular statistical graphic, which is divided into slices to illustrate numerical proportion. In a pie chart, the arc length of each slice (and consequently its central angle and area), is proportional to the quantity it represents. While it is named for its resemblance to a pie which has been sliced, there are variations on the way it can be presented. The earliest known pie chart is generally credited to William Playfair's Statistical Breviary of 1801
-Wikipedia
Mermaid can render Pie Chart diagrams.
```
pie
pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15
@ -19,17 +20,34 @@ pie title Pets adopted by volunteers
## Syntax
Drawing a pie chart is really simple in mermaid.
- Start with `pie` keyword to begin the diagram
- Followed by `title` keyword and its value in string to give a title to the pie-chart. This is ***OPTIONAL***
- Followed by dataSet
- `label` for a section in the pie diagram within `" "` quotes.
- Followed by `:` semi-colon as separator
- Followed by `positive numeric value` (supported upto two decimal places)
[pie]
[title] [titlevalue] (OPTIONAL)
"[datakey1]" : [dataValue1]
"[datakey2]" : [dataValue2]
"[datakey3]" : [dataValue3]
.
.
## Example
```
pie
"DataKey1" : Positive numeric value (upto two decimal places)
title Key elements in Product X
"Calcium" : 42.96
"Potassium" : 50.05
"Magnesium" : 10.01
"Iron" : 5
```
```mermaid
pie title Key elements in Product X
pie
title Key elements in Product X
"Calcium" : 42.96
"Potassium" : 50.05
"Magnesium" : 25.01

View File

@ -275,7 +275,7 @@ sequenceDiagram
## Styling
Styling of the a sequence diagram is done by defining a number of css classes. During rendering these classes are extracted from the
Styling of the a sequence diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/sequence.scss
### Classes used

327
docs/stateDiagram.md Executable file
View File

@ -0,0 +1,327 @@
# State diagrams
> "A state diagram is a type of diagram used in computer science and related fields to describe the behavior of systems. State diagrams require that the system described is composed of a finite number of states; sometimes, this is indeed the case, while at other times this is a reasonable abstraction." Wikipedia
Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make it easier for users to share diagrams between mermaid and plantUml.
```markdown
stateDiagram
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
```mermaid
stateDiagram
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
In state diagrams systems are described in terms of its states and how the systems state can change to another state via a transitions. The example diagram above shows three states **Still**, **Moving** and **Crash**. You start in the state of Still. From Still you can change the state to Moving. In Moving you can change the state either back to Still or to Crash. There is no transition from Still to Crash.
## States
A state can be declares in multiple ways. The simplest way is to define a state id as a description.
```markdown
stateDiagram
s1
```
```mermaid
stateDiagram
s1
```
Another way is by using the state keyword with a description as per below:
```markdown
stateDiagram
state "This is a state description" as s2
```
```mermaid
stateDiagram
state "This is a state description" as s2
```
Another way to define a state with a description is to define the state id followed by a colon and the description:
```markdown
stateDiagram
s2 : This is a state description
```
```mermaid
stateDiagram
s2 : This is a state description
```
## Transitions
Transitions are path/edges when one state passes into another. This is represented using text arrow, "\-\-\>".
When you define a transition between two states and the states are not already defined the undefined states are defined with the id from the transition. You can later add descriptions to states defined this way.
```markdown
stateDiagram
s1 --> s2
```
```mermaid
stateDiagram
s1 --> s2
```
It is possible to add text to a transition. To describe what it represents.
```markdown
stateDiagram
s1 --> s2: A transition
```
```mermaid
stateDiagram
s1 --> s2: A transition
```
## Start and End
There are two special states indicating the start and stop of the diagram. These are written with the [\*] syntax and the direction of the transition to it defines it either as a start or a stop state.
```markdown
stateDiagram
[*] --> s1
s1 --> [*]
```
```mermaid
stateDiagram
[*] --> s1
s1 --> [*]
```
## Composite states
In a real world use of state diagrams you often end up with diagrams that are multi-dimensional as one state can
have several internal states. These are called composite states in this terminology.
In order to define a composite state you need to use the state keyword followed by and id and the body of the composite state between \{\}. See the example below:
```markdown
stateDiagram
[*] --> First
state First {
[*] --> second
second --> [*]
}
```
```mermaid
stateDiagram
[*] --> First
state First {
[*] --> second
second --> [*]
}
```
You can do this in several layers:
```markdown
stateDiagram
[*] --> First
state First {
[*] --> Second
state Second {
[*] --> second
second --> Third
state Third {
[*] --> third
third --> [*]
}
}
}
```
```mermaid
stateDiagram
[*] --> First
state First {
[*] --> Second
state Second {
[*] --> second
second --> Third
state Third {
[*] --> third
third --> [*]
}
}
}
```
You can also define transitions also between composite states:
```markdown
stateDiagram
[*] --> First
First --> Second
First --> Third
state First {
[*] --> fir
fir --> [*]
}
state Second {
[*] --> sec
sec --> [*]
}
state Third {
[*] --> thi
thi --> [*]
}
```
```mermaid
stateDiagram
[*] --> First
First --> Second
First --> Third
state First {
[*] --> fir
fir --> [*]
}
state Second {
[*] --> sec
sec --> [*]
}
state Third {
[*] --> thi
thi --> [*]
}
```
*You can not define transitions between internal states belonging to different composite states*
## Forks
It is possible to specify a fork in the diagram using &lt;&lt;fork&gt;&gt; &lt;&lt;join&gt;&gt;.
```markdown
stateDiagram
state fork_state <<fork>>
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state <<join>>
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
```
```mermaid
stateDiagram
state fork_state <<fork>>
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state <<join>>
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
```
## Notes
Sometimes nothing says it better then a Post-it note. That is also the case in state diagrams.
Here you can choose to put the note to the *right of* or to the *left of* a node.
```markdown
stateDiagram
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
State1 --> State2
note left of State2 : This is the note to the left.
```
```mermaid
stateDiagram
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
State1 --> State2
note left of State2 : This is the note to the left.
```
## Concurrency
As in plantUml you can specify concurrency using the -- symbol.
```markdown
stateDiagram
[*] --> Active
state Active {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
--
[*] --> CapsLockOff
CapsLockOff --> CapsLockOn : EvCapsLockPressed
CapsLockOn --> CapsLockOff : EvCapsLockPressed
--
[*] --> ScrollLockOff
ScrollLockOff --> ScrollLockOn : EvCapsLockPressed
ScrollLockOn --> ScrollLockOff : EvCapsLockPressed
}
```
```mermaid
stateDiagram
[*] --> Active
state Active {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
--
[*] --> CapsLockOff
CapsLockOff --> CapsLockOn : EvCapsLockPressed
CapsLockOn --> CapsLockOff : EvCapsLockPressed
--
[*] --> ScrollLockOff
ScrollLockOff --> ScrollLockOn : EvCapsLockPressed
ScrollLockOn --> ScrollLockOff : EvCapsLockPressed
}
```
## Styling
Styling of the a state diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/state.scss

View File

@ -12,7 +12,7 @@ yarn add mermaid
https://unpkg.com/mermaid/
Please note that you can switch versions through the dropdown box on top right.
Please note that you can switch versions through the dropdown box at the top right.
## Simple usage on a web page
@ -21,8 +21,8 @@ The easiest way to integrate mermaid on a web page requires two elements:
1. Inclusion of the mermaid framework in the html page using a script tag
2. A graph definition on the web page
If these things are in place mermaid listens to the page load event and when fires, when the page has loaded, it will
locate the graphs n the page and transform them to svg files.
If these things are in place mermaid listens to the page load event and when fired (when the page has loaded) it will
locate the graphs on the page and transform them to svg files.
### Include mermaid on your web page:
@ -32,7 +32,7 @@ locate the graphs n the page and transform them to svg files.
```
Further down on your page mermaid will look for tags with `class="mermaid"`. From these tags mermaid will try to
read the chart definiton which will be replaced with the svg chart.
read the chart definiton and replace it with the svg chart.
### Define a chart like this:
@ -53,18 +53,18 @@ Would end up like this:
</div>
```
An id is also added to mermaid tags without id.
An id attribute is also added to mermaid tags without one.
### To enable click event and tags in nodes
In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced wich sets the level of trust to be used on the parsed diagrams.
In version 8.2 a security improvement was introduced. A `securityLevel` configuration was introduced which sets the level of trust to be used on the parsed diagrams.
* **true**: (default) tags in text are encoded, click functionality is disabled
* false: tags in text are allowed, click functionality is enabledClosed issues:
* false: tags in text are allowed, click functionality is enabled
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the `securityLevel` is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed.
If your application is taking resposibility for the diagram source security you can set the `securityLevel` accordingly. By doing this clicks and tags are again allowed.
```javascript
mermaidAPI.initialize({
@ -79,7 +79,6 @@ If your application is taking resposibility for the diagram source security you
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="mermaid.min.css">
</head>
<body>
<div class="mermaid">
@ -97,7 +96,7 @@ If your application is taking resposibility for the diagram source security you
### Labels out of bounds
If you use dynamically loaded fonts that are loaded through CSS, such as Google fonts, mermaid should wait for the
whole page to have been loaded (dom + assets, particularly the fonts file).
whole page to load (dom + assets, particularly the fonts file).
```javascript
$(document).load(function() {
@ -113,7 +112,7 @@ $(document).ready(function() {
});
```
Not doing so will most likely result in mermaid rendering graphs that have labels out of bounds. The default integration in mermaid uses the window.load event to start rendering. Also note that when rendering the width of lebale sare calucated of what width it bening meassured at the time.
Not doing so will most likely result in mermaid rendering graphs that have labels out of bounds. The default integration in mermaid uses the window.load event to start rendering.
If your page has other fonts in its body those might be used instead of the mermaid font. Specifying the font in your styling is a workaround for this.
```
@ -131,7 +130,7 @@ finer-grained control of this behavior, you can call `init` yourself with:
- a configuration object
- some nodes, as
- a node
- an a array-like of nodes
- an array-like of nodes
- or W3C selector that will find your nodes
Example:
@ -146,7 +145,7 @@ Or with no config object, and a jQuery selection:
mermaid.init(undefined, $("#someId .yetAnotherClass"));
```
> **Warning** This type of integration is deprecated instead the preferred way of handling more complex integration is to us the mermaidAPI instead.
> **Warning** This type of integration is deprecated. Instead the preferred way of handling more complex integration is to use the mermaidAPI instead.
## Usage with webpack
@ -156,9 +155,9 @@ mermaid fully supports webpack. Here is a [working demo](https://github.com/merm
## API usage
The main idea with the API is to be able to call a render function with graph defintion as a string. The render function
The main idea of the API is to be able to call a render function with the graph defintion as a string. The render function
will render the graph and call a callback with the resulting svg code. With this approach it is up to the site creator to
fetch the graph definition from the site, perhaps from a textarea, render it and place the graph somewhere in the site.
fetch the graph definition from the site (perhaps from a textarea), render it and place the graph somewhere in the site.
To do this, include mermaidAPI on your web website instead of mermaid.js. The example below show an outline of how this
could be used. The example just logs the resulting svg to the javascript console.
@ -211,8 +210,8 @@ mermaidAPI.render(id,txt,insertSvg, element);
1. The graph is generated using the render call.
2. After generation the render function calls the provided callback function, in this case it's called insertSvg.
3. The callback function is called with two parameters, the svg code of the generated graph and a function. This function binds events to the svg **after** it is inserted into the DOM.
4. Insert the svg code into the DOM for presentation
5. Call the binding function that binds the events
4. Insert the svg code into the DOM for presentation.
5. Call the binding function that binds the events.
## Example of a marked renderer
@ -231,7 +230,7 @@ renderer.code = function (code, language) {
};
```
Another example in coffeescript that also includes the mermaid script tag into the generated markup.
Another example in coffeescript that also includes the mermaid script tag in the generated markup.
```CoffeeScript
marked = require 'marked'
@ -259,7 +258,7 @@ module.exports = (options) ->
**Error handling**
When the parser encounters invalid syntax the **mermaid.parseError** function is called. It is possible to override this
function in order to handle the error in an application specific way.
function in order to handle the error in an application-specific way.
**Parsing text without rendering**
@ -296,13 +295,13 @@ setting the options in mermaid.
3. *using the global mermaid_config object* - deprecated
4. Instantiation of the configuration using the **mermaid.init** call
The list above has two ways to many of doing this. Three are deprecated and will eventually be removed. The list of
The list above has two ways too many of doing this. Three are deprecated and will eventually be removed. The list of
configuration objects are described [in the mermaidAPI documentation](mermaidAPI.html).
## Using the `mermaidAPI.initialize`/`mermaid.initialize` call
The future proof way of setting the configuration is by using the initialization call to mermaid or mermaidAPi depending
The future proof way of setting the configuration is by using the initialization call to mermaid or mermaidAPI depending
on what kind of integration you use.
```html
@ -334,11 +333,11 @@ approach are:
mermaid.startOnLoad = true;
```
> **Warning** This way of setting the configuration is deprecated instead the preferred way of is to use the initialize method. This functionality is only kept for not breaking existing integrations
> **Warning** This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
## Using the mermaid_config
Is it possible to set some configuration via the mermaid object. The two parameters that are supported using this
It is possible to set some configuration via the mermaid object. The two parameters that are supported using this
approach are:
* mermaid_config.startOnLoad
@ -348,7 +347,7 @@ approach are:
mermaid_config.startOnLoad = true;
```
> **Warning** This way of setting the configuration is deprecated instead the preferred way of is to use the initialize method. This functionality is only kept for not breaking existing integrations
> **Warning** This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
## Using the mermaid.init call
@ -362,4 +361,4 @@ approach are:
mermaid_config.startOnLoad = true;
```
> **Warning** This way of setting the configuration is deprecated instead the preferred way of is to use the initialize method. This functionality is only kept for not breaking existing integrations
> **Warning** This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
img/gray-class.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
img/gray-flow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
img/gray-gantt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
img/gray-pie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/gray-sequence.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/gray-state.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "8.3.1",
"version": "8.4.2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.js",
"keywords": [
@ -24,9 +24,9 @@
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
"e2e-upd": "yarn lint && jest e2e -u --config e2e/jest.config.js",
"dev": "webpack-dev-server --config webpack.config.e2e.js",
"test": "yarn lint && jest src",
"test": "yarn lint && jest src/.*",
"test:watch": "jest --watch src",
"prepublishOnly": "yarn build && yarn release && yarn test",
"prepublishOnly": "yarn build && yarn release && yarn test && yarn e2e",
"prepush": "yarn test"
},
"repository": {
@ -47,10 +47,11 @@
},
"dependencies": {
"@braintree/sanitize-url": "^3.1.0",
"crypto-random-string": "^3.0.1",
"d3": "^5.7.0",
"dagre-d3-renderer": "^0.5.8",
"dagre-layout": "^0.8.8",
"graphlibrary": "^2.2.0",
"dagre-d3": "dagrejs/dagre-d3",
"dagre": "^0.8.4",
"graphlib": "^2.1.7",
"he": "^1.2.0",
"lodash": "^4.17.11",
"minify": "^4.1.1",
@ -76,13 +77,13 @@
"cypress": "3.4.0",
"husky": "^1.2.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^23.6.0",
"jest-environment-puppeteer": "^4.2.0",
"jest-image-snapshot": "^2.8.2",
"jest-puppeteer": "^4.2.0",
"jest": "^24.9.0",
"jest-environment-puppeteer": "^4.3.0",
"jest-image-snapshot": "^2.11.1",
"jest-puppeteer": "^4.3.0",
"jison": "^0.4.18",
"moment": "^2.23.0",
"node-sass": "^4.11.0",
"node-sass": "^4.12.0",
"puppeteer": "^1.17.0",
"sass-loader": "^7.1.0",
"start-server-and-test": "^1.10.0",

View File

@ -25,3 +25,12 @@ export const setConfig = conf => {
setConf(conf);
};
export const getConfig = () => config;
const configApi = {
setConfig,
getConfig
// get conf() {
// return config;
// }
};
export default configApi;

View File

@ -6,18 +6,18 @@ let classes = {};
/**
* Function called by parser when a node definition has been found.
* @param id
* @param text
* @param type
* @param style
* @public
*/
export const addClass = function(id) {
if (typeof classes[id] === 'undefined') {
classes[id] = {
id: id,
methods: [],
members: []
};
}
// Only add class if not exists
if (typeof classes[id] !== 'undefined') return;
classes[id] = {
id: id,
methods: [],
members: [],
annotations: []
};
};
export const clear = function() {
@ -43,20 +43,47 @@ export const addRelation = function(relation) {
relations.push(relation);
};
/**
* Adds an annotation to the specified class
* Annotations mark special properties of the given type (like 'interface' or 'service')
* @param className The class name
* @param annotation The name of the annotation without any brackets
* @public
*/
export const addAnnotation = function(className, annotation) {
classes[className].annotations.push(annotation);
};
/**
* Adds a member to the specified class
* @param className The class name
* @param member The full name of the member.
* If the member is enclosed in <<brackets>> it is treated as an annotation
* If the member is ending with a closing bracket ) it is treated as a method
* Otherwise the member will be treated as a normal property
* @public
*/
export const addMember = function(className, member) {
const theClass = classes[className];
if (typeof member === 'string') {
if (member.substr(-1) === ')') {
theClass.methods.push(member);
} else {
theClass.members.push(member);
// Member can contain white spaces, we trim them out
const memberString = member.trim();
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
// Remove leading and trailing brackets
theClass.annotations.push(memberString.substring(2, memberString.length - 2));
} else if (memberString.endsWith(')')) {
theClass.methods.push(memberString);
} else if (memberString) {
theClass.members.push(memberString);
}
}
};
export const addMembers = function(className, MembersArr) {
if (Array.isArray(MembersArr)) {
MembersArr.forEach(member => addMember(className, member));
export const addMembers = function(className, members) {
if (Array.isArray(members)) {
members.reverse();
members.forEach(member => addMember(className, member));
}
};
@ -85,6 +112,7 @@ export default {
clear,
getClass,
getClasses,
addAnnotation,
getRelations,
addRelation,
addMember,

View File

@ -64,7 +64,7 @@ describe('class diagram, ', function() {
it('should handle parsing of method statements grouped by brackets', function() {
const str =
'classDiagram\n' +
'class Dummy {\n' +
'class Dummy_Class {\n' +
'String data\n' +
' void methods()\n' +
'}\n' +
@ -207,5 +207,80 @@ describe('class diagram, ', function() {
expect(relations[3].relation.type2).toBe('none');
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
});
it('should handle class annotations', function() {
const str = 'classDiagram\n' + 'class Class1\n' + '<<interface>> Class1';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.annotations.length).toBe(1);
expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(0);
expect(testClass.annotations[0]).toBe('interface');
});
it('should handle class annotations with members and methods', function() {
const str =
'classDiagram\n' +
'class Class1\n' +
'Class1 : int test\n' +
'Class1 : test()\n' +
'<<interface>> Class1';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.annotations.length).toBe(1);
expect(testClass.members.length).toBe(1);
expect(testClass.methods.length).toBe(1);
expect(testClass.annotations[0]).toBe('interface');
});
it('should handle class annotations in brackets', function() {
const str = 'classDiagram\n' + 'class Class1 {\n' + '<<interface>>\n' + '}';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.annotations.length).toBe(1);
expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(0);
expect(testClass.annotations[0]).toBe('interface');
});
it('should handle class annotations in brackets with members and methods', function() {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'<<interface>>\n' +
'int : test\n' +
'test()\n' +
'}';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.annotations.length).toBe(1);
expect(testClass.members.length).toBe(1);
expect(testClass.methods.length).toBe(1);
expect(testClass.annotations[0]).toBe('interface');
});
it('should add bracket members in right order', function() {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.members.length).toBe(2);
expect(testClass.methods.length).toBe(2);
expect(testClass.members[0]).toBe('int : test');
expect(testClass.members[1]).toBe('string : foo');
expect(testClass.methods[0]).toBe('test()');
expect(testClass.methods[1]).toBe('foo()');
});
});
});

View File

@ -1,13 +1,14 @@
import * as d3 from 'd3';
import dagre from 'dagre-layout';
import graphlib from 'graphlibrary';
import dagre from 'dagre';
import graphlib from 'graphlib';
import { logger } from '../../logger';
import classDb from './classDb';
import utils from '../../utils';
import { parser } from './parser/classDiagram';
parser.yy = classDb;
const idCache = {};
let idCache = {};
let classCnt = 0;
const conf = {
@ -135,7 +136,6 @@ const insertMarkers = function(elem) {
};
let edgeCount = 0;
let total = 0;
const drawEdge = function(elem, path, relation) {
const getRelationType = function(type) {
switch (type) {
@ -198,15 +198,36 @@ const drawEdge = function(elem, path, relation) {
let x, y;
const l = path.points.length;
// Calculate Label position
let labalPosition = utils.calcLabelPosition(path.points);
x = labalPosition.x;
y = labalPosition.y;
let p1_card_x, p1_card_y;
// p1_card_padd_x = conf.padding * 2,
// p1_card_padd_y = conf.padding;
let p2_card_x, p2_card_y;
// p2_card_padd_x = conf.padding * 2,
// p2_card_padd_y = -conf.padding / 2;
if (l % 2 !== 0 && l > 1) {
const p1 = path.points[Math.floor(l / 2)];
const p2 = path.points[Math.ceil(l / 2)];
x = (p1.x + p2.x) / 2;
y = (p1.y + p2.y) / 2;
} else {
const p = path.points[Math.floor(l / 2)];
x = p.x;
y = p.y;
let cardinality_1_point = utils.calcCardinalityPosition(
relation.relation.type1 !== 'none',
path.points,
path.points[0]
);
let cardinality_2_point = utils.calcCardinalityPosition(
relation.relation.type2 !== 'none',
path.points,
path.points[l - 1]
);
logger.debug('cardinality_1_point ' + JSON.stringify(cardinality_1_point));
logger.debug('cardinality_2_point ' + JSON.stringify(cardinality_2_point));
p1_card_x = cardinality_1_point.x;
p1_card_y = cardinality_1_point.y;
p2_card_x = cardinality_2_point.x;
p2_card_y = cardinality_2_point.y;
}
if (typeof relation.title !== 'undefined') {
@ -231,6 +252,28 @@ const drawEdge = function(elem, path, relation) {
.attr('height', bounds.height + conf.padding);
}
logger.info('Rendering relation ' + JSON.stringify(relation));
if (typeof relation.relationTitle1 !== 'undefined' && relation.relationTitle1 !== 'none') {
const g = elem.append('g').attr('class', 'cardinality');
g.append('text')
.attr('class', 'type1')
.attr('x', p1_card_x)
.attr('y', p1_card_y)
.attr('fill', 'black')
.attr('font-size', '6')
.text(relation.relationTitle1);
}
if (typeof relation.relationTitle2 !== 'undefined' && relation.relationTitle2 !== 'none') {
const g = elem.append('g').attr('class', 'cardinality');
g.append('text')
.attr('class', 'type2')
.attr('x', p2_card_x)
.attr('y', p2_card_y)
.attr('fill', 'black')
.attr('font-size', '6')
.text(relation.relationTitle2);
}
edgeCount++;
};
@ -247,7 +290,7 @@ const drawClass = function(elem, classDef) {
}
};
const id = 'classId' + (classCnt % total);
const id = 'classId' + classCnt;
const classInfo = {
id: id,
label: classDef.id,
@ -255,15 +298,34 @@ const drawClass = function(elem, classDef) {
height: 0
};
// add class group
const g = elem
.append('g')
.attr('id', id)
.attr('class', 'classGroup');
// add title
const title = g
.append('text')
.attr('x', conf.padding)
.attr('y', conf.textHeight + conf.padding)
.text(classDef.id);
.attr('x', 0);
// add annotations
let isFirst = true;
classDef.annotations.forEach(function(member) {
const titleText2 = title.append('tspan').text('«' + member + '»');
if (!isFirst) titleText2.attr('dy', conf.textHeight);
isFirst = false;
});
// add class title
const classTitle = title
.append('tspan')
.text(classDef.id)
.attr('class', 'title');
// If class has annotations the title needs to have an offset of the text height
if (!isFirst) classTitle.attr('dy', conf.textHeight);
const titleHeight = title.node().getBBox().height;
@ -280,7 +342,7 @@ const drawClass = function(elem, classDef) {
.attr('fill', 'white')
.attr('class', 'classText');
let isFirst = true;
isFirst = true;
classDef.members.forEach(function(member) {
addTspan(members, member, isFirst);
isFirst = false;
@ -309,16 +371,25 @@ const drawClass = function(elem, classDef) {
});
const classBox = g.node().getBBox();
g.insert('rect', ':first-child')
const rect = g
.insert('rect', ':first-child')
.attr('x', 0)
.attr('y', 0)
.attr('width', classBox.width + 2 * conf.padding)
.attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin);
membersLine.attr('x2', classBox.width + 2 * conf.padding);
methodsLine.attr('x2', classBox.width + 2 * conf.padding);
const rectWidth = rect.node().getBBox().width;
classInfo.width = classBox.width + 2 * conf.padding;
// Center title
// We subtract the width of each text element from the class box width and divide it by 2
title.node().childNodes.forEach(function(x) {
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
});
membersLine.attr('x2', rectWidth);
methodsLine.attr('x2', rectWidth);
classInfo.width = rectWidth;
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
idCache[id] = classInfo;
@ -339,6 +410,7 @@ export const setConf = function(cnf) {
* @param id
*/
export const draw = function(text, id) {
idCache = {};
parser.yy.clear();
parser.parse(text);
@ -365,7 +437,6 @@ export const draw = function(text, id) {
const classes = classDb.getClasses();
const keys = Object.keys(classes);
total = keys.length;
for (let i = 0; i < keys.length; i++) {
const classDef = classes[keys[i]];
const node = drawClass(diagram, classDef);
@ -407,8 +478,8 @@ export const draw = function(text, id) {
});
diagram.attr('height', '100%');
diagram.attr('width', '100%');
diagram.attr('viewBox', '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20));
diagram.attr('width', `${g.graph().width * 1.5 + 20}`);
diagram.attr('viewBox', '-10 -10 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20));
};
export default {

View File

@ -21,6 +21,8 @@
"class" return 'CLASS';
"<<" return 'ANNOTATION_START';
">>" return 'ANNOTATION_END';
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
@ -41,8 +43,8 @@
\% return 'PCT';
"=" return 'EQUALS';
\= return 'EQUALS';
[A-Za-z]+ return 'ALPHA';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
\w+ return 'ALPHA';
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
[0-9]+ return 'NUM';
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
@ -131,7 +133,6 @@ statements
| statement NEWLINE statements
;
className
: alphaNumToken className { $$=$1+$2; }
| alphaNumToken { $$=$1; }
@ -142,6 +143,7 @@ statement
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
| classStatement
| methodStatement
| annotationStatement
;
classStatement
@ -149,6 +151,10 @@ classStatement
| CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addClass($2);yy.addMembers($2,$4);}
;
annotationStatement
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
;
members
: MEMBER { $$ = [$1]; }
| MEMBER members { $2.push($1);$$=$2;}
@ -157,7 +163,7 @@ members
methodStatement
: className {/*console.log('Rel found',$1);*/}
| className LABEL {yy.addMember($1,yy.cleanupLabel($2));}
| MEMBER {console.warn('Member',$1);}
| MEMBER {/*console.warn('Member',$1);*/}
| SEPARATOR {/*console.log('sep found',$1);*/}
;

View File

@ -0,0 +1,177 @@
import dagreD3 from 'dagre-d3';
function question(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const s = (w + h) * 0.9;
const points = [
{ x: s / 2, y: 0 },
{ x: s, y: -s / 2 },
{ x: s / 2, y: -s },
{ x: 0, y: -s / 2 }
];
const shapeSvg = insertPolygonShape(parent, s, s, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function hexagon(parent, bbox, node) {
const f = 4;
const h = bbox.height;
const m = h / f;
const w = bbox.width + 2 * m;
const points = [
{ x: m, y: 0 },
{ x: w - m, y: 0 },
{ x: w, y: -h / 2 },
{ x: w - m, y: -h },
{ x: m, y: -h },
{ x: 0, y: -h / 2 }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function rect_left_inv_arrow(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: -h / 2, y: 0 },
{ x: w, y: 0 },
{ x: w, y: -h },
{ x: -h / 2, y: -h },
{ x: 0, y: -h / 2 }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function lean_right(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: h / 6, y: -h }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function lean_left(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: (2 * h) / 6, y: 0 },
{ x: w + h / 6, y: 0 },
{ x: w - (2 * h) / 6, y: -h },
{ x: -h / 6, y: -h }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function trapezoid(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w + (2 * h) / 6, y: 0 },
{ x: w - h / 6, y: -h },
{ x: h / 6, y: -h }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function inv_trapezoid(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: h / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: (-2 * h) / 6, y: -h }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
function rect_right_inv_arrow(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: 0, y: 0 },
{ x: w + h / 2, y: 0 },
{ x: w, y: -h / 2 },
{ x: w + h / 2, y: -h },
{ x: 0, y: -h }
];
const shapeSvg = insertPolygonShape(parent, w, h, points);
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
}
export function addToRender(render) {
render.shapes().question = question;
render.shapes().hexagon = hexagon;
// Add custom shape for box with inverted arrow on left side
render.shapes().rect_left_inv_arrow = rect_left_inv_arrow;
// Add custom shape for box with inverted arrow on left side
render.shapes().lean_right = lean_right;
// Add custom shape for box with inverted arrow on left side
render.shapes().lean_left = lean_left;
// Add custom shape for box with inverted arrow on left side
render.shapes().trapezoid = trapezoid;
// Add custom shape for box with inverted arrow on left side
render.shapes().inv_trapezoid = inv_trapezoid;
// Add custom shape for box with inverted arrow on right side
render.shapes().rect_right_inv_arrow = rect_right_inv_arrow;
}
function insertPolygonShape(parent, w, h, points) {
return parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')');
}
export default {
addToRender
};

View File

@ -0,0 +1,91 @@
import { addToRender } from './flowChartShapes';
describe('flowchart shapes', function() {
[
[
'question',
4,
function(w, h) {
return (w + h) * 0.9;
},
function(w, h) {
return (w + h) * 0.9;
}
],
[
'hexagon',
6,
function(w, h) {
return w + h / 2;
},
useHeight
],
['rect_left_inv_arrow', 5, useWidth, useHeight],
['rect_right_inv_arrow', 5, useWidth, useHeight],
['lean_right', 4, useWidth, useHeight],
['lean_left', 4, useWidth, useHeight],
['trapezoid', 4, useWidth, useHeight],
['inv_trapezoid', 4, useWidth, useHeight]
].forEach(function([shapeType, expectedPointCount, getW, getH]) {
it(`should add a ${shapeType} shape that renders a properly translated polygon element`, function() {
const mockRender = MockRender();
const mockSvg = MockSvg();
addToRender(mockRender);
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
const dx = -getW(width, height) / 2;
const dy = getH(width, height) / 2;
const points = shape.__attrs.points.split(' ');
expect(shape.__tag).toEqual('polygon');
expect(shape.__attrs).toHaveProperty('transform', `translate(${dx},${dy})`);
expect(points).toHaveLength(expectedPointCount);
});
});
});
});
function MockRender() {
const shapes = {};
return {
shapes() {
return shapes;
}
};
}
function MockSvg(tag, ...args) {
const children = [];
const attributes = {};
return {
get __args() {
return args;
},
get __tag() {
return tag;
},
get __children() {
return children;
},
get __attrs() {
return attributes;
},
insert: function(tag, ...args) {
const child = MockSvg(tag, ...args);
children.push(child);
return child;
},
attr(name, value) {
this.__attrs[name] = value;
return this;
}
};
}
function useWidth(w, h) {
return w;
}
function useHeight(w, h) {
return h;
}

View File

@ -4,6 +4,8 @@ import { logger } from '../../logger';
import utils from '../../utils';
import { getConfig } from '../../config';
const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-';
const config = getConfig();
let vertices = {};
let edges = [];
@ -48,7 +50,7 @@ export const addVertex = function(_id, text, type, style, classes) {
return;
}
if (id[0].match(/\d/)) id = 's' + id;
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (typeof vertices[id] === 'undefined') {
vertices[id] = { id: id, styles: [], classes: [] };
@ -96,8 +98,8 @@ export const addVertex = function(_id, text, type, style, classes) {
export const addLink = function(_start, _end, type, linktext) {
let start = _start;
let end = _end;
if (start[0].match(/\d/)) start = 's' + start;
if (end[0].match(/\d/)) end = 's' + end;
if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
logger.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '' };
@ -194,7 +196,7 @@ export const setDirection = function(dir) {
export const setClass = function(ids, className) {
ids.split(',').forEach(function(_id) {
let id = _id;
if (_id[0].match(/\d/)) id = 's' + id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (typeof vertices[id] !== 'undefined') {
vertices[id].classes.push(className);
}
@ -215,7 +217,7 @@ const setTooltip = function(ids, tooltip) {
const setClickFun = function(_id, functionName) {
let id = _id;
if (_id[0].match(/\d/)) id = 's' + id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (config.securityLevel !== 'loose') {
return;
}
@ -223,7 +225,7 @@ const setClickFun = function(_id, functionName) {
return;
}
if (typeof vertices[id] !== 'undefined') {
funs.push(function(element) {
funs.push(function() {
const elem = document.querySelector(`[id="${id}"]`);
if (elem !== null) {
elem.addEventListener(
@ -247,7 +249,7 @@ const setClickFun = function(_id, functionName) {
export const setLink = function(ids, linkStr, tooltip) {
ids.split(',').forEach(function(_id) {
let id = _id;
if (_id[0].match(/\d/)) id = 's' + id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (typeof vertices[id] !== 'undefined') {
if (config.securityLevel !== 'loose') {
vertices[id].link = sanitizeUrl(linkStr); // .replace(/javascript:.*/g, '')
@ -395,7 +397,7 @@ export const addSubGraph = function(_id, list, _title) {
return false;
}
if (type in prims) {
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true); // eslint-disable-line
} else {
return objs.indexOf(item) >= 0 ? false : objs.push(item);
}
@ -406,11 +408,11 @@ export const addSubGraph = function(_id, list, _title) {
nodeList = uniq(nodeList.concat.apply(nodeList, list));
for (let i = 0; i < nodeList.length; i++) {
if (nodeList[i][0].match(/\d/)) nodeList[i] = 's' + nodeList[i];
if (nodeList[i][0].match(/\d/)) nodeList[i] = MERMAID_DOM_ID_PREFIX + nodeList[i];
}
id = id || 'subGraph' + subCount;
if (id[0].match(/\d/)) id = 's' + id;
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
title = title || '';
title = sanitize(title);
subCount = subCount + 1;

View File

@ -1,13 +1,19 @@
import graphlib from 'graphlibrary';
import graphlib from 'graphlib';
import * as d3 from 'd3';
import flowDb from './flowDb';
import flow from './parser/flow';
import { getConfig } from '../../config';
import dagreD3 from 'dagre-d3-renderer';
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js';
const newDagreD3 = true;
import dagreD3 from 'dagre-d3';
// const newDagreD3 = false;
// import dagreD3 from '../../../../dagre-d3-renderer/dist/dagre-d3.core.js';
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { logger } from '../../logger';
import { interpolateToCurve } from '../../utils';
import flowChartShapes from './flowChartShapes';
const conf = {};
export const setConf = function(cnf) {
@ -35,9 +41,10 @@ export const addVertices = function(vert, g, svgId) {
}
}
} else {
// create the style definition for the text, if property is a text-property
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] !== 'undefined') {
if (arr[i].match('^color:')) styleStr = styleStr + arr[i] + ';';
if (arr[i].match('^color:|^text-align:')) styleStr = styleStr + arr[i] + ';';
}
}
}
@ -121,6 +128,9 @@ export const addVertices = function(vert, g, svgId) {
case 'diamond':
_shape = 'question';
break;
case 'hexagon':
_shape = 'hexagon';
break;
case 'odd':
_shape = 'rect_left_inv_arrow';
break;
@ -286,18 +296,35 @@ export const draw = function(text, id) {
}
// Create the input mermaid.graph
const g = new graphlib.Graph({
multigraph: true,
compound: true
})
.setGraph({
rankdir: dir,
marginx: 20,
marginy: 20
let g;
// Todo remove newDagreD3 when properly verified
if (newDagreD3) {
g = new graphlib.Graph({
multigraph: true,
compound: true
})
.setDefaultEdgeLabel(function() {
return {};
});
.setGraph({
rankdir: dir,
marginx: 8,
marginy: 8
})
.setDefaultEdgeLabel(function() {
return {};
});
} else {
g = new graphlib.Graph({
multigraph: true,
compound: true
})
.setGraph({
rankdir: dir,
marginx: 20,
marginy: 20
})
.setDefaultEdgeLabel(function() {
return {};
});
}
let subG;
const subGraphs = flowDb.getSubGraphs();
@ -328,199 +355,8 @@ export const draw = function(text, id) {
const Render = dagreD3.render;
const render = new Render();
// Add custom shape for rhombus type of boc (decision)
render.shapes().question = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const s = (w + h) * 0.9;
const points = [
{ x: s / 2, y: 0 },
{ x: s, y: -s / 2 },
{ x: s / 2, y: -s },
{ x: 0, y: -s / 2 }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('rx', 5)
.attr('ry', 5)
.attr('transform', 'translate(' + -s / 2 + ',' + (s * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side
render.shapes().rect_left_inv_arrow = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: -h / 2, y: 0 },
{ x: w, y: 0 },
{ x: w, y: -h },
{ x: -h / 2, y: -h },
{ x: 0, y: -h / 2 }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side
render.shapes().lean_right = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: h / 6, y: -h }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side
render.shapes().lean_left = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: (2 * h) / 6, y: 0 },
{ x: w + h / 6, y: 0 },
{ x: w - (2 * h) / 6, y: -h },
{ x: -h / 6, y: -h }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side
render.shapes().trapezoid = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w + (2 * h) / 6, y: 0 },
{ x: w - h / 6, y: -h },
{ x: h / 6, y: -h }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side
render.shapes().inv_trapezoid = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: h / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: (-2 * h) / 6, y: -h }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on right side
render.shapes().rect_right_inv_arrow = function(parent, bbox, node) {
const w = bbox.width;
const h = bbox.height;
const points = [
{ x: 0, y: 0 },
{ x: w + h / 2, y: 0 },
{ x: w, y: -h / 2 },
{ x: w + h / 2, y: -h },
{ x: 0, y: -h }
];
const shapeSvg = parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shapes
flowChartShapes.addToRender(render);
// Add our custom arrow - an empty arrowhead
render.arrows().none = function normal(parent, id, edge, type) {
@ -540,7 +376,7 @@ export const draw = function(text, id) {
};
// Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
render.arrows().normal = function normal(parent, id, edge, type) {
render.arrows().normal = function normal(parent, id) {
const marker = parent
.append('marker')
.attr('id', id)
@ -571,14 +407,51 @@ export const draw = function(text, id) {
return flowDb.getTooltip(this.id);
});
const conf = getConfig().flowchart;
const padding = 8;
const width = g.maxX - g.minX + padding * 2;
const height = g.maxY - g.minY + padding * 2;
svg.attr('width', '100%');
svg.attr('style', `max-width: ${width}px;`);
svg.attr('viewBox', `0 0 ${width} ${height}`);
svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`);
// Todo remove newDagreD3 when properly verified
if (newDagreD3) {
const svgBounds = svg.node().getBBox();
const width = svgBounds.width + padding * 2;
const height = svgBounds.height + padding * 2;
logger.debug(
`new ViewBox 0 0 ${width} ${height}`,
`translate(${padding - g._label.marginx}, ${padding - g._label.marginy})`
);
if (conf.useMaxWidth) {
svg.attr('width', '100%');
svg.attr('style', `max-width: ${width}px;`);
} else {
svg.attr('height', height);
svg.attr('width', width);
}
svg.attr('viewBox', `0 0 ${width} ${height}`);
svg
.select('g')
.attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`);
} else {
const width = g.maxX - g.minX + padding * 2;
const height = g.maxY - g.minY + padding * 2;
if (conf.useMaxWidth) {
svg.attr('width', '100%');
svg.attr('style', `max-width: ${width}px;`);
} else {
svg.attr('height', height);
svg.attr('width', width);
}
logger.debug(
`Org ViewBox 0 0 ${width} ${height}`,
`translate(${padding - g.minX}, ${padding - g.minY})\n${location.href}`
);
svg.attr('viewBox', `0 0 ${width} ${height}`);
svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`);
// svg.select('g').attr('transform', `translate(${padding - minX}, ${padding - minY})`);
}
// Index nodes
flowDb.indexNodes('subGraph' + i);
@ -601,7 +474,7 @@ export const draw = function(text, id) {
}
// Add label rects for non html labels
if (!getConfig().flowchart.htmlLabels) {
if (!conf.htmlLabels) {
const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label');
for (let k = 0; k < labels.length; k++) {
const label = labels[k];

View File

@ -0,0 +1,96 @@
import { addVertices } from './flowRenderer';
import { setConfig } from '../../config';
setConfig({
flowchart: {
htmlLabels: false
}
});
describe('the flowchart renderer', function() {
describe('when adding vertices to a graph', function() {
[
['round', 'rect', 5],
['square', 'rect'],
['diamond', 'question'],
['hexagon', 'hexagon'],
['odd', 'rect_left_inv_arrow'],
['lean_right', 'lean_right'],
['lean_left', 'lean_left'],
['trapezoid', 'trapezoid'],
['inv_trapezoid', 'inv_trapezoid'],
['odd_right', 'rect_left_inv_arrow'],
['circle', 'circle'],
['ellipse', 'ellipse'],
['group', 'rect']
].forEach(function([type, expectedShape, expectedRadios = 0]) {
it(`should add the correct shaped node to the graph for vertex type ${type}`, function() {
const addedNodes = [];
const mockG = {
setNode: function(id, object) {
addedNodes.push([id, object]);
}
};
addVertices(
{
v1: {
type,
id: 'my-node-id',
classes: [],
styles: [],
text: 'my vertex text'
}
},
mockG,
'svg-id'
);
expect(addedNodes).toHaveLength(1);
expect(addedNodes[0][0]).toEqual('my-node-id');
expect(addedNodes[0][1]).toHaveProperty('id', 'my-node-id');
expect(addedNodes[0][1]).toHaveProperty('labelType', 'svg');
expect(addedNodes[0][1]).toHaveProperty('shape', expectedShape);
expect(addedNodes[0][1]).toHaveProperty('rx', expectedRadios);
expect(addedNodes[0][1]).toHaveProperty('ry', expectedRadios);
});
});
});
[
[['fill:#fff'], 'fill:#fff;', ''],
[['color:#ccc'], 'color:#ccc;', 'color:#ccc;'],
[['fill:#fff', 'color:#ccc'], 'fill:#fff;color:#ccc;', 'color:#ccc;'],
[
['fill:#fff', 'color:#ccc', 'text-align:center'],
'fill:#fff;color:#ccc;text-align:center;',
'color:#ccc;text-align:center;'
]
].forEach(function([style, expectedStyle, expectedLabelStyle]) {
it(`should add the styles to style and/or labelStyle for style ${style}`, function() {
const addedNodes = [];
const mockG = {
setNode: function(id, object) {
addedNodes.push([id, object]);
}
};
addVertices(
{
v1: {
type: 'rect',
id: 'my-node-id',
classes: [],
styles: style,
text: 'my vertex text'
}
},
mockG,
'svg-id'
);
expect(addedNodes).toHaveLength(1);
expect(addedNodes[0][0]).toEqual('my-node-id');
expect(addedNodes[0][1]).toHaveProperty('id', 'my-node-id');
expect(addedNodes[0][1]).toHaveProperty('labelType', 'svg');
expect(addedNodes[0][1]).toHaveProperty('style', expectedStyle);
expect(addedNodes[0][1]).toHaveProperty('labelStyle', expectedLabelStyle);
});
});
});

View File

@ -0,0 +1,245 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Arrows] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('should handle a nodes and edges', function() {
const res = flow.parser.parse('graph TD;\nA-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it("should handle angle bracket ' > ' as direction LR", function() {
const res = flow.parser.parse('graph >;A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
const direction = flow.parser.yy.getDirection();
expect(direction).toBe('LR');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it("should handle angle bracket ' < ' as direction RL", function() {
const res = flow.parser.parse('graph <;A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
const direction = flow.parser.yy.getDirection();
expect(direction).toBe('RL');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it("should handle caret ' ^ ' as direction BT", function() {
const res = flow.parser.parse('graph ^;A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
const direction = flow.parser.yy.getDirection();
expect(direction).toBe('BT');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it("should handle lower-case 'v' as direction TB", function() {
const res = flow.parser.parse('graph v;A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
const direction = flow.parser.yy.getDirection();
expect(direction).toBe('TB');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle a nodes and edges and a space between link and node', function() {
const res = flow.parser.parse('graph TD;A --> B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle a nodes and edges, a space between link and node and each line ending without semicolon', function() {
const res = flow.parser.parse('graph TD\nA --> B\n style e red');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle statements ending without semicolon', function() {
const res = flow.parser.parse('graph TD\nA-->B\nB-->C');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(2);
expect(edges[1].start).toBe('B');
expect(edges[1].end).toBe('C');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
describe('it should multi directional arrows', function() {
describe('point', function() {
it('should handle double edged nodes and edges', function() {
const res = flow.parser.parse('graph TD;\nA<-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_point');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text', function() {
const res = flow.parser.parse('graph TD;\nA<-- text -->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_point');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].text).toBe('text');
});
it('should handle double edged nodes and edges on thick arrows', function() {
const res = flow.parser.parse('graph TD;\nA<==>B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_point');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on thick arrows', function() {
const res = flow.parser.parse('graph TD;\nA<== text ==>B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_point');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].text).toBe('text');
});
it('should handle double edged nodes and edges on dotted arrows', function() {
const res = flow.parser.parse('graph TD;\nA<-.->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_point');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on dotted arrows', function() {
const res = flow.parser.parse('graph TD;\nA<-. text .->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_point');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].text).toBe('text');
});
});
});
});

View File

@ -0,0 +1,151 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Comments] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('should handle a comments', function() {
const res = flow.parser.parse('graph TD;\n%% CComment\n A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle comments a at the start', function() {
const res = flow.parser.parse('%% Comment\ngraph TD;\n A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle comments at the end', function() {
const res = flow.parser.parse('graph TD;\n A-->B\n %% Comment at the find\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle comments at the end no trailing newline', function() {
const res = flow.parser.parse('graph TD;\n A-->B\n%% Commento');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle comments at the end many trailing newlines', function() {
const res = flow.parser.parse('graph TD;\n A-->B\n%% Commento\n\n\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle no trailing newlines', function() {
const res = flow.parser.parse('graph TD;\n A-->B');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle many trailing newlines', function() {
const res = flow.parser.parse('graph TD;\n A-->B\n\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle a comments with blank rows in-between', function() {
const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle a comments mermaid flowchart code in them', function() {
const res = flow.parser.parse(
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
});

View File

@ -0,0 +1,253 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Edges] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('should handle open ended edges', function() {
const res = flow.parser.parse('graph TD;A---B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_open');
});
it('should handle cross ended edges', function() {
const res = flow.parser.parse('graph TD;A--xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle open ended edges', function() {
const res = flow.parser.parse('graph TD;A--oB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_circle');
});
describe('cross', function() {
it('should handle double edged nodes and edges', function() {
const res = flow.parser.parse('graph TD;\nA x--x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text', function() {
const res = flow.parser.parse('graph TD;\nA x-- text --x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].text).toBe('text');
});
it('should handle double edged nodes and edges on thick arrows', function() {
const res = flow.parser.parse('graph TD;\nA x==x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on thick arrows', function() {
const res = flow.parser.parse('graph TD;\nA x== text ==x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].text).toBe('text');
});
it('should handle double edged nodes and edges on dotted arrows', function() {
const res = flow.parser.parse('graph TD;\nA x-.-x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on dotted arrows', function() {
const res = flow.parser.parse('graph TD;\nA x-. text .-x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].text).toBe('text');
});
});
describe('circle', function() {
it('should handle double edged nodes and edges', function() {
const res = flow.parser.parse('graph TD;\nA o--o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text', function() {
const res = flow.parser.parse('graph TD;\nA o-- text --o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].text).toBe('text');
});
it('should handle double edged nodes and edges on thick arrows', function() {
const res = flow.parser.parse('graph TD;\nA o==o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on thick arrows', function() {
const res = flow.parser.parse('graph TD;\nA o== text ==o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].text).toBe('text');
});
it('should handle double edged nodes and edges on dotted arrows', function() {
const res = flow.parser.parse('graph TD;\nA o-.-o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on dotted arrows', function() {
const res = flow.parser.parse('graph TD;\nA o-. text .-o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].text).toBe('text');
});
});
it('should handle multiple edges', function() {
const res = flow.parser.parse(
'graph TD;A---|This is the 123 s text|B;\nA---|This is the second edge|B;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].text).toBe('This is the 123 s text');
expect(edges[1].start).toBe('A');
expect(edges[1].end).toBe('B');
expect(edges[1].text).toBe('This is the second edge');
});
});

View File

@ -0,0 +1,54 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Interactions] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('it should be possible to use click to a callback', function() {
spyOn(flowDb, 'setClickEvent');
const res = flow.parser.parse('graph TD\nA-->B\nclick A callback');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', undefined);
});
it('it should be possible to use click to a callback with toolip', function() {
spyOn(flowDb, 'setClickEvent');
const res = flow.parser.parse('graph TD\nA-->B\nclick A callback "tooltip"');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', 'tooltip');
});
it('should handle interaction - click to a link', function() {
spyOn(flowDb, 'setLink');
const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html"');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', undefined);
});
it('should handle interaction - click to a link with tooltip', function() {
spyOn(flowDb, 'setLink');
const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html" "tooltip"');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', 'tooltip');
});
});

View File

@ -0,0 +1,119 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Lines] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('should handle line interpolation default definitions', function() {
const res = flow.parser.parse('graph TD\n' + 'A-->B\n' + 'linkStyle default interpolate basis');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.defaultInterpolate).toBe('basis');
});
it('should handle line interpolation numbered definitions', function() {
const res = flow.parser.parse(
'graph TD\n' +
'A-->B\n' +
'A-->C\n' +
'linkStyle 0 interpolate basis\n' +
'linkStyle 1 interpolate cardinal'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('cardinal');
});
it('should handle line interpolation multi-numbered definitions', function() {
const res = flow.parser.parse(
'graph TD\n' + 'A-->B\n' + 'A-->C\n' + 'linkStyle 0,1 interpolate basis'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('basis');
});
it('should handle line interpolation default with style', function() {
const res = flow.parser.parse(
'graph TD\n' + 'A-->B\n' + 'linkStyle default interpolate basis stroke-width:1px;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.defaultInterpolate).toBe('basis');
});
it('should handle line interpolation numbered with style', function() {
const res = flow.parser.parse(
'graph TD\n' +
'A-->B\n' +
'A-->C\n' +
'linkStyle 0 interpolate basis stroke-width:1px;\n' +
'linkStyle 1 interpolate cardinal stroke-width:1px;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('cardinal');
});
it('should handle line interpolation multi-numbered with style', function() {
const res = flow.parser.parse(
'graph TD\n' + 'A-->B\n' + 'A-->C\n' + 'linkStyle 0,1 interpolate basis stroke-width:1px;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('basis');
});
describe('it should handle new line type notation', function() {
it('it should handle regular lines', function() {
const res = flow.parser.parse('graph TD;A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('normal');
});
it('it should handle dotted lines', function() {
const res = flow.parser.parse('graph TD;A-.->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('dotted');
});
it('it should handle dotted lines', function() {
const res = flow.parser.parse('graph TD;A==>B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('thick');
});
});
});

View File

@ -0,0 +1,218 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Singlenodes] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('should handle a single node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;A;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['A'].styles.length).toBe(0);
});
it('should handle a single square node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a[A];');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].styles.length).toBe(0);
expect(vert['a'].type).toBe('square');
});
it('should handle a single round square node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a[A];');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].styles.length).toBe(0);
expect(vert['a'].type).toBe('square');
});
it('should handle a single circle node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a((A));');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('circle');
});
it('should handle a single round node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a(A);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('round');
});
it('should handle a single odd node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a>A];');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('odd');
});
it('should handle a single diamond node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a{A};');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('diamond');
});
it('should handle a single diamond node with whitespace after it', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a{A} ;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('diamond');
});
it('should handle a single diamond node with html in it', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a{A <br> end};');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('diamond');
expect(vert['a'].text).toBe('A <br/> end');
});
it('should handle a single hexagon node', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a{{A}};');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('hexagon');
});
it('should handle a single hexagon node with html in it', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a{{A <br> end}};');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('hexagon');
expect(vert['a'].text).toBe('A <br/> end');
});
it('should handle a single round node with html in it', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a(A <br> end);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('round');
expect(vert['a'].text).toBe('A <br/> end');
});
it('should handle a single node with alphanumerics starting on a char', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;id1;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['id1'].styles.length).toBe(0);
});
it('should handle a single node with a single digit', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;1;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['mermaid-dom-id-1'].text).toBe('1');
});
it('should handle a single node with a single digit in a subgraph', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;subgraph "hello";1;end;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['mermaid-dom-id-1'].text).toBe('1');
});
it('should handle a single node with alphanumerics starting on a num', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;1id;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['mermaid-dom-id-1id'].styles.length).toBe(0);
});
it('should handle a single node with alphanumerics containing a minus sign', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;i-d;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['i-d'].styles.length).toBe(0);
});
it('should handle a single node with alphanumerics containing a underscore sign', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;i_d;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['i_d'].styles.length).toBe(0);
});
});

View File

@ -0,0 +1,311 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Style] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
// log.debug(flow.parser.parse('graph TD;style Q background:#fff;'));
it('should handle styles for vertices', function() {
const res = flow.parser.parse('graph TD;style Q background:#fff;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
const style = vert['Q'].styles[0];
expect(vert['Q'].styles.length).toBe(1);
expect(vert['Q'].styles[0]).toBe('background:#fff');
});
// log.debug(flow.parser.parse('graph TD;style Q background:#fff;'));
it('should handle styles for edges', function() {
const res = flow.parser.parse('graph TD;a-->b;\nstyle #0 stroke: #f66;');
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(1);
});
it('should handle multiple styles for a vortex', function() {
const res = flow.parser.parse('graph TD;style R background:#fff,border:1px solid red;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['R'].styles.length).toBe(2);
expect(vert['R'].styles[0]).toBe('background:#fff');
expect(vert['R'].styles[1]).toBe('border:1px solid red');
});
it('should handle multiple styles in a graph', function() {
const res = flow.parser.parse(
'graph TD;style S background:#aaa;\nstyle T background:#bbb,border:1px solid red;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['S'].styles.length).toBe(1);
expect(vert['T'].styles.length).toBe(2);
expect(vert['S'].styles[0]).toBe('background:#aaa');
expect(vert['T'].styles[0]).toBe('background:#bbb');
expect(vert['T'].styles[1]).toBe('border:1px solid red');
});
it('should handle styles and graph definitons in a graph', function() {
const res = flow.parser.parse(
'graph TD;S-->T;\nstyle S background:#aaa;\nstyle T background:#bbb,border:1px solid red;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['S'].styles.length).toBe(1);
expect(vert['T'].styles.length).toBe(2);
expect(vert['S'].styles[0]).toBe('background:#aaa');
expect(vert['T'].styles[0]).toBe('background:#bbb');
expect(vert['T'].styles[1]).toBe('border:1px solid red');
});
it('should handle styles and graph definitons in a graph', function() {
const res = flow.parser.parse('graph TD;style T background:#bbb,border:1px solid red;');
// const res = flow.parser.parse('graph TD;style T background: #bbb;');
const vert = flow.parser.yy.getVertices();
expect(vert['T'].styles.length).toBe(2);
expect(vert['T'].styles[0]).toBe('background:#bbb');
expect(vert['T'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to declare a class', function() {
const res = flow.parser.parse(
'graph TD;classDef exClass background:#bbb,border:1px solid red;'
);
// const res = flow.parser.parse('graph TD;style T background: #bbb;');
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to declare a class with a dot in the style', function() {
const res = flow.parser.parse(
'graph TD;classDef exClass background:#bbb,border:1.5px solid red;'
);
// const res = flow.parser.parse('graph TD;style T background: #bbb;');
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1.5px solid red');
});
it('should be possible to declare a class with a space in the style', function() {
const res = flow.parser.parse(
'graph TD;classDef exClass background: #bbb,border:1.5px solid red;'
);
// const res = flow.parser.parse('graph TD;style T background : #bbb;');
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background: #bbb');
expect(classes['exClass'].styles[1]).toBe('border:1.5px solid red');
});
it('should be possible to apply a class to a vertex', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a-->b;' + '\n';
statement = statement + 'class a exClass;';
const res = flow.parser.parse(statement);
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to apply a class to a vertex with an id containing _', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a_a-->b_b;' + '\n';
statement = statement + 'class a_a exClass;';
const res = flow.parser.parse(statement);
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to apply a class to a vertex directly', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a-->b[test]:::exClass;' + '\n';
const res = flow.parser.parse(statement);
const vertices = flow.parser.yy.getVertices();
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(vertices['b'].classes[0]).toBe('exClass');
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to apply a class to a vertex directly : usecase A[text].class ', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'b[test]:::exClass;' + '\n';
const res = flow.parser.parse(statement);
const vertices = flow.parser.yy.getVertices();
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(vertices['b'].classes[0]).toBe('exClass');
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to apply a class to a vertex directly : usecase A[text].class-->B[test2] ', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'A[test]:::exClass-->B[test2];' + '\n';
const res = flow.parser.parse(statement);
const vertices = flow.parser.yy.getVertices();
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(vertices['A'].classes[0]).toBe('exClass');
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to apply a class to a vertex directly 2', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a-->b[1 a a text!.]:::exClass;' + '\n';
const res = flow.parser.parse(statement);
const vertices = flow.parser.yy.getVertices();
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(vertices['b'].classes[0]).toBe('exClass');
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
});
it('should be possible to apply a class to a comma separated list of vertices', function() {
let statement = '';
statement = statement + 'graph TD;' + '\n';
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a-->b;' + '\n';
statement = statement + 'class a,b exClass;';
const res = flow.parser.parse(statement);
const classes = flow.parser.yy.getClasses();
const vertices = flow.parser.yy.getVertices();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
expect(vertices['a'].classes[0]).toBe('exClass');
expect(vertices['b'].classes[0]).toBe('exClass');
});
it('should handle style definitions with more then 1 digit in a row', function() {
const res = flow.parser.parse(
'graph TD\n' +
'A-->B1\n' +
'A-->B2\n' +
'A-->B3\n' +
'A-->B4\n' +
'A-->B5\n' +
'A-->B6\n' +
'A-->B7\n' +
'A-->B8\n' +
'A-->B9\n' +
'A-->B10\n' +
'A-->B11\n' +
'linkStyle 10 stroke-width:1px;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
it('should handle multi-numbered style definitons with more then 1 digit in a row', function() {
const res = flow.parser.parse(
'graph TD\n' +
'A-->B1\n' +
'A-->B2\n' +
'A-->B3\n' +
'A-->B4\n' +
'A-->B5\n' +
'A-->B6\n' +
'A-->B7\n' +
'A-->B8\n' +
'A-->B9\n' +
'A-->B10\n' +
'A-->B11\n' +
'A-->B12\n' +
'linkStyle 10,11 stroke-width:1px;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
it('should handle classDefs with style in classes', function() {
const res = flow.parser.parse('graph TD\nA-->B\nclassDef exClass font-style:bold;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
it('should handle classDefs with % in classes', function() {
const res = flow.parser.parse(
'graph TD\nA-->B\nclassDef exClass fill:#f96,stroke:#333,stroke-width:4px,font-size:50%,font-style:bold;'
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
});

View File

@ -0,0 +1,493 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict'
});
describe('[Text] when parsing', () => {
beforeEach(function() {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
describe('it should handle text on edges', function() {
it('it should handle text without space', function() {
const res = flow.parser.parse('graph TD;A--x|textNoSpace|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle with space', function() {
const res = flow.parser.parse('graph TD;A--x|text including space|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('it should handle text with /', function() {
const res = flow.parser.parse('graph TD;A--x|text with / should work|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
});
it('it should handle space and space between vertices and link', function() {
const res = flow.parser.parse('graph TD;A --x|textNoSpace| B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and CAPS', function() {
const res = flow.parser.parse('graph TD;A--x|text including CAPS space|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and dir', function() {
const res = flow.parser.parse('graph TD;A--x|text including URL space|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and send', function() {
const res = flow.parser.parse('graph TD;A--text including URL space and send-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('text including URL space and send');
});
it('should handle space and send', function() {
const res = flow.parser.parse('graph TD;A-- text including URL space and send -->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('text including URL space and send');
});
it('should handle space and dir (TD)', function() {
const res = flow.parser.parse('graph TD;A--x|text including R TD space|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including R TD space');
});
it('should handle `', function() {
const res = flow.parser.parse('graph TD;A--x|text including `|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including `');
});
it('should handle v in node ids only v', function() {
// only v
const res = flow.parser.parse('graph TD;A--xv(my text);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert['v'].text).toBe('my text');
});
it('should handle v in node ids v at end', function() {
// v at end
const res = flow.parser.parse('graph TD;A--xcsv(my text);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert['csv'].text).toBe('my text');
});
it('should handle v in node ids v in middle', function() {
// v in middle
const res = flow.parser.parse('graph TD;A--xava(my text);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert['ava'].text).toBe('my text');
});
it('should handle v in node ids, v at start', function() {
// v at start
const res = flow.parser.parse('graph TD;A--xva(my text);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert['va'].text).toBe('my text');
});
it('should handle keywords', function() {
const res = flow.parser.parse('graph TD;A--x|text including graph space|B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text including graph space');
});
it('should handle keywords', function() {
const res = flow.parser.parse('graph TD;V-->a[v]');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['a'].text).toBe('v');
});
it('should handle keywords', function() {
const res = flow.parser.parse('graph TD;V-->a[v]');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['a'].text).toBe('v');
});
it('should handle quoted text', function() {
const res = flow.parser.parse('graph TD;V-- "test string()" -->a[v]');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('test string()');
});
});
describe('it should handle text on lines', () => {
it('it should handle normal text on lines', function() {
const res = flow.parser.parse('graph TD;A-- test text with == -->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('normal');
});
it('it should handle dotted text on lines', function() {
const res = flow.parser.parse('graph TD;A-. test text with == .->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('dotted');
});
it('it should handle thick text on lines', function() {
const res = flow.parser.parse('graph TD;A== test text with - ==>B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('thick');
});
});
describe('it should handle text on edges using the new notation', function() {
it('it should handle text without space', function() {
const res = flow.parser.parse('graph TD;A-- textNoSpace --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('it should handle text with multiple leading space', function() {
const res = flow.parser.parse('graph TD;A-- textNoSpace --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle with space', function() {
const res = flow.parser.parse('graph TD;A-- text including space --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('it should handle text with /', function() {
const res = flow.parser.parse('graph TD;A -- text with / should work --x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
});
it('it should handle space and space between vertices and link', function() {
const res = flow.parser.parse('graph TD;A -- textNoSpace --x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and CAPS', function() {
const res = flow.parser.parse('graph TD;A-- text including CAPS space --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and dir', function() {
const res = flow.parser.parse('graph TD;A-- text including URL space --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and dir (TD)', function() {
const res = flow.parser.parse('graph TD;A-- text including R TD space --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including R TD space');
});
it('should handle keywords', function() {
const res = flow.parser.parse('graph TD;A-- text including graph space and v --xB;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text including graph space and v');
});
it('should handle keywords', function() {
const res = flow.parser.parse('graph TD;A-- text including graph space and v --xB[blav]');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text including graph space and v');
});
// xit('should handle text on open links',function(){
// const res = flow.parser.parse('graph TD;A-- text including graph space --B');
//
// const vert = flow.parser.yy.getVertices();
// const edges = flow.parser.yy.getEdges();
//
// expect(edges[0].text).toBe('text including graph space');
//
// });
});
describe('it should handle text in vertices, ', function() {
it('it should handle space', function() {
const res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar');
});
it('it should handle åäö and minus', function() {
const res = flow.parser.parse('graph TD;A-->C{Chimpansen hoppar åäö-ÅÄÖ};');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('diamond');
expect(vert['C'].text).toBe('Chimpansen hoppar åäö-ÅÄÖ');
});
it('it should handle with åäö, minus and space and br', function() {
const res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar åäö <br> - ÅÄÖ);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar åäö <br/> - ÅÄÖ');
});
// xit('it should handle åäö, minus and space and br',function(){
// const res = flow.parser.parse('graph TD; A[Object&#40;foo,bar&#41;]-->B(Thing);');
//
// const vert = flow.parser.yy.getVertices();
// const edges = flow.parser.yy.getEdges();
//
// expect(vert['C'].type).toBe('round');
// expect(vert['C'].text).toBe(' A[Object&#40;foo,bar&#41;]-->B(Thing);');
// });
it('it should handle unicode chars', function() {
const res = flow.parser.parse('graph TD;A-->C(Начало);');
const vert = flow.parser.yy.getVertices();
expect(vert['C'].text).toBe('Начало');
});
it('it should handle backslask', function() {
const res = flow.parser.parse('graph TD;A-->C(c:\\windows);');
const vert = flow.parser.yy.getVertices();
expect(vert['C'].text).toBe('c:\\windows');
});
it('it should handle CAPS', function() {
const res = flow.parser.parse('graph TD;A-->C(some CAPS);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('some CAPS');
});
it('it should handle directions', function() {
const res = flow.parser.parse('graph TD;A-->C(some URL);');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('some URL');
});
});
it('should handle multi-line text', function() {
const res = flow.parser.parse('graph TD;A--o|text space|B;\n B-->|more text with space|C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_circle');
expect(edges[1].type).toBe('arrow');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C');
expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
// expect(edges[0].text).toBe('text space');
expect(edges[1].start).toBe('B');
expect(edges[1].end).toBe('C');
expect(edges[1].text).toBe('more text with space');
});
it('should handle text in vertices with space', function() {
const res = flow.parser.parse('graph TD;A[chimpansen hoppar]-->C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('square');
expect(vert['A'].text).toBe('chimpansen hoppar');
});
it('should handle text in vertices with space with spaces between vertices and link', function() {
const res = flow.parser.parse('graph TD;A[chimpansen hoppar] --> C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('square');
expect(vert['A'].text).toBe('chimpansen hoppar');
});
it('should handle text including _ in vertices', function() {
const res = flow.parser.parse('graph TD;A[chimpansen_hoppar] --> C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('square');
expect(vert['A'].text).toBe('chimpansen_hoppar');
});
it('should handle quoted text in vertices ', function() {
const res = flow.parser.parse('graph TD;A["chimpansen hoppar ()[]"] --> C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('square');
expect(vert['A'].text).toBe('chimpansen hoppar ()[]');
});
it('should handle text in circle vertices with space', function() {
const res = flow.parser.parse('graph TD;A((chimpansen hoppar))-->C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('circle');
expect(vert['A'].text).toBe('chimpansen hoppar');
});
it('should handle text in ellipse vertices', function() {
const res = flow.parser.parse('graph TD\nA(-this is an ellipse-)-->B');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('ellipse');
expect(vert['A'].text).toBe('this is an ellipse');
});
it('should handle text in diamond vertices with space', function() {
const res = flow.parser.parse('graph TD;A(chimpansen hoppar)-->C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].type).toBe('round');
expect(vert['A'].text).toBe('chimpansen hoppar');
});
it('should handle text in with ?', function() {
const res = flow.parser.parse('graph TD;A(?)-->|?|C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].text).toBe('?');
expect(edges[0].text).toBe('?');
});
it('should handle text in with éèêàçô', function() {
const res = flow.parser.parse('graph TD;A(éèêàçô)-->|éèêàçô|C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].text).toBe('éèêàçô');
expect(edges[0].text).toBe('éèêàçô');
});
it('should handle text in with ,.?!+-*', function() {
const res = flow.parser.parse('graph TD;A(,.?!+-*)-->|,.?!+-*|C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].text).toBe(',.?!+-*');
expect(edges[0].text).toBe(',.?!+-*');
});
});

View File

@ -169,7 +169,7 @@
"{" return 'DIAMOND_START'
"}" return 'DIAMOND_STOP'
"\"" return 'QUOTE';
\n+ return 'NEWLINE';
(\r|\n|\r\n)+ return 'NEWLINE';
\s return 'SPACE';
<<EOF>> return 'EOF';
@ -313,6 +313,10 @@ vertex: idString SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| idString DIAMOND_START text DIAMOND_STOP spaceList
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$4,'hexagon');}
| idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP spaceList
{$$ = $1;yy.addVertex($1,$4,'hexagon');}
| idString TAGEND text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');}
| idString TAGEND text SQE spaceList

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,7 @@ describe('when parsing subgraphs', function() {
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(1);
expect(subgraph.nodes[0]).toBe('A');
expect(subgraph.id).toBe('s1test');
expect(subgraph.id).toBe('mermaid-dom-id-1test');
});
it('should handle subgraphs1', function() {

View File

@ -387,10 +387,11 @@ const compileTasks = function() {
const task = rawTasks[pos];
let startTime = '';
switch (rawTasks[pos].raw.startTime.type) {
case 'prevTaskEnd':
case 'prevTaskEnd': {
const prevTask = findTaskById(task.prevTaskId);
task.startTime = prevTask.endTime;
break;
}
case 'getStartDate':
startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData);
if (startTime) {
@ -501,7 +502,7 @@ const setClickFun = function(id, functionName, functionArgs) {
* @param callbackFunction A function to be executed when clicked on the task or the task's text
*/
const pushFun = function(id, callbackFunction) {
funs.push(function(element) {
funs.push(function() {
// const elem = d3.select(element).select(`[id="${id}"]`)
const elem = document.querySelector(`[id="${id}"]`);
if (elem !== null) {
@ -510,7 +511,7 @@ const pushFun = function(id, callbackFunction) {
});
}
});
funs.push(function(element) {
funs.push(function() {
// const elem = d3.select(element).select(`[id="${id}-text"]`)
const elem = document.querySelector(`[id="${id}-text"]`);
if (elem !== null) {

View File

@ -102,7 +102,7 @@ export const draw = function(text, id) {
drawToday(leftPadding, topPadding, pageWidth, pageHeight);
}
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) {
// Draw background rects covering the entire width of the graph, these form the section rows.
svg
.append('g')
@ -309,10 +309,18 @@ export const draw = function(text, id) {
if (endX + textWidth + 1.5 * conf.leftPadding > w) {
return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType;
} else {
return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType;
return (
classStr +
' taskTextOutsideRight taskTextOutside' +
secNum +
' ' +
taskType +
' width-' +
textWidth
);
}
} else {
return classStr + ' taskText taskText' + secNum + ' ' + taskType;
return classStr + ' taskText taskText' + secNum + ' ' + taskType + ' width-' + textWidth;
}
});
}
@ -393,7 +401,7 @@ export const draw = function(text, id) {
const hash = {};
const result = [];
for (let i = 0, l = arr.length; i < l; ++i) {
if (!hash.hasOwnProperty(arr[i])) {
if (!hash.hasOwnProperty(arr[i])) { // eslint-disable-line
// it works with objects! in FF, at least
hash[arr[i]] = true;
result.push(arr[i]);

View File

@ -1,4 +1,5 @@
import _ from 'lodash';
import randomString from 'crypto-random-string';
import { logger } from '../../logger';
@ -9,17 +10,11 @@ let curBranch = 'master';
let direction = 'LR';
let seq = 0;
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
function getId() {
const pool = '0123456789abcdef';
let id = '';
for (let i = 0; i < 7; i++) {
id += pool[getRandomInt(0, 16)];
}
return id;
return randomString({
length: 7,
characters: '0123456789abcdef'
});
}
function isfastforwardable(currentCommit, otherCommit) {

View File

@ -1,11 +1,24 @@
/* eslint-env jasmine */
import gitGraphAst from './gitGraphAst';
import { parser } from './parser/gitGraph';
import randomString from 'crypto-random-string';
import cryptoRandomString from 'crypto-random-string';
jest.mock('crypto-random-string');
describe('when parsing a gitGraph', function() {
let randomNumber;
beforeEach(function() {
parser.yy = gitGraphAst;
parser.yy.clear();
randomNumber = 0;
cryptoRandomString.mockImplementation(() => {
randomNumber = randomNumber + 1;
return String(randomNumber);
});
});
afterEach(function() {
cryptoRandomString.mockReset();
});
it('should handle a gitGraph defintion', function() {
const str = 'gitGraph:\n' + 'commit\n';
@ -224,4 +237,51 @@ describe('when parsing a gitGraph', function() {
parser.yy.prettyPrint();
});
it('it should generate a secure random ID for commits', function() {
const str = 'gitGraph:\n' + 'commit\n' + 'commit\n';
const EXPECTED_LENGTH = 7;
const EXPECTED_CHARACTERS = '0123456789abcdef';
let idCount = 0;
randomString.mockImplementation(options => {
if (
options.length === EXPECTED_LENGTH &&
options.characters === EXPECTED_CHARACTERS &&
Object.keys(options).length === 2
) {
const id = `abcdef${idCount}`;
idCount += 1;
return id;
}
return 'unexpected-ID';
});
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits)).toEqual(['abcdef0', 'abcdef1']);
Object.keys(commits).forEach(key => {
expect(commits[key].id).toEqual(key);
});
});
it('it should generate an array of known branches', function() {
const str =
'gitGraph:\n' +
'commit\n' +
'branch b1\n' +
'checkout b1\n' +
'commit\n' +
'commit\n' +
'branch b2\n';
parser.parse(str);
const branches = gitGraphAst.getBranchesAsObjArray();
expect(branches).toHaveLength(3);
expect(branches[0]).toHaveProperty('name', 'master');
expect(branches[1]).toHaveProperty('name', 'b1');
expect(branches[2]).toHaveProperty('name', 'b2');
});
});

View File

@ -303,7 +303,7 @@ export const draw = function(txt, id, ver) {
const parser = gitGraphParser.parser;
parser.yy = db;
logger.debug('in gitgraph renderer', txt, id, ver);
logger.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
// Parse the graph definition
parser.parse(txt + '\n');

View File

@ -21,7 +21,7 @@ export const setConf = function(cnf) {
* @param id
*/
let w;
export const draw = (txt, id, ver) => {
export const draw = (txt, id) => {
try {
const parser = pieParser.parser;
parser.yy = pieData;
@ -50,6 +50,8 @@ export const draw = (txt, id, ver) => {
var width = w; // 450
var height = 450;
var margin = 40;
var legendRectSize = 18;
var legendSpacing = 4;
var radius = Math.min(width, height) / 2 - margin;
@ -62,6 +64,10 @@ export const draw = (txt, id, ver) => {
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
var data = pieData.getSections();
var sum = 0;
Object.keys(data).forEach(function(key) {
sum += data[key];
});
logger.info(data);
// set the color scale
@ -75,7 +81,6 @@ export const draw = (txt, id, ver) => {
return d.value;
});
var dataReady = pie(d3.entries(data));
// Now I know that group A goes from 0 degrees to x degrees and so on.
// shape helper to build arcs:
var arcGenerator = d3
@ -97,19 +102,20 @@ export const draw = (txt, id, ver) => {
.style('stroke-width', '2px')
.style('opacity', 0.7);
// Now add the annotation. Use the centroid method to get the best coordinates
// Now add the Percentage. Use the centroid method to get the best coordinates
svg
.selectAll('mySlices')
.data(dataReady)
.enter()
.append('text')
.text(function(d) {
return d.data.key;
return ((d.data.value / sum) * 100).toFixed(0) + '%';
})
.attr('transform', function(d) {
return 'translate(' + arcGenerator.centroid(d) + ')';
})
.style('text-anchor', 'middle')
.attr('class', 'slice')
.style('font-size', 17);
svg
@ -118,6 +124,36 @@ export const draw = (txt, id, ver) => {
.attr('x', 0)
.attr('y', -(h - 50) / 2)
.attr('class', 'pieTitleText');
//Add the slegend/annotations for each section
var legend = svg
.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = (height * color.domain().length) / 2;
var horz = 12 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend
.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend
.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) {
return d;
});
} catch (e) {
logger.error('Error while rendering info diagram');
logger.error(e.message);

View File

@ -15,7 +15,7 @@ describe('when parsing a sequenceDiagram', function() {
parser.yy = sequenceDb;
parser.yy.clear();
});
it('it should handle a sequenceDiagram defintion', function() {
it('it should handle a sequenceDiagram definition', function() {
const str =
'sequenceDiagram\n' +
'Alice->Bob:Hello Bob, how are you?\n' +

View File

@ -230,13 +230,22 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
const g = elem.append('g');
const txtCenter = startx + (stopx - startx) / 2;
const textElem = g
.append('text') // text label for the x axis
.attr('x', txtCenter)
.attr('y', verticalPos - 7)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(msg.message);
let textElem;
let counterBreaklines = 0;
let breaklineOffset = 17;
const breaklines = msg.message.split(/<br\/?>/gi);
for (const breakline of breaklines) {
textElem = g
.append('text') // text label for the x axis
.attr('x', txtCenter)
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(breakline.trim());
counterBreaklines++;
}
const offsetLineCounter = counterBreaklines - 1;
const totalOffset = offsetLineCounter * breaklineOffset;
let textWidth = (textElem._groups || textElem)[0][0].getBBox().width;
@ -247,8 +256,9 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
.append('path')
.attr(
'd',
`M ${startx},${verticalPos} H ${startx + conf.width / 2} V ${verticalPos +
25} H ${startx}`
`M ${startx},${verticalPos + totalOffset} H ${startx + conf.width / 2} V ${verticalPos +
25 +
totalOffset} H ${startx}`
);
} else {
line = g
@ -258,32 +268,42 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
'M ' +
startx +
',' +
verticalPos +
(verticalPos + totalOffset) +
' C ' +
(startx + 60) +
',' +
(verticalPos - 10) +
(verticalPos - 10 + totalOffset) +
' ' +
(startx + 60) +
',' +
(verticalPos + 30) +
(verticalPos + 30 + totalOffset) +
' ' +
startx +
',' +
(verticalPos + 20)
(verticalPos + 20 + totalOffset)
);
}
bounds.bumpVerticalPos(30);
bounds.bumpVerticalPos(30 + totalOffset);
const dx = Math.max(textWidth / 2, 100);
bounds.insert(startx - dx, bounds.getVerticalPos() - 10, stopx + dx, bounds.getVerticalPos());
bounds.insert(
startx - dx,
bounds.getVerticalPos() - 10 + totalOffset,
stopx + dx,
bounds.getVerticalPos() + totalOffset
);
} else {
line = g.append('line');
line.attr('x1', startx);
line.attr('y1', verticalPos);
line.attr('x2', stopx);
line.attr('y2', verticalPos);
bounds.insert(startx, bounds.getVerticalPos() - 10, stopx, bounds.getVerticalPos());
bounds.insert(
startx,
bounds.getVerticalPos() - 10 + totalOffset,
stopx,
bounds.getVerticalPos() + totalOffset
);
}
// Make an SVG Container
// Draw the line
@ -362,6 +382,7 @@ export const setConf = function(cnf) {
keys.forEach(function(key) {
conf[key] = cnf[key];
});
conf.actorFontFamily = cnf.fontFamily;
};
const actorActivations = function(actor) {
@ -493,11 +514,12 @@ export const draw = function(text, id) {
bounds.newLoop(undefined, msg.message);
bounds.bumpVerticalPos(conf.boxMargin);
break;
case parser.yy.LINETYPE.RECT_END:
case parser.yy.LINETYPE.RECT_END: {
const rectData = bounds.endLoop();
svgDraw.drawBackgroundRect(diagram, rectData);
bounds.bumpVerticalPos(conf.boxMargin);
break;
}
case parser.yy.LINETYPE.OPT_START:
bounds.bumpVerticalPos(conf.boxMargin);
bounds.newLoop(msg.message);

View File

@ -16,7 +16,7 @@ export const drawRect = function(elem, rectData) {
return rectElem;
};
export const drawText = function(elem, textData, width) {
export const drawText = function(elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(/<br\/?>/gi, ' ');
@ -374,7 +374,7 @@ const _drawTextCandidateFunc = (function() {
function _setTextAttrs(toText, fromTextAttrsDict) {
for (const key in fromTextAttrsDict) {
if (fromTextAttrsDict.hasOwnProperty(key)) {
if (fromTextAttrsDict.hasOwnProperty(key)) { // eslint-disable-line
toText.attr(key, fromTextAttrsDict[key]);
}
}

View File

@ -0,0 +1,16 @@
const idCache = {};
export const set = (key, val) => {
idCache[key] = val;
};
export const get = k => idCache[k];
export const keys = () => Object.keys(idCache);
export const size = () => keys().length;
export default {
get,
set,
keys,
size
};

View File

@ -0,0 +1,179 @@
/** mermaid
* https://mermaidjs.github.io/
* (c) 2014-2015 Knut Sveidqvist
* MIT license.
*
* Based on js sequence diagrams jison grammr
* http://bramp.github.io/js-sequence-diagrams/
* (c) 2012-2013 Andrew Brampton (bramp.net)
* Simplified BSD license.
*/
%lex
%options case-insensitive
// Special states for recognizing aliases
%x ID
%x STATE
%x FORK_STATE
%x STATE_STRING
%x STATE_ID
%x ALIAS
%x SCALE
%x NOTE
%x NOTE_ID
%x NOTE_TEXT
%x FLOATING_NOTE
%x FLOATING_NOTE_ID
%x struct
// A special state for grabbing text up to the first comment/newline
%x LINE
%%
[\n]+ return 'NL';
\s+ /* skip all whitespace */
<ID,STATE,struct,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,STATE,struct,LINE>\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
<SCALE>\d+ return 'WIDTH';
<SCALE>\s+"width" {this.popState();}
<INITIAL,struct>"state"\s+ { this.pushState('STATE'); }
<STATE>.*"<<fork>>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"<<join>>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>["] this.begin("STATE_STRING");
<STATE>"as"\s* {this.popState();this.pushState('STATE_ID');return "AS";}
<STATE_ID>[^\n\{]* {this.popState();/* console.log('STATE_ID', yytext);*/return "ID";}
<STATE_STRING>["] this.popState();
<STATE_STRING>[^"]* { /*console.log('Long description:', yytext);*/return "STATE_DESCR";}
<STATE>[^\n\s\{]+ {/*console.log('COMPOSIT_STATE', yytext);*/return 'COMPOSIT_STATE';}
<STATE>\n {this.popState();}
<INITIAL,STATE>\{ {this.popState();this.pushState('struct'); /*console.log('begin struct', yytext);*/return 'STRUCT_START';}
<struct>\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';}}
<struct>[\n] /* nothing */
<INITIAL,struct>"note"\s+ { this.begin('NOTE'); return 'note'; }
<NOTE>"left of" { this.popState();this.pushState('NOTE_ID');return 'left_of';}
<NOTE>"right of" { this.popState();this.pushState('NOTE_ID');return 'right_of';}
<NOTE>\" { this.popState();this.pushState('FLOATING_NOTE');}
<FLOATING_NOTE>\s*"as"\s* {this.popState();this.pushState('FLOATING_NOTE_ID');return "AS";}
<FLOATING_NOTE>["] /**/
<FLOATING_NOTE>[^"]* { /*console.log('Floating note text: ', yytext);*/return "NOTE_TEXT";}
<FLOATING_NOTE_ID>[^\n]* {this.popState();/*console.log('Floating note ID', yytext);*/return "ID";}
<NOTE_ID>\s*[^:\n\s\-]+ { this.popState();this.pushState('NOTE_TEXT');/*console.log('Got ID for note', yytext);*/return 'ID';}
<NOTE_TEXT>\s*":"[^:\n;]+ { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim();return 'NOTE_TEXT';}
<NOTE_TEXT>\s*[^:;]+"end note" { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim();return 'NOTE_TEXT';}
"stateDiagram"\s+ { /*console.log('Got state diagram', yytext,'#');*/return 'SD'; }
"hide empty description" { /*console.log('HIDE_EMPTY', yytext,'#');*/return 'HIDE_EMPTY'; }
<INITIAL,struct>"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';}
<INITIAL,struct>[^:\n\s\-\{]+ { /*console.log('=>ID=',yytext);*/ return 'ID';}
// <INITIAL,struct>\s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; }
<INITIAL,struct>\s*":"[^:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; }
<INITIAL,struct>"-->" return '-->';
<struct>"--" return 'CONCURRENT';
<<EOF>> return 'NL';
. return 'INVALID';
/lex
%left '^'
%start start
%% /* language grammar */
start
: SPACE start
| NL start
| SD document { /*console.warn('Root document', $2);*/ yy.setRootDoc($2);return $2; }
;
document
: /* empty */ { $$ = [] }
| document line {
if($2!='nl'){
$1.push($2);$$ = $1
}
// console.warn('Got document',$1, $2);
}
;
line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NL { $$='nl';}
;
statement
: idStatement { /*console.warn('got id and descr', $1);*/$$={ stmt: 'state', id: $1, type: 'default', description: ''};}
| idStatement DESCR { /*console.warn('got id and descr', $1, $2.trim());*/$$={ stmt: 'state', id: $1, type: 'default', description: $2.trim()};}
| idStatement '-->' idStatement
{
/*console.warn('got id', $1);yy.addRelation($1, $3);*/
$$={ stmt: 'relation', state1: { stmt: 'state', id: $1, type: 'default', description: '' }, state2:{ stmt: 'state', id: $3 ,type: 'default', description: ''}};
}
| idStatement '-->' idStatement DESCR
{
/*yy.addRelation($1, $3, $4.substr(1).trim());*/
$$={ stmt: 'relation', state1: { stmt: 'state', id: $1, type: 'default', description: '' }, state2:{ stmt: 'state', id: $3 ,type: 'default', description: ''}, description: $4.substr(1).trim()};
}
| HIDE_EMPTY
| scale WIDTH
| COMPOSIT_STATE
| COMPOSIT_STATE STRUCT_START document STRUCT_STOP
{
/* console.warn('Adding document for state without id ', $1);*/
$$={ stmt: 'state', id: $1, type: 'default', description: '', doc: $3 }
}
| STATE_DESCR AS ID {
var id=$3;
var description = $1.trim();
if($3.match(':')){
var parts = $3.split(':');
id=parts[0];
description = [description, parts[1]];
}
$$={stmt: 'state', id: id, type: 'default', description: description};
}
| STATE_DESCR AS ID STRUCT_START document STRUCT_STOP
{
//console.warn('Adding document for state with id ', $3, $4); yy.addDocument($3);
$$={ stmt: 'state', id: $3, type: 'default', description: $1, doc: $5 }
}
| FORK {
$$={ stmt: 'state', id: $1, type: 'fork' }
}
| JOIN {
$$={ stmt: 'state', id: $1, type: 'join' }
}
| CONCURRENT {
$$={ stmt: 'state', id: yy.getDividerId(), type: 'divider' }
}
| note notePosition ID NOTE_TEXT
{
/*console.warn('got NOTE, position: ', $2.trim(), 'id = ', $3.trim(), 'note: ', $4);*/
$$={ stmt: 'state', id: $3.trim(), note:{position: $2.trim(), text: $4.trim()}};
}
| note NOTE_TEXT AS ID
;
idStatement
: ID {$$=$1;}
| EDGE_STATE {$$=$1;}
;
notePosition
: left_of
| right_of
;
%%

View File

@ -0,0 +1,477 @@
import * as d3 from 'd3';
import idCache from './id-cache.js';
import stateDb from './stateDb';
import utils from '../../utils';
import { getConfig } from '../../config';
// let conf;
/**
* Draws a start state as a black circle
*/
export const drawStartState = g =>
g
.append('circle')
.style('stroke', 'black')
.style('fill', 'black')
.attr('r', getConfig().state.sizeUnit)
.attr('cx', getConfig().state.padding + getConfig().state.sizeUnit)
.attr('cy', getConfig().state.padding + getConfig().state.sizeUnit);
/**
* Draws a start state as a black circle
*/
export const drawDivider = g =>
g
.append('line')
.style('stroke', 'grey')
.style('stroke-dasharray', '3')
.attr('x1', getConfig().state.textHeight)
.attr('class', 'divider')
.attr('x2', getConfig().state.textHeight * 2)
.attr('y1', 0)
.attr('y2', 0);
/**
* Draws a an end state as a black circle
*/
export const drawSimpleState = (g, stateDef) => {
const state = g
.append('text')
.attr('x', 2 * getConfig().state.padding)
.attr('y', getConfig().state.textHeight + 2 * getConfig().state.padding)
.attr('font-size', getConfig().state.fontSize)
.attr('class', 'state-title')
.text(stateDef.id);
const classBox = state.node().getBBox();
g.insert('rect', ':first-child')
.attr('x', getConfig().state.padding)
.attr('y', getConfig().state.padding)
.attr('width', classBox.width + 2 * getConfig().state.padding)
.attr('height', classBox.height + 2 * getConfig().state.padding)
.attr('rx', getConfig().state.radius);
return state;
};
/**
* Draws a state with descriptions
* @param {*} g
* @param {*} stateDef
*/
export const drawDescrState = (g, stateDef) => {
const addTspan = function(textEl, txt, isFirst) {
const tSpan = textEl
.append('tspan')
.attr('x', 2 * getConfig().state.padding)
.text(txt);
if (!isFirst) {
tSpan.attr('dy', getConfig().state.textHeight);
}
};
const title = g
.append('text')
.attr('x', 2 * getConfig().state.padding)
.attr('y', getConfig().state.textHeight + 1.5 * getConfig().state.padding)
.attr('font-size', getConfig().state.fontSize)
.attr('class', 'state-title')
.text(stateDef.descriptions[0]);
const titleBox = title.node().getBBox();
const titleHeight = titleBox.height;
const description = g
.append('text') // text label for the x axis
.attr('x', getConfig().state.padding)
.attr(
'y',
titleHeight +
getConfig().state.padding * 0.2 +
getConfig().state.dividerMargin +
getConfig().state.textHeight
)
.attr('class', 'state-description');
let isFirst = true;
let isSecond = true;
stateDef.descriptions.forEach(function(descr) {
if (!isFirst) {
addTspan(description, descr, isSecond);
isSecond = false;
}
isFirst = false;
});
const descrLine = g
.append('line') // text label for the x axis
.attr('x1', getConfig().state.padding)
.attr('y1', getConfig().state.padding + titleHeight + getConfig().state.dividerMargin / 2)
.attr('y2', getConfig().state.padding + titleHeight + getConfig().state.dividerMargin / 2)
.attr('class', 'descr-divider');
const descrBox = description.node().getBBox();
const width = Math.max(descrBox.width, titleBox.width);
descrLine.attr('x2', width + 3 * getConfig().state.padding);
// const classBox = title.node().getBBox();
g.insert('rect', ':first-child')
.attr('x', getConfig().state.padding)
.attr('y', getConfig().state.padding)
.attr('width', width + 2 * getConfig().state.padding)
.attr('height', descrBox.height + titleHeight + 2 * getConfig().state.padding)
.attr('rx', getConfig().state.radius);
return g;
};
/**
* Adds the creates a box around the existing content and adds a
* panel for the id on top of the content.
*/
export const addIdAndBox = (g, stateDef) => {
// TODO Move hardcodings to conf
// const addTspan = function(textEl, txt, isFirst) {
// const tSpan = textEl
// .append('tspan')
// .attr('x', 2 * getConfig().state.padding)
// .text(txt);
// if (!isFirst) {
// tSpan.attr('dy', getConfig().state.textHeight);
// }
// };
const title = g
.append('text')
.attr('x', 2 * getConfig().state.padding)
.attr('y', getConfig().state.titleShift)
.attr('font-size', getConfig().state.fontSize)
.attr('class', 'state-title')
.text(stateDef.id);
const titleBox = title.node().getBBox();
const lineY = 1 - getConfig().state.textHeight;
const descrLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', lineY)
.attr('y2', lineY)
.attr('class', 'descr-divider');
const graphBox = g.node().getBBox();
title.attr('x', graphBox.width / 2 - titleBox.width / 2);
descrLine.attr('x2', graphBox.width + getConfig().state.padding);
// White color
g.insert('rect', ':first-child')
.attr('x', graphBox.x)
.attr('y', lineY)
.attr('class', 'composit')
.attr('width', graphBox.width + getConfig().state.padding)
.attr(
'height',
graphBox.height + getConfig().state.textHeight + getConfig().state.titleShift + 1
)
.attr('rx', '0');
// Title background
g.insert('rect', ':first-child')
.attr('x', graphBox.x)
.attr(
'y',
getConfig().state.titleShift - getConfig().state.textHeight - getConfig().state.padding
)
.attr('width', graphBox.width + getConfig().state.padding)
// Just needs to be higher then the descr line, will be clipped by the white color box
.attr('height', getConfig().state.textHeight * 3)
.attr('rx', getConfig().state.radius);
// Full background
g.insert('rect', ':first-child')
.attr('x', graphBox.x)
.attr(
'y',
getConfig().state.titleShift - getConfig().state.textHeight - getConfig().state.padding
)
.attr('width', graphBox.width + getConfig().state.padding)
.attr('height', graphBox.height + 3 + 2 * getConfig().state.textHeight)
.attr('rx', getConfig().state.radius);
return g;
};
const drawEndState = g => {
g.append('circle')
.style('stroke', 'black')
.style('fill', 'white')
.attr('r', getConfig().state.sizeUnit + getConfig().state.miniPadding)
.attr(
'cx',
getConfig().state.padding + getConfig().state.sizeUnit + getConfig().state.miniPadding
)
.attr(
'cy',
getConfig().state.padding + getConfig().state.sizeUnit + getConfig().state.miniPadding
);
return g
.append('circle')
.style('stroke', 'black')
.style('fill', 'black')
.attr('r', getConfig().state.sizeUnit)
.attr('cx', getConfig().state.padding + getConfig().state.sizeUnit + 2)
.attr('cy', getConfig().state.padding + getConfig().state.sizeUnit + 2);
};
const drawForkJoinState = (g, stateDef) => {
let width = getConfig().state.forkWidth;
let height = getConfig().state.forkHeight;
if (stateDef.parentId) {
let tmp = width;
width = height;
height = tmp;
}
return g
.append('rect')
.style('stroke', 'black')
.style('fill', 'black')
.attr('width', width)
.attr('height', height)
.attr('x', getConfig().state.padding)
.attr('y', getConfig().state.padding);
};
export const drawText = function(elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(/<br\/?>/gi, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
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;
};
const _drawLongText = (_text, x, y, g) => {
let textHeight = 0;
const textElem = g.append('text');
textElem.style('text-anchor', 'start');
textElem.attr('class', 'noteText');
let text = _text.replace(/\r\n/g, '<br/>');
text = text.replace(/\n/g, '<br/>');
const lines = text.split(/<br\/?>/gi);
let tHeight = 1.25 * getConfig().state.noteMargin;
for (const line of lines) {
const txt = line.trim();
if (txt.length > 0) {
const span = textElem.append('tspan');
span.text(txt);
if (tHeight === 0) {
const textBounds = span.node().getBBox();
tHeight += textBounds.height;
}
// console.warn('textBounds', textBounds);
textHeight += tHeight;
span.attr('x', x + getConfig().state.noteMargin);
span.attr('y', y + textHeight + 1.25 * getConfig().state.noteMargin);
}
}
return { textWidth: textElem.node().getBBox().width, textHeight };
};
/**
* Draws an actor in the diagram with the attaced line
* @param center - The center of the the actor
* @param pos The position if the actor in the liost of actors
* @param description The text in the box
*/
export const drawNote = (text, g) => {
g.attr('class', 'state-note');
const note = g
.append('rect')
.attr('x', 0)
.attr('y', getConfig().state.padding);
const rectElem = g.append('g');
const { textWidth, textHeight } = _drawLongText(text, 0, 0, rectElem);
note.attr('height', textHeight + 2 * getConfig().state.noteMargin);
note.attr('width', textWidth + getConfig().state.noteMargin * 2);
return note;
};
/**
* Starting point for drawing a state. The function finds out the specifics
* about the state and renders with approprtiate function.
* @param {*} elem
* @param {*} stateDef
*/
export const drawState = function(elem, stateDef) {
const id = stateDef.id;
const stateInfo = {
id: id,
label: stateDef.id,
width: 0,
height: 0
};
const g = elem
.append('g')
.attr('id', id)
.attr('class', 'stateGroup');
if (stateDef.type === 'start') drawStartState(g);
if (stateDef.type === 'end') drawEndState(g);
if (stateDef.type === 'fork' || stateDef.type === 'join') drawForkJoinState(g, stateDef);
if (stateDef.type === 'note') drawNote(stateDef.note.text, g);
if (stateDef.type === 'divider') drawDivider(g);
if (stateDef.type === 'default' && stateDef.descriptions.length === 0)
drawSimpleState(g, stateDef);
if (stateDef.type === 'default' && stateDef.descriptions.length > 0) drawDescrState(g, stateDef);
const stateBox = g.node().getBBox();
stateInfo.width = stateBox.width + 2 * getConfig().state.padding;
stateInfo.height = stateBox.height + 2 * getConfig().state.padding;
idCache.set(id, stateInfo);
// stateCnt++;
return stateInfo;
};
const getRows = s => {
let str = s.replace(/<br\/?>/gi, '#br#');
str = str.replace(/\\n/g, '#br#');
return str.split('#br#');
};
let edgeCount = 0;
export const drawEdge = function(elem, path, relation) {
const getRelationType = function(type) {
switch (type) {
case stateDb.relationType.AGGREGATION:
return 'aggregation';
case stateDb.relationType.EXTENSION:
return 'extension';
case stateDb.relationType.COMPOSITION:
return 'composition';
case stateDb.relationType.DEPENDENCY:
return 'dependency';
}
};
path.points = path.points.filter(p => !Number.isNaN(p.y));
// The data for our line
const lineData = path.points;
// This is the accessor function we talked about above
const lineFunction = d3
.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.curve(d3.curveBasis);
const svgPath = elem
.append('path')
.attr('d', lineFunction(lineData))
.attr('id', 'edge' + edgeCount)
.attr('class', 'transition');
let url = '';
if (getConfig().state.arrowMarkerAbsolute) {
url =
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
}
svgPath.attr(
'marker-end',
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')'
);
if (typeof relation.title !== 'undefined') {
const label = elem.append('g').attr('class', 'stateLabel');
const { x, y } = utils.calcLabelPosition(path.points);
const rows = getRows(relation.title);
// console.warn(rows);
let titleHeight = 0;
const titleRows = [];
for (let i = 0; i <= rows.length; i++) {
const title = label
.append('text')
.attr('text-anchor', 'middle')
.text(rows[i])
.attr('x', x)
.attr('y', y + titleHeight);
if (titleHeight === 0) {
const titleBox = title.node().getBBox();
titleHeight = titleBox.height;
}
titleRows.push(title);
}
if (rows.length > 1) {
const heightAdj = rows.length * titleHeight * 0.25;
titleRows.forEach((title, i) => title.attr('y', y + i * titleHeight - heightAdj));
}
const bounds = label.node().getBBox();
label
.insert('rect', ':first-child')
.attr('class', 'box')
.attr('x', bounds.x - getConfig().state.padding / 2)
.attr('y', bounds.y - getConfig().state.padding / 2)
.attr('width', bounds.width + getConfig().state.padding)
.attr('height', bounds.height + getConfig().state.padding);
//label.attr('transform', '0 -' + (bounds.y / 2));
// Debug points
// path.points.forEach(point => {
// g.append('circle')
// .style('stroke', 'red')
// .style('fill', 'red')
// .attr('r', 1)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
// g.append('circle')
// .style('stroke', 'blue')
// .style('fill', 'blue')
// .attr('r', 1)
// .attr('cx', x)
// .attr('cy', y);
}
edgeCount++;
};

View File

@ -0,0 +1,171 @@
import { logger } from '../../logger';
let rootDoc = [];
const setRootDoc = o => {
logger.info('Setting root doc', o);
rootDoc = o;
};
const getRootDoc = () => rootDoc;
const extract = doc => {
// const res = { states: [], relations: [] };
clear();
doc.forEach(item => {
if (item.stmt === 'state') {
addState(item.id, item.type, item.doc, item.description, item.note);
}
if (item.stmt === 'relation') {
addRelation(item.state1.id, item.state2.id, item.description);
}
});
};
const newDoc = () => {
return {
relations: [],
states: {},
documents: {}
};
};
let documents = {
root: newDoc()
};
let currentDocument = documents.root;
let startCnt = 0;
let endCnt = 0; // eslint-disable-line
// let stateCnt = 0;
/**
* Function called by parser when a node definition has been found.
* @param id
* @param text
* @param type
* @param style
*/
export const addState = function(id, type, doc, descr, note) {
if (typeof currentDocument.states[id] === 'undefined') {
currentDocument.states[id] = {
id: id,
descriptions: [],
type,
doc,
note
};
} else {
if (!currentDocument.states[id].doc) {
currentDocument.states[id].doc = doc;
}
if (!currentDocument.states[id].type) {
currentDocument.states[id].type = type;
}
}
if (descr) {
if (typeof descr === 'string') addDescription(id, descr.trim());
if (typeof descr === 'object') {
descr.forEach(des => addDescription(id, des.trim()));
}
}
if (note) currentDocument.states[id].note = note;
};
export const clear = function() {
documents = {
root: newDoc()
};
currentDocument = documents.root;
};
export const getState = function(id) {
return currentDocument.states[id];
};
export const getStates = function() {
return currentDocument.states;
};
export const logDocuments = function() {
logger.info('Documents = ', documents);
};
export const getRelations = function() {
return currentDocument.relations;
};
export const addRelation = function(_id1, _id2, title) {
let id1 = _id1;
let id2 = _id2;
let type1 = 'default';
let type2 = 'default';
if (_id1 === '[*]') {
startCnt++;
id1 = 'start' + startCnt;
type1 = 'start';
}
if (_id2 === '[*]') {
endCnt++;
id2 = 'end' + startCnt;
type2 = 'end';
}
addState(id1, type1);
addState(id2, type2);
currentDocument.relations.push({ id1, id2, title });
};
const addDescription = function(id, _descr) {
const theState = currentDocument.states[id];
let descr = _descr;
if (descr[0] === ':') {
descr = descr.substr(1).trim();
}
theState.descriptions.push(descr);
};
export const cleanupLabel = function(label) {
if (label.substring(0, 1) === ':') {
return label.substr(2).trim();
} else {
return label.trim();
}
};
export const lineType = {
LINE: 0,
DOTTED_LINE: 1
};
let dividerCnt = 0;
const getDividerId = () => {
dividerCnt++;
return 'divider-id-' + dividerCnt;
};
export const relationType = {
AGGREGATION: 0,
EXTENSION: 1,
COMPOSITION: 2,
DEPENDENCY: 3
};
export default {
addState,
clear,
getState,
getStates,
getRelations,
addRelation,
getDividerId,
// addDescription,
cleanupLabel,
lineType,
relationType,
logDocuments,
getRootDoc,
setRootDoc,
extract
};

View File

@ -0,0 +1,316 @@
/* eslint-env jasmine */
import { parser } from './parser/stateDiagram';
import stateDb from './stateDb';
describe('state diagram, ', function() {
describe('when parsing an info graph it', function() {
beforeEach(function() {
parser.yy = stateDb;
});
it('super simple', function() {
const str = `
stateDiagram
[*] --> State1
State1 --> [*]
`;
parser.parse(str);
});
it('simple', function() {
const str = `stateDiagram\n
State1 : this is another string
[*] --> State1
State1 --> [*]
`;
parser.parse(str);
});
it('should handle relation definitions', function() {
const str = `stateDiagram\n
[*] --> State1
State1 --> [*]
State1 : this is a string
State1 : this is another string
State1 --> State2
State2 --> [*]
`;
parser.parse(str);
});
it('hide empty description', function() {
const str = `stateDiagram\n
hide empty description
[*] --> State1
State1 --> [*]
State1 : this is a string
State1 : this is another string
State1 --> State2
State2 --> [*]
`;
parser.parse(str);
});
it('scale', function() {
const str = `stateDiagram\n
scale 350 width
[*] --> State1
State1 --> [*]
State1 : this is a string with - in it
State1 : this is another string
State1 --> State2
State2 --> [*]
`;
parser.parse(str);
});
it('description after second state', function() {
const str = `stateDiagram\n
scale 350 width
[*] --> State1 : This is the description with - in it
State1 --> [*]
`;
parser.parse(str);
});
it('shall handle descriptions inkluding minus signs', function() {
const str = `stateDiagram\n
scale 350 width
[*] --> State1 : This is the description +-!
State1 --> [*]
`;
parser.parse(str);
});
it('should handle state statements', function() {
const str = `stateDiagram\n
state Configuring {
[*] --> NewValueSelection
NewValueSelection --> NewValuePreview : EvNewValue
NewValuePreview --> NewValueSelection : EvNewValueRejected
NewValuePreview --> NewValueSelection : EvNewValueSaved1
}
`;
parser.parse(str);
});
it('should handle recursive state definitions', function() {
const str = `stateDiagram\n
state Configuring {
[*] --> NewValueSelection
NewValueSelection --> NewValuePreview : EvNewValue
NewValuePreview --> NewValueSelection : EvNewValueRejected
NewValuePreview --> NewValueSelection : EvNewValueSaved
state NewValuePreview {
State1 --> State2
}
}
`;
parser.parse(str);
});
it('should handle multiple recursive state definitions', function() {
const str = `stateDiagram\n
scale 350 width
[*] --> NotShooting
state NotShooting {
[*] --> Idle
Idle --> Configuring : EvConfig
Configuring --> Idle : EvConfig
}
state Configuring {
[*] --> NewValueSelection
NewValueSelection --> NewValuePreview : EvNewValue
NewValuePreview --> NewValueSelection : EvNewValueRejected
NewValuePreview --> NewValueSelection : EvNewValueSaved
state NewValuePreview {
State1 --> State2
}
}
`;
parser.parse(str);
});
it('should handle state deifintions with separation of id', function() {
const str = `stateDiagram\n
state "Long state description" as state1
`;
parser.parse(str);
});
it('should handle state deifintions with separation of id', function() {
const str = `stateDiagram
state "Not Shooting State" as NotShooting {
state "Idle mode" as Idle
state "Configuring mode" as Configuring
[*] --> Idle
Idle --> Configuring : EvConfig
Configuring --> Idle : EvConfig
}
`;
parser.parse(str);
});
it('should State definition with quotes', function() {
const str = `stateDiagram\n
scale 600 width
[*] --> State1
State1 --> State2 : Succeeded
State1 --> [*] : Aborted
State2 --> State3 : Succeeded
State2 --> [*] : Aborted
state State3 {
state "Accumulate Enough Data\nLong State Name" as long1
long1 : Just a test
[*] --> long1
long1 --> long1 : New Data
long1 --> ProcessData : Enough Data
}
State3 --> State3 : Failed
State3 --> [*] : Succeeded / Save Result
State3 --> [*] : Aborted
`;
parser.parse(str);
});
it('should handle fork statements', function() {
const str = `stateDiagram\n
state fork_state <<fork>>
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state <<join>>
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
`;
parser.parse(str);
});
it('should handle concurrent state', function() {
const str = `stateDiagram\n
[*] --> Active
state Active {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
--
[*] --> CapsLockOff
CapsLockOff --> CapsLockOn : EvCapsLockPressed
CapsLockOn --> CapsLockOff : EvCapsLockPressed
--
[*] --> ScrollLockOff
ScrollLockOff --> ScrollLockOn : EvCapsLockPressed
ScrollLockOn --> ScrollLockOff : EvCapsLockPressed
}
`;
parser.parse(str);
});
it('should handle concurrent state', function() {
const str = `stateDiagram\n
[*] --> Active
state Active {
[*] --> NumLockOff
--
[*] --> CapsLockOff
--
[*] --> ScrollLockOff
}
`;
parser.parse(str);
});
// it('should handle arrow directions definitions', function() {
// const str = `stateDiagram\n
// [*] -up-> First
// First -right-> Second
// Second --> Third
// Third -left-> Last
// `;
// parser.parse(str);
// });
it('should handle note statements', function() {
const str = `stateDiagram\n
[*] --> Active
Active --> Inactive
note left of Active : this is a short<br/>note
note right of Inactive
A note can also
be defined on
several lines
end note
`;
parser.parse(str);
});
it('should handle floating notes', function() {
const str = `stateDiagram
foo: bar
note "This is a floating note" as N1
`;
parser.parse(str);
});
it('should handle floating notes', function() {
const str = `stateDiagram\n
state foo
note "This is a floating note" as N1
`;
parser.parse(str);
});
it('should handle notes for composit states', function() {
const str = `stateDiagram\n
[*] --> NotShooting
state "Not Shooting State" as NotShooting {
state "Idle mode" as Idle
state "Configuring mode" as Configuring
[*] --> Idle
Idle --> Configuring : EvConfig
Configuring --> Idle : EvConfig
}
note right of NotShooting : This is a note on a composite state
`;
parser.parse(str);
});
xit('should handle if statements', function() {
const str = `stateDiagram\n
[*] --> "Order Submitted"
if "Payment Accepted" then
-->[yes] "Pack products"
--> "Send parcel"
-right-> (*)
else
->[no] "Send error message"
-->[Cancel Order] [*]
endif
}
note right of NotShooting : This is a note on a composite state
`;
parser.parse(str);
});
});
});

View File

@ -0,0 +1,319 @@
import * as d3 from 'd3';
import dagre from 'dagre';
import graphlib from 'graphlib';
import { logger } from '../../logger';
import stateDb from './stateDb';
import { parser } from './parser/stateDiagram';
// import idCache from './id-cache';
import { drawState, addIdAndBox, drawEdge } from './shapes';
import { getConfig } from '../../config';
parser.yy = stateDb;
// TODO Move conf object to main conf in mermaidAPI
let conf;
// {
// // Used
// padding: 5,
// // Font size factor, this is used to guess the width of the edges labels before rendering by dagre
// // layout. This might need updating if/when switching font
// fontSizeFactor: 5.02,
// labelHeight: 16,
// edgeLengthFactor: '20',
// compositTitleSize: 35
// };
const transformationLog = {};
export const setConf = function() {};
// Todo optimize
// const getGraphId = function(label) {
// const keys = idCache.keys();
// for (let i = 0; i < keys.length; i++) {
// if (idCache.get(keys[i]).label === label) {
// return keys[i];
// }
// }
// return undefined;
// };
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*/
const insertMarkers = function(elem) {
elem
.append('defs')
.append('marker')
.attr('id', 'dependencyEnd')
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
* @param text
* @param id
*/
export const draw = function(text, id) {
conf = getConfig().state;
parser.yy.clear();
parser.parse(text);
logger.debug('Rendering diagram ' + text);
// /// / Fetch the default direction, use TD if none was found
const diagram = d3.select(`[id='${id}']`);
insertMarkers(diagram);
// // Layout graph, Create a new directed graph
const graph = new graphlib.Graph({
multigraph: false,
compound: true,
// acyclicer: 'greedy',
rankdir: 'RL'
// ranksep: '20'
});
// // Default to assigning a new object as a label for each new edge.
graph.setDefaultEdgeLabel(function() {
return {};
});
const rootDoc = stateDb.getRootDoc();
renderDoc(rootDoc, diagram);
const padding = conf.padding;
const bounds = diagram.node().getBBox();
console.warn(bounds);
const width = bounds.width + padding * 2;
const height = bounds.height + padding * 2;
// diagram.attr('height', '100%');
// diagram.attr('style', `width: ${bounds.width * 3 + conf.padding * 2};`);
// diagram.attr('height', height);
// Zoom in a bit
diagram.attr('width', width * 2);
// diagram.attr('height', bounds.height * 3 + conf.padding * 2);
diagram.attr(
'viewBox',
`${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height
);
// diagram.attr('transform', `translate(, 0)`);
// diagram.attr(
// 'viewBox',
// `${conf.padding * -1} ${conf.padding * -1} ` +
// (bounds.width * 1.5 + conf.padding * 2) +
// ' ' +
// (bounds.height + conf.padding * 5)
// );
};
const getLabelWidth = text => {
return text ? text.length * conf.fontSizeFactor : 1;
};
/* TODO: REMOVE DUPLICATION, SEE SHAPES */
const getRows = s => {
if (!s) return 1;
let str = s.replace(/<br\/?>/gi, '#br#');
str = str.replace(/\\n/g, '#br#');
return str.split('#br#');
};
const renderDoc = (doc, diagram, parentId) => {
// // Layout graph, Create a new directed graph
const graph = new graphlib.Graph({
compound: true
});
// Set an object for the graph label
if (parentId)
graph.setGraph({
rankdir: 'LR',
// multigraph: false,
compound: true,
// acyclicer: 'greedy',
ranker: 'tight-tree',
ranksep: conf.edgeLengthFactor
// isMultiGraph: false
});
else {
graph.setGraph({
rankdir: 'TB',
compound: true,
// isCompound: true,
// acyclicer: 'greedy',
// ranker: 'longest-path'
ranksep: conf.edgeLengthFactor,
ranker: 'tight-tree'
// ranker: 'network-simplex'
// isMultiGraph: false
});
}
// Default to assigning a new object as a label for each new edge.
graph.setDefaultEdgeLabel(function() {
return {};
});
stateDb.extract(doc);
const states = stateDb.getStates();
const relations = stateDb.getRelations();
const keys = Object.keys(states);
let first = true;
for (let i = 0; i < keys.length; i++) {
const stateDef = states[keys[i]];
if (parentId) {
stateDef.parentId = parentId;
}
let node;
if (stateDef.doc) {
let sub = diagram
.append('g')
.attr('id', stateDef.id)
.attr('class', 'stateGroup');
node = renderDoc(stateDef.doc, sub, stateDef.id);
if (first) {
// first = false;
sub = addIdAndBox(sub, stateDef);
let boxBounds = sub.node().getBBox();
node.width = boxBounds.width;
node.height = boxBounds.height + 2 * conf.padding;
transformationLog[stateDef.id] = { y: conf.compositTitleSize };
} else {
// sub = addIdAndBox(sub, stateDef);
let boxBounds = sub.node().getBBox();
node.width = boxBounds.width;
node.height = boxBounds.height;
// transformationLog[stateDef.id] = { y: conf.compositTitleSize };
}
} else {
node = drawState(diagram, stateDef, graph);
}
if (stateDef.note) {
// Draw note note
const noteDef = {
descriptions: [],
id: stateDef.id + '-note',
note: stateDef.note,
type: 'note'
};
const note = drawState(diagram, noteDef, graph);
// graph.setNode(node.id, node);
if (stateDef.note.position === 'left of') {
graph.setNode(node.id + '-note', note);
graph.setNode(node.id, node);
} else {
graph.setNode(node.id, node);
graph.setNode(node.id + '-note', note);
}
// graph.setNode(node.id);
graph.setParent(node.id, node.id + '-group');
graph.setParent(node.id + '-note', node.id + '-group');
} else {
// Add nodes to the graph. The first argument is the node id. The second is
// metadata about the node. In this case we're going to add labels to each of
// our nodes.
graph.setNode(node.id, node);
}
}
logger.info('Count=', graph.nodeCount());
relations.forEach(function(relation) {
graph.setEdge(relation.id1, relation.id2, {
relation: relation,
width: getLabelWidth(relation.title),
height: conf.labelHeight * getRows(relation.title).length,
labelpos: 'c'
});
});
dagre.layout(graph);
logger.debug('Graph after layout', graph.nodes());
const svgElem = diagram.node();
graph.nodes().forEach(function(v) {
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
logger.warn('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
d3.select('#' + svgElem.id + ' #' + v).attr(
'transform',
'translate(' +
(graph.node(v).x - graph.node(v).width / 2) +
',' +
(graph.node(v).y +
(transformationLog[v] ? transformationLog[v].y : 0) -
graph.node(v).height / 2) +
' )'
);
d3.select('#' + svgElem.id + ' #' + v).attr(
'data-x-shift',
graph.node(v).x - graph.node(v).width / 2
);
const dividers = document.querySelectorAll('#' + svgElem.id + ' #' + v + ' .divider');
dividers.forEach(divider => {
const parent = divider.parentElement;
let pWidth = 0;
let pShift = 0;
if (parent) {
if (parent.parentElement) pWidth = parent.parentElement.getBBox().width;
pShift = parseInt(parent.getAttribute('data-x-shift'), 10);
if (Number.isNaN(pShift)) {
pShift = 0;
}
}
divider.setAttribute('x1', 0 - pShift);
divider.setAttribute('x2', pWidth - pShift);
});
} else {
logger.debug('No Node ' + v + ': ' + JSON.stringify(graph.node(v)));
}
});
let stateBox = svgElem.getBBox();
graph.edges().forEach(function(e) {
if (typeof e !== 'undefined' && typeof graph.edge(e) !== 'undefined') {
logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
drawEdge(diagram, graph.edge(e), graph.edge(e).relation);
}
});
stateBox = svgElem.getBBox();
const stateInfo = {
id: parentId ? parentId : 'root',
label: parentId ? parentId : 'root',
width: 0,
height: 0
};
stateInfo.width = stateBox.width + 2 * conf.padding;
stateInfo.height = stateBox.height + 2 * conf.padding;
logger.info('Doc rendered', stateInfo, graph);
return stateInfo;
};
export default {
setConf,
draw
};

View File

@ -28,6 +28,9 @@ import ganttDb from './diagrams/gantt/ganttDb';
import classRenderer from './diagrams/class/classRenderer';
import classParser from './diagrams/class/parser/classDiagram';
import classDb from './diagrams/class/classDb';
import stateRenderer from './diagrams/state/stateRenderer';
import stateParser from './diagrams/state/parser/stateDiagram';
import stateDb from './diagrams/state/stateDb';
import gitGraphRenderer from './diagrams/git/gitGraphRenderer';
import gitGraphParser from './diagrams/git/parser/gitGraph';
import gitGraphAst from './diagrams/git/gitGraphAst';
@ -90,10 +93,14 @@ const config = {
* "themeCSS": ".node rect { fill: red; }"
* </pre>
*/
theme: 'default',
themeCSS: undefined,
/**
* **fontFamily** The font to be used for the rendered diagrams. Default value is \"trebuchet ms\", verdana, arial;
*/
fontFamily: '"trebuchet ms", verdana, arial;',
/**
* This option decides the amount of logging to be used.
* * debug: 1
@ -299,7 +306,27 @@ const config = {
axisFormat: '%Y-%m-%d'
},
class: {},
git: {}
git: {},
state: {
dividerMargin: 10,
sizeUnit: 5,
padding: 8,
textHeight: 10,
titleShift: -15,
noteMargin: 10,
forkWidth: 70,
forkHeight: 7,
// Used
miniPadding: 2,
// Font size factor, this is used to guess the width of the edges labels before rendering by dagre
// layout. This might need updating if/when switching font
fontSizeFactor: 5.02,
fontSize: 24,
labelHeight: 16,
edgeLengthFactor: '20',
compositTitleSize: 35,
radius: 5
}
};
setLogLevel(config.logLevel);
@ -332,10 +359,12 @@ function parse(text) {
parser = classParser;
parser.parser.yy = classDb;
break;
case 'state':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'info':
logger.debug('info info info');
console.warn('In API', pkg.version);
parser = infoParser;
parser.parser.yy = infoDb;
break;
@ -424,12 +453,17 @@ const render = function(id, txt, cb, container) {
d3.select(container)
.append('div')
.attr('id', 'd' + id)
.attr('style', 'font-family: ' + config.fontFamily)
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g');
} else {
const existingSvg = document.getElementById(id);
if (existingSvg) {
existingSvg.remove();
}
const element = document.querySelector('#' + 'd' + id);
if (element) {
element.innerHTML = '';
@ -465,6 +499,14 @@ const render = function(id, txt, cb, container) {
if (config.themeCSS !== undefined) {
style += `\n${config.themeCSS}`;
}
// user provided theme CSS
if (config.fontFamily !== undefined) {
style += `\n:root { --mermaid-font-family: ${config.fontFamily}}`;
}
// user provided theme CSS
if (config.altFontFamily !== undefined) {
style += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
}
// classDef
if (graphType === 'flowchart') {
@ -522,6 +564,11 @@ const render = function(id, txt, cb, container) {
classRenderer.setConf(config.class);
classRenderer.draw(txt, id);
break;
case 'state':
// config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
stateRenderer.setConf(config.state);
stateRenderer.draw(txt, id);
break;
case 'info':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
infoRenderer.setConf(config.class);

View File

@ -2,7 +2,12 @@ g.classGroup text {
fill: $nodeBorder;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 10px;
.title {
font-weight: bolder;
}
}
g.classGroup rect {

View File

@ -56,4 +56,7 @@ $critBkgColor: #E83737;
$taskTextDarkColor: $darkTextColor;
$todayLineColor: #DB5757;
/* state colors */
$labelColor: black;
@import '../mermaid';

View File

@ -54,4 +54,7 @@ $critBorderColor: #ff8888;
$critBkgColor: red;
$todayLineColor: red;
/* state colors */
$labelColor: black;
@import '../mermaid';

View File

@ -1,5 +1,6 @@
.label {
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
color: #333;
}
@ -16,6 +17,9 @@
stroke-width: 1px;
}
.node .label {
text-align: center;
}
.node.clickable {
cursor: pointer;
}
@ -31,6 +35,7 @@
.edgeLabel {
background-color: $edgeLabelBackground;
text-align: center;
}
.cluster rect {
@ -49,6 +54,7 @@ div.mermaidTooltip {
max-width: 200px;
padding: 2px;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 12px;
background: $secondBkg;
border: 1px solid $border2;

View File

@ -55,4 +55,7 @@ $critBorderColor: #ff8888;
$critBkgColor: red;
$todayLineColor: red;
/* state colors */
$labelColor: black;
@import '../mermaid';

View File

@ -1,5 +1,10 @@
/** Section styling */
.mermaid-main-font {
font-family: "trebuchet ms", verdana, arial;
font-family: var(--mermaid-font-family);
}
.section {
stroke: none;
opacity: 0.2;
@ -39,6 +44,9 @@
text-anchor: start;
font-size: 11px;
text-height: 14px;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}
@ -48,6 +56,10 @@
stroke: $gridColor;
opacity: 0.3;
shape-rendering: crispEdges;
text {
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}
}
.grid path {
@ -74,6 +86,11 @@
.taskText {
text-anchor: middle;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}
.taskText:not([font-size]) {
font-size: 11px;
}
@ -81,6 +98,9 @@
fill: $taskTextDarkColor;
text-anchor: start;
font-size: 11px;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}
.taskTextOutsideLeft {
@ -233,4 +253,6 @@
text-anchor: middle;
font-size: 18px;
fill: $taskTextDarkColor;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}

View File

@ -3,4 +3,6 @@
.branch-label {
fill: lightgrey;
color: lightgrey;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}

View File

@ -4,3 +4,16 @@
@import 'class';
@import 'git';
@import 'pie';
@import 'state';
// .composit {
// fill: white;
// border-bottom: 1px
// }
:root {
--mermaid-font-family: '"trebuchet ms", verdana, arial';
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive;
// --mermaid-alt-font-family: '"Lucida Console", Monaco, monospace';
}

View File

@ -59,4 +59,7 @@ $critBkgColor: $critical;
$critBorderColor: darken($critBkgColor, 10%);
$todayLineColor: $critBkgColor;
/* state colors */
$labelColor: black;
@import '../mermaid';

View File

@ -2,4 +2,10 @@
text-anchor: middle;
font-size: 25px;
fill: $taskTextDarkColor;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}
.slice {
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}

View File

@ -77,6 +77,7 @@ text.actor {
fill: black;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 14px;
}

64
src/themes/state.scss Normal file
View File

@ -0,0 +1,64 @@
g.stateGroup text {
fill: $nodeBorder;
stroke: none;
font-size: 10px;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}
g.stateGroup text {
fill: $nodeBorder;
stroke: none;
font-size: 10px;
}
g.stateGroup .state-title {
font-weight: bolder;
fill: $labelColor;
}
g.stateGroup rect {
fill: $nodeBkg;
stroke: $nodeBorder;
}
g.stateGroup line {
stroke: $nodeBorder;
stroke-width: 1;
}
.transition {
stroke: $nodeBorder;
stroke-width: 1;
fill: none;
}
.stateGroup .composit {
fill: white;
border-bottom: 1px
}
.state-note {
stroke: $noteBorderColor;
fill: $noteBkgColor;
text {
fill: black;
stroke: none;
font-size: 10px;
}
}
.stateLabel .box {
stroke: none;
stroke-width: 0;
fill: $nodeBkg;
opacity: 0.5;
}
.stateLabel text {
fill: $labelColor;
font-size: 10px;
font-weight: bold;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
}

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