├── assets
├── readme.md
├── themes
│ ├── git.scss
│ ├── mermaid.scss
│ ├── flowchart.scss
│ ├── class.scss
│ ├── sequence.scss
│ ├── default
│ │ └── index.scss
│ ├── forest
│ │ └── index.scss
│ ├── neutral
│ │ └── index.scss
│ ├── dark
│ │ └── index.scss
│ └── gantt.scss
├── logger.js
├── utils.spec.js
├── diagrams
│ ├── gantt
│ │ ├── gantt.spec.js
│ │ ├── parser
│ │ │ └── gantt.jison
│ │ ├── ganttDb.spec.js
│ │ ├── ganttDb.js
│ │ └── ganttRenderer.js
│ ├── class
│ │ ├── classDb.js
│ │ ├── classDiagram.spec.js
│ │ ├── parser
│ │ │ └── classDiagram.jison
│ │ └── classRenderer.js
│ ├── git
│ │ ├── parser
│ │ │ └── gitGraph.jison
│ │ ├── gitGraphAst.js
│ │ ├── gitGraphParser.spec.js
│ │ └── gitGraphRenderer.js
│ ├── sequence
│ │ ├── sequenceDb.js
│ │ ├── parser
│ │ │ └── sequenceDiagram.jison
│ │ └── svgDraw.js
│ └── flowchart
│ │ ├── flowDb.js
│ │ ├── flowRenderer.js
│ │ └── parser
│ │ └── flow.jison
├── utils.js
├── mermaidAPI.spec.js
├── mermaid.js
├── mermaid.spec.js
└── mermaidAPI.js
├── screenshots
├── README.md
├── SCREENSHOTS.MD
├── mongodb-url.png
├── ngrok-token.png
├── create-db-user.png
└── connect-to-repo.png
├── .gitignore
├── favicon.ico
├── Gemfile
├── _config.yml
├── index.md
├── _data
└── navigation.yml
├── _plugins
└── tags
│ └── napolean.rb
├── about.md
├── README.md
├── achievibit-local.md
└── kibibit-bit.md
/assets/readme.md:
--------------------------------------------------------------------------------
1 | asd
2 |
--------------------------------------------------------------------------------
/screenshots/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _site/
2 | Gemfile.lock
3 |
--------------------------------------------------------------------------------
/screenshots/SCREENSHOTS.MD:
--------------------------------------------------------------------------------
1 | save your documentation screenshots here
2 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kibibit/doc-explorer/master/favicon.ico
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'github-pages', group: :jekyll_plugins
3 |
--------------------------------------------------------------------------------
/screenshots/mongodb-url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/mongodb-url.png
--------------------------------------------------------------------------------
/screenshots/ngrok-token.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/ngrok-token.png
--------------------------------------------------------------------------------
/screenshots/create-db-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/create-db-user.png
--------------------------------------------------------------------------------
/screenshots/connect-to-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/connect-to-repo.png
--------------------------------------------------------------------------------
/assets/themes/git.scss:
--------------------------------------------------------------------------------
1 | .commit-id,
2 | .commit-msg,
3 | .branch-label {
4 | fill: lightgrey;
5 | color: lightgrey;
6 | }
7 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | remote_theme: Kibibit/bulma-theme
2 | name: kibibit doc explorer
3 | plugins:
4 | - jekyll
5 | - jekyll-spark
6 |
--------------------------------------------------------------------------------
/assets/themes/mermaid.scss:
--------------------------------------------------------------------------------
1 | @import 'flowchart';
2 | @import 'sequence';
3 | @import 'gantt';
4 | @import 'class';
5 | @import 'git';
6 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: kibibit's Document Explorer
3 | layout: homepage
4 | ---
5 |
6 | # kibibit's Document Explorer - Jekyll theme
7 |
8 | some general documentation for kibibt projects
9 |
--------------------------------------------------------------------------------
/_data/navigation.yml:
--------------------------------------------------------------------------------
1 | - title: "@kibibit/bit documentation"
2 | url: /kibibit-bit
3 | icon: fa-terminal
4 | - title: Run Achievibit Locally
5 | url: /achievibit-local
6 | icon: fa-flask
7 | - title: About Us
8 | url: /about
9 | icon: fa-info-circle
10 |
--------------------------------------------------------------------------------
/_plugins/tags/napolean.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | # Create your component class
5 | class NapoleanComponent < ComponentTag
6 | def template(context)
7 | render = %Q[
8 |
9 |

10 |
11 | ]
12 | end
13 | end
14 | end
15 |
16 | # Register your component with Liquid
17 | Liquid::Template.register_tag(
18 | "Napolean", # Namespace your component
19 | Jekyll::NapoleanComponent, # Pass your newly created component class
20 | )
21 |
--------------------------------------------------------------------------------
/about.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: About Us
3 | authors:
4 | - Neil Kalman
5 | layout: default
6 | id: about-us
7 | permalink: /about
8 | ---
9 |
10 |
11 | # About Us
12 |
13 | kibibit is an open source team of software developers, trying to make the world easier piece by piece.
14 |
15 | you’re more than welcome to contribute!
16 |
17 | ## About this project
18 |
19 | This is a Jekyll theme for GitHub Pages. It's the more "formal" theme we have for important documents (happy theme: kibibit/colorful-theme)
20 |
21 |
22 | ### Example Variables:
23 |
24 | this is the **default** layout
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # doc-explorer
2 | General documentation for kibibit thingies
3 |
4 | We can keep getting started documents here, design documents, and even thoughts and ideas
5 |
6 | To add a document, simply create a new `markdown` file and include the following header:
7 |
8 | ```markdown
9 | ---
10 | title:
11 | authors:
12 | -
13 | layout:
14 | id:
15 | permalink: /
16 | ---
17 | ```
18 |
19 | When the document is ready in your opinion, you can publish it by adding it to the navigation object located in `_data/navigation.yml`:
20 |
21 | ```yml
22 | - title:
23 | url: /
24 | icon:
25 | ```
26 |
27 | if you want to add it inside a category, use `sublinks`:
28 |
29 | ```yml
30 | - title: Postmortems
31 | url: /postmortem
32 | icon: fa-flask
33 | sublinks:
34 | - title: Shakespeare Sonnet++ Postmortem
35 | url: /postmortem/001
36 | incident: 1
37 | ```
38 |
--------------------------------------------------------------------------------
/assets/themes/flowchart.scss:
--------------------------------------------------------------------------------
1 | .label {
2 | font-family: 'trebuchet ms', verdana, arial;
3 | color: #333;
4 | }
5 |
6 | .node rect,
7 | .node circle,
8 | .node ellipse,
9 | .node polygon {
10 | fill: $mainBkg;
11 | stroke: $nodeBorder;
12 | stroke-width: 1px;
13 | }
14 |
15 | .node.clickable {
16 | cursor: pointer;
17 | }
18 |
19 | .arrowheadPath {
20 | fill: $arrowheadColor;
21 | }
22 |
23 | .edgePath .path {
24 | stroke: $lineColor;
25 | stroke-width: 1.5px;
26 | }
27 |
28 | .edgeLabel {
29 | background-color: $edgeLabelBackground;
30 | }
31 |
32 | .cluster rect {
33 | fill: $secondBkg !important;
34 | stroke: $clusterBorder !important;
35 | stroke-width: 1px !important;
36 | }
37 |
38 | .cluster text {
39 | fill: $titleColor;
40 | }
41 |
42 | div.mermaidTooltip {
43 | position: absolute;
44 | text-align: center;
45 | max-width: 200px;
46 | padding: 2px;
47 | font-family: 'trebuchet ms', verdana, arial;
48 | font-size: 12px;
49 | background: $secondBkg;
50 | border: 1px solid $border2;
51 | border-radius: 2px;
52 | pointer-events: none;
53 | z-index: 100;
54 | }
55 |
--------------------------------------------------------------------------------
/assets/logger.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 |
3 | export const LEVELS = {
4 | debug: 1,
5 | info: 2,
6 | warn: 3,
7 | error: 4,
8 | fatal: 5
9 | }
10 |
11 | export const logger = {
12 | debug: () => {},
13 | info: () => {},
14 | warn: () => {},
15 | error: () => {},
16 | fatal: () => {}
17 | }
18 |
19 | export const setLogLevel = function (level) {
20 | logger.debug = () => {}
21 | logger.info = () => {}
22 | logger.warn = () => {}
23 | logger.error = () => {}
24 | logger.fatal = () => {}
25 | if (level <= LEVELS.fatal) {
26 | logger.fatal = console.log.bind(console, '\x1b[35m', format('FATAL'))
27 | }
28 | if (level <= LEVELS.error) {
29 | logger.error = console.log.bind(console, '\x1b[31m', format('ERROR'))
30 | }
31 | if (level <= LEVELS.warn) {
32 | logger.warn = console.log.bind(console, `\x1b[33m`, format('WARN'))
33 | }
34 | if (level <= LEVELS.info) {
35 | logger.info = console.log.bind(console, '\x1b[34m', format('INFO'))
36 | }
37 | if (level <= LEVELS.debug) {
38 | logger.debug = console.log.bind(console, '\x1b[32m', format('DEBUG'))
39 | }
40 | }
41 |
42 | const format = (level) => {
43 | const time = moment().format('HH:mm:ss.SSS')
44 | return `${time} : ${level} : `
45 | }
46 |
--------------------------------------------------------------------------------
/assets/themes/class.scss:
--------------------------------------------------------------------------------
1 | g.classGroup text {
2 | fill: $nodeBorder;
3 | stroke: none;
4 | font-family: 'trebuchet ms', verdana, arial;
5 | font-size: 10px;
6 | }
7 |
8 | g.classGroup rect {
9 | fill: $nodeBkg;
10 | stroke: $nodeBorder;
11 | }
12 |
13 | g.classGroup line {
14 | stroke: $nodeBorder;
15 | stroke-width: 1;
16 | }
17 |
18 | .classLabel .box {
19 | stroke: none;
20 | stroke-width: 0;
21 | fill: $nodeBkg;
22 | opacity: 0.5;
23 | }
24 |
25 | .classLabel .label {
26 | fill: $nodeBorder;
27 | font-size: 10px;
28 | }
29 |
30 | .relation {
31 | stroke: $nodeBorder;
32 | stroke-width: 1;
33 | fill: none;
34 | }
35 |
36 | @mixin composition {
37 | fill: $nodeBorder;
38 | stroke: $nodeBorder;
39 | stroke-width: 1;
40 | }
41 |
42 | #compositionStart {
43 | @include composition;
44 | }
45 |
46 | #compositionEnd {
47 | @include composition;
48 | }
49 |
50 | @mixin aggregation {
51 | fill: $nodeBkg;
52 | stroke: $nodeBorder;
53 | stroke-width: 1;
54 | }
55 |
56 | #aggregationStart {
57 | @include aggregation;
58 | }
59 |
60 | #aggregationEnd {
61 | @include aggregation;
62 | }
63 |
64 | #dependencyStart {
65 | @include composition;
66 | }
67 |
68 | #dependencyEnd {
69 | @include composition;
70 | }
71 |
72 | #extensionStart {
73 | @include composition;
74 | }
75 |
76 | #extensionEnd {
77 | @include composition;
78 | }
79 |
--------------------------------------------------------------------------------
/assets/themes/sequence.scss:
--------------------------------------------------------------------------------
1 | .actor {
2 | stroke: $actorBorder;
3 | fill: $actorBkg;
4 | }
5 |
6 | text.actor {
7 | fill: $actorTextColor;
8 | stroke: none;
9 | }
10 |
11 | .actor-line {
12 | stroke: $actorLineColor;
13 | }
14 |
15 | .messageLine0 {
16 | stroke-width: 1.5;
17 | stroke-dasharray: '2 2';
18 | marker-end: 'url(#arrowhead)';
19 | stroke: $signalColor;
20 | }
21 |
22 | .messageLine1 {
23 | stroke-width: 1.5;
24 | stroke-dasharray: '2 2';
25 | stroke: $signalColor;
26 | }
27 |
28 | #arrowhead {
29 | fill: $signalColor;
30 | }
31 |
32 | #crosshead path {
33 | fill: $signalColor !important;
34 | stroke: $signalColor !important;
35 | }
36 |
37 | .messageText {
38 | fill: $signalTextColor;
39 | stroke: none;
40 | }
41 |
42 | .labelBox {
43 | stroke: $labelBoxBorderColor;
44 | fill: $labelBoxBkgColor;
45 | }
46 |
47 | .labelText {
48 | fill: $labelTextColor;
49 | stroke: none;
50 | }
51 |
52 | .loopText {
53 | fill: $labelTextColor;
54 | stroke: none;
55 | }
56 |
57 | .loopLine {
58 | stroke-width: 2;
59 | stroke-dasharray: '2 2';
60 | marker-end: 'url(#arrowhead)';
61 | stroke: $labelBoxBorderColor;
62 | }
63 |
64 | .note {
65 | //stroke: #decc93;
66 | stroke: $noteBorderColor;
67 | fill: $noteBkgColor;
68 | }
69 |
70 | .noteText {
71 | fill: black;
72 | stroke: none;
73 | font-family: 'trebuchet ms', verdana, arial;
74 | font-size: 14px;
75 | }
76 |
--------------------------------------------------------------------------------
/assets/themes/default/index.scss:
--------------------------------------------------------------------------------
1 | $mainBkg: #ECECFF;
2 | $secondBkg: #ffffde;
3 | $lineColor: #333333;
4 | $border1: #CCCCFF;
5 | $border2: #aaaa33;
6 | $arrowheadColor: #333333;
7 |
8 | /* Flowchart variables */
9 |
10 | $nodeBkg: $mainBkg;
11 | $nodeBorder: #9370DB;
12 | $clusterBkg: $secondBkg;
13 | $clusterBorder: $border2;
14 | $defaultLinkColor: $lineColor;
15 | $titleColor: #333;
16 | $edgeLabelBackground: #e8e8e8;
17 |
18 | /* Sequence Diagram variables */
19 |
20 | $actorBorder: $border1;
21 | $actorBkg: $mainBkg;
22 | $actorTextColor: black;
23 | $actorLineColor: grey;
24 | $signalColor: #333;
25 | $signalTextColor: #333;
26 | $labelBoxBkgColor: $actorBkg;
27 | $labelBoxBorderColor: $actorBorder;
28 | $labelTextColor: $actorTextColor;
29 | $noteBorderColor: $border2;
30 | $noteBkgColor: #fff5ad;
31 |
32 | /* Gantt chart variables */
33 |
34 | $sectionBkgColor: rgba(102, 102, 255, 0.49);
35 | $altSectionBkgColor: white;
36 | $sectionBkgColor2: #fff400;
37 | $taskBorderColor: #534fbc;
38 | $taskBkgColor: #8a90dd;
39 | $taskTextLightColor: white;
40 | $taskTextColor: $taskTextLightColor;
41 | $taskTextDarkColor: black;
42 | $taskTextOutsideColor: $taskTextDarkColor;
43 | $activeTaskBorderColor: #534fbc;
44 | $activeTaskBkgColor: #bfc7ff;
45 | $gridColor: lightgrey;
46 | $doneTaskBkgColor: lightgrey;
47 | $doneTaskBorderColor: grey;
48 | $critBorderColor: #ff8888;
49 | $critBkgColor: red;
50 | $todayLineColor: red;
51 |
52 | @import '../mermaid';
53 |
--------------------------------------------------------------------------------
/assets/themes/forest/index.scss:
--------------------------------------------------------------------------------
1 | $mainBkg: #cde498;
2 | $secondBkg: #cdffb2;
3 | $lineColor: #1a3318;
4 | $lineColor: green;
5 | $border1: #13540c;
6 | $border2: #6eaa49;
7 | $arrowheadColor: green;
8 |
9 | /* Flowchart variables */
10 |
11 | $nodeBkg: $mainBkg;
12 | $nodeBorder: $border1;
13 | $clusterBkg: $secondBkg;
14 | $clusterBorder: $border2;
15 | $defaultLinkColor: $lineColor;
16 | $titleColor: #333;
17 | $edgeLabelBackground: #e8e8e8;
18 |
19 | /* Sequence Diagram variables */
20 |
21 | $actorBorder: $border1;
22 | $actorBkg: $mainBkg;
23 | $actorTextColor: black;
24 | $actorLineColor: grey;
25 | $signalColor: #333;
26 | $signalTextColor: #333;
27 | $labelBoxBkgColor: $actorBkg;
28 | $labelBoxBorderColor: #326932;
29 | $labelTextColor: $actorTextColor;
30 | $noteBorderColor: $border2;
31 | $noteBkgColor: #fff5ad;
32 |
33 | /* Gantt chart variables */
34 |
35 | $sectionBkgColor: #6eaa49;
36 | $altSectionBkgColor: white;
37 | $sectionBkgColor2: #6eaa49;
38 | $taskBorderColor: $border1;
39 | $taskBkgColor: #487e3a;
40 | $taskTextLightColor: white;
41 | $taskTextColor: $taskTextLightColor;
42 | $taskTextDarkColor: black;
43 | $taskTextOutsideColor: $taskTextDarkColor;
44 | $activeTaskBorderColor: $taskBorderColor;
45 | $activeTaskBkgColor: $mainBkg;
46 | $gridColor: lightgrey;
47 | $doneTaskBkgColor: lightgrey;
48 | $doneTaskBorderColor: grey;
49 | $critBorderColor: #ff8888;
50 | $critBkgColor: red;
51 | $todayLineColor: red;
52 |
53 | @import '../mermaid';
54 |
--------------------------------------------------------------------------------
/assets/utils.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import utils from './utils'
3 |
4 | describe('when detecting chart type ', function () {
5 | it('should handle a graph defintion', function () {
6 | const str = 'graph TB\nbfs1:queue'
7 | const type = utils.detectType(str)
8 | expect(type).toBe('flowchart')
9 | })
10 | it('should handle a graph defintion with leading spaces', function () {
11 | const str = ' graph TB\nbfs1:queue'
12 | const type = utils.detectType(str)
13 | expect(type).toBe('flowchart')
14 | })
15 |
16 | it('should handle a graph defintion with leading spaces and newline', function () {
17 | const str = ' \n graph TB\nbfs1:queue'
18 | const type = utils.detectType(str)
19 | expect(type).toBe('flowchart')
20 | })
21 | it('should handle a graph defintion for gitGraph', function () {
22 | const str = ' \n gitGraph TB:\nbfs1:queue'
23 | const type = utils.detectType(str)
24 | expect(type).toBe('git')
25 | })
26 | })
27 |
28 | describe('when finding substring in array ', function () {
29 | it('should return the array index that contains the substring', function () {
30 | const arr = ['stroke:val1', 'fill:val2']
31 | const result = utils.isSubstringInArray('fill', arr)
32 | expect(result).toEqual(1)
33 | })
34 | it('should return -1 if the substring is not found in the array', function () {
35 | const arr = ['stroke:val1', 'stroke-width:val2']
36 | const result = utils.isSubstringInArray('fill', arr)
37 | expect(result).toEqual(-1)
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/assets/diagrams/gantt/gantt.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import { parser } from './parser/gantt'
3 | import ganttDb from './ganttDb'
4 |
5 | describe('when parsing a gantt diagram it', function () {
6 | beforeEach(function () {
7 | parser.yy = ganttDb
8 | })
9 |
10 | it('should handle a dateFormat definition', function () {
11 | const str = 'gantt\ndateFormat yyyy-mm-dd'
12 |
13 | parser.parse(str)
14 | })
15 | it('should handle a title definition', function () {
16 | const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid'
17 |
18 | parser.parse(str)
19 | })
20 | it('should handle a section definition', function () {
21 | const str = 'gantt\n' +
22 | 'dateFormat yyyy-mm-dd\n' +
23 | 'title Adding gantt diagram functionality to mermaid\n' +
24 | 'section Documentation'
25 |
26 | parser.parse(str)
27 | })
28 | /**
29 | * Beslutsflöde inligt nedan. Obs bla bla bla
30 | * ```
31 | * graph TD
32 | * A[Hard pledge] -- text on link -->B(Round edge)
33 | * B --> C{to do or not to do}
34 | * C -->|Too| D[Result one]
35 | * C -->|Doo| E[Result two]
36 | ```
37 | * params bapa - a unique bapap
38 | */
39 | it('should handle a task definition', function () {
40 | const str = 'gantt\n' +
41 | 'dateFormat yyyy-mm-dd\n' +
42 | 'title Adding gantt diagram functionality to mermaid\n' +
43 | 'section Documentation\n' +
44 | 'Design jison grammar:des1, 2014-01-01, 2014-01-04'
45 |
46 | parser.parse(str)
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/assets/themes/neutral/index.scss:
--------------------------------------------------------------------------------
1 | $mainBkg: #eee;
2 | $contrast: #26a;
3 | $secondBkg: lighten($contrast, 55%);
4 | $lineColor: #666;
5 | $border1: #999;
6 | $border2: $contrast;
7 | $note: #ffa;
8 | $text: #333;
9 | $critical: #d42;
10 | $done: #bbb;
11 | $arrowheadColor: #333333;
12 |
13 | /* Flowchart variables */
14 |
15 | $nodeBkg: $mainBkg;
16 | $nodeBorder: $border1;
17 | $clusterBkg: $secondBkg;
18 | $clusterBorder: $border2;
19 | $defaultLinkColor: $lineColor;
20 | $titleColor: $text;
21 | $edgeLabelBackground: white;
22 |
23 | /* Sequence Diagram variables */
24 |
25 | $actorBorder: $border1;
26 | $actorBkg: $mainBkg;
27 | $actorTextColor: $text;
28 | $actorLineColor: $lineColor;
29 | $signalColor: $text;
30 | $signalTextColor: $text;
31 | $labelBoxBkgColor: $actorBkg;
32 | $labelBoxBorderColor: $actorBorder;
33 | $labelTextColor: white;
34 | $noteBorderColor: darken($note, 60%);
35 | $noteBkgColor: $note;
36 |
37 | /* Gantt chart variables */
38 |
39 | $sectionBkgColor: lighten($contrast, 30%);
40 | $altSectionBkgColor: white;
41 | $sectionBkgColor2: lighten($contrast, 30%);
42 | $taskBorderColor: darken($contrast, 10%);
43 | $taskBkgColor: $contrast;
44 | $taskTextLightColor: white;
45 | $taskTextColor: $taskTextLightColor;
46 | $taskTextDarkColor: $text;
47 | $taskTextOutsideColor: $taskTextDarkColor;
48 | $activeTaskBorderColor: $taskBorderColor;
49 | $activeTaskBkgColor: $mainBkg;
50 | $gridColor: lighten($border1, 30%);
51 | $doneTaskBkgColor: $done;
52 | $doneTaskBorderColor: $lineColor;
53 | $critBkgColor: $critical;
54 | $critBorderColor: darken($critBkgColor, 10%);
55 | $todayLineColor: $critBkgColor;
56 |
57 | @import '../mermaid';
58 |
--------------------------------------------------------------------------------
/assets/themes/dark/index.scss:
--------------------------------------------------------------------------------
1 | $mainBkg: #BDD5EA;
2 | $secondBkg: #6D6D65;
3 | $mainContrastColor: lightgrey;
4 | $darkTextColor: #323D47;
5 | $lineColor: $mainContrastColor;
6 | $border1: #81B1DB;
7 | $border2: rgba(255, 255, 255, 0.25);
8 | $arrowheadColor: $mainContrastColor;
9 |
10 | /* Flowchart variables */
11 |
12 | $nodeBkg: $mainBkg;
13 | $nodeBorder: purple;
14 | $clusterBkg: $secondBkg;
15 | $clusterBorder: $border2;
16 | $defaultLinkColor: $lineColor;
17 | $titleColor: #F9FFFE;
18 | $edgeLabelBackground: #e8e8e8;
19 |
20 | /* Sequence Diagram variables */
21 |
22 | $actorBorder: $border1;
23 | $actorBkg: $mainBkg;
24 | $actorTextColor: black;
25 | $actorLineColor: $mainContrastColor;
26 | $signalColor: $mainContrastColor;
27 | $signalTextColor: $mainContrastColor;
28 | $labelBoxBkgColor: $actorBkg;
29 | $labelBoxBorderColor: $actorBorder;
30 | $labelTextColor: $mainContrastColor;
31 | $noteBorderColor: $border2;
32 | $noteBkgColor: #fff5ad;
33 |
34 | /* Gantt chart variables */
35 |
36 | $sectionBkgColor: rgba(255, 255, 255, 0.3);
37 | $altSectionBkgColor: white;
38 | $sectionBkgColor2: #EAE8B9;
39 | $taskBorderColor: rgba(255, 255, 255, 0.5);
40 | $taskBkgColor: $mainBkg;
41 | $taskTextColor: $darkTextColor;
42 | $taskTextLightColor: $mainContrastColor;
43 | $taskTextOutsideColor: $taskTextLightColor;
44 | $activeTaskBorderColor: rgba(255, 255, 255, 0.5);
45 | $activeTaskBkgColor: #81B1DB;
46 | $gridColor: $mainContrastColor;
47 | $doneTaskBkgColor: $mainContrastColor;
48 | $doneTaskBorderColor: grey;
49 | $critBorderColor: #E83737;
50 | $critBkgColor: #E83737;
51 | $taskTextDarkColor: $darkTextColor;
52 | $todayLineColor: #DB5757;
53 |
54 | @import '../mermaid';
55 |
--------------------------------------------------------------------------------
/assets/utils.js:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3'
2 |
3 | /**
4 | * @function detectType
5 | * Detects the type of the graph text.
6 | * ```mermaid
7 | * graph LR
8 | * a-->b
9 | * b-->c
10 | * c-->d
11 | * d-->e
12 | * e-->f
13 | * f-->g
14 | * g-->h
15 | * ```
16 | *
17 | * @param {string} text The text defining the graph
18 | * @returns {string} A graph definition key
19 | */
20 | export const detectType = function (text) {
21 | text = text.replace(/^\s*%%.*\n/g, '\n')
22 | if (text.match(/^\s*sequenceDiagram/)) {
23 | return 'sequence'
24 | }
25 |
26 | if (text.match(/^\s*gantt/)) {
27 | return 'gantt'
28 | }
29 |
30 | if (text.match(/^\s*classDiagram/)) {
31 | return 'class'
32 | }
33 |
34 | if (text.match(/^\s*gitGraph/)) {
35 | return 'git'
36 | }
37 | return 'flowchart'
38 | }
39 |
40 | /**
41 | * @function isSubstringInArray
42 | * Detects whether a substring in present in a given array
43 | * @param {string} str The substring to detect
44 | * @param {array} arr The array to search
45 | * @returns {number} the array index containing the substring or -1 if not present
46 | **/
47 | export const isSubstringInArray = function (str, arr) {
48 | for (let i = 0; i < arr.length; i++) {
49 | if (arr[i].match(str)) return i
50 | }
51 | return -1
52 | }
53 |
54 | export const interpolateToCurve = (interpolate, defaultCurve) => {
55 | if (!interpolate) {
56 | return defaultCurve
57 | }
58 | const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`
59 | return d3[curveName] || defaultCurve
60 | }
61 |
62 | export default {
63 | detectType,
64 | isSubstringInArray,
65 | interpolateToCurve
66 | }
67 |
--------------------------------------------------------------------------------
/assets/mermaidAPI.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import mermaidAPI from './mermaidAPI'
3 |
4 | describe('when using mermaidAPI and ', function () {
5 | describe('doing initialize ', function () {
6 | beforeEach(function () {
7 | document.body.innerHTML = ''
8 | })
9 |
10 | it('should copy a literal into the configuration', function () {
11 | const orgConfig = mermaidAPI.getConfig()
12 | expect(orgConfig.testLiteral).toBe(undefined)
13 |
14 | mermaidAPI.initialize({ 'testLiteral': true })
15 | const config = mermaidAPI.getConfig()
16 |
17 | expect(config.testLiteral).toBe(true)
18 | })
19 | it('should copy a an object into the configuration', function () {
20 | const orgConfig = mermaidAPI.getConfig()
21 | expect(orgConfig.testObject).toBe(undefined)
22 |
23 | const object = {
24 | test1: 1,
25 | test2: false
26 | }
27 |
28 | mermaidAPI.initialize({ 'testObject': object })
29 | mermaidAPI.initialize({ 'testObject': { 'test3': true } })
30 | const config = mermaidAPI.getConfig()
31 |
32 | expect(config.testObject.test1).toBe(1)
33 | expect(config.testObject.test2).toBe(false)
34 | expect(config.testObject.test3).toBe(true)
35 | })
36 | })
37 | describe('checking validity of input ', function () {
38 | it('it should throw for an invalid definiton', function () {
39 | expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow()
40 | })
41 | it('it should not throw for a valid definiton', function () {
42 | expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow()
43 | })
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/assets/diagrams/gantt/parser/gantt.jison:
--------------------------------------------------------------------------------
1 | /** mermaid
2 | * https://mermaidjs.github.io/
3 | * (c) 2015 Knut Sveidqvist
4 | * MIT license.
5 | */
6 | %lex
7 |
8 | %options case-insensitive
9 |
10 | %{
11 | // Pre-lexer code can go here
12 | %}
13 |
14 | %%
15 |
16 | [\n]+ return 'NL';
17 | \s+ /* skip whitespace */
18 | \#[^\n]* /* skip comments */
19 | \%%[^\n]* /* skip comments */
20 | "gantt" return 'gantt';
21 | "dateFormat"\s[^#\n;]+ return 'dateFormat';
22 | "axisFormat"\s[^#\n;]+ return 'axisFormat';
23 | \d\d\d\d"-"\d\d"-"\d\d return 'date';
24 | "title"\s[^#\n;]+ return 'title';
25 | "section"\s[^#:\n;]+ return 'section';
26 | [^#:\n;]+ return 'taskTxt';
27 | ":"[^#\n;]+ return 'taskData';
28 | ":" return ':';
29 | <> return 'EOF';
30 | . return 'INVALID';
31 |
32 | /lex
33 |
34 | %left '^'
35 |
36 | %start start
37 |
38 | %% /* language grammar */
39 |
40 | start
41 | : gantt document 'EOF' { return $2; }
42 | ;
43 |
44 | document
45 | : /* empty */ { $$ = [] }
46 | | document line {$1.push($2);$$ = $1}
47 | ;
48 |
49 | line
50 | : SPACE statement { $$ = $2 }
51 | | statement { $$ = $1 }
52 | | NL { $$=[];}
53 | | EOF { $$=[];}
54 | ;
55 |
56 | statement
57 | : 'dateFormat' {yy.setDateFormat($1.substr(11));$$=$1.substr(11);}
58 | | 'axisFormat' {yy.setAxisFormat($1.substr(11));$$=$1.substr(11);}
59 | | title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
60 | | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
61 | | taskTxt taskData {yy.addTask($1,$2);$$='task';}
62 | ;
63 |
64 | %%
65 |
--------------------------------------------------------------------------------
/assets/diagrams/class/classDb.js:
--------------------------------------------------------------------------------
1 |
2 | import { logger } from '../../logger'
3 |
4 | let relations = []
5 | let classes = {}
6 |
7 | /**
8 | * Function called by parser when a node definition has been found.
9 | * @param id
10 | * @param text
11 | * @param type
12 | * @param style
13 | */
14 | export const addClass = function (id) {
15 | if (typeof classes[id] === 'undefined') {
16 | classes[id] = {
17 | id: id,
18 | methods: [],
19 | members: []
20 | }
21 | }
22 | }
23 |
24 | export const clear = function () {
25 | relations = []
26 | classes = {}
27 | }
28 |
29 | export const getClass = function (id) {
30 | return classes[id]
31 | }
32 | export const getClasses = function () {
33 | return classes
34 | }
35 |
36 | export const getRelations = function () {
37 | return relations
38 | }
39 |
40 | export const addRelation = function (relation) {
41 | logger.debug('Adding relation: ' + JSON.stringify(relation))
42 | addClass(relation.id1)
43 | addClass(relation.id2)
44 | relations.push(relation)
45 | }
46 |
47 | export const addMembers = function (className, MembersArr) {
48 | const theClass = classes[className]
49 | if (typeof MembersArr === 'string') {
50 | if (MembersArr.substr(-1) === ')') {
51 | theClass.methods.push(MembersArr)
52 | } else {
53 | theClass.members.push(MembersArr)
54 | }
55 | }
56 | }
57 |
58 | export const cleanupLabel = function (label) {
59 | if (label.substring(0, 1) === ':') {
60 | return label.substr(2).trim()
61 | } else {
62 | return label.trim()
63 | }
64 | }
65 |
66 | export const lineType = {
67 | LINE: 0,
68 | DOTTED_LINE: 1
69 | }
70 |
71 | export const relationType = {
72 | AGGREGATION: 0,
73 | EXTENSION: 1,
74 | COMPOSITION: 2,
75 | DEPENDENCY: 3
76 | }
77 |
78 | export default {
79 | addClass,
80 | clear,
81 | getClass,
82 | getClasses,
83 | getRelations,
84 | addRelation,
85 | addMembers,
86 | cleanupLabel,
87 | lineType,
88 | relationType
89 | }
90 |
--------------------------------------------------------------------------------
/assets/diagrams/git/parser/gitGraph.jison:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Parse following
4 | * gitGraph:
5 | * commit
6 | * commit
7 | * branch
8 | */
9 | %lex
10 |
11 | %x string
12 | %x options
13 | %options case-insensitive
14 |
15 | %%
16 |
17 | (\r?\n)+ return 'NL';
18 | \s+ /* skip all whitespace */
19 | \#[^\n]* /* skip comments */
20 | \%%[^\n]* /* skip comments */
21 | "gitGraph" return 'GG';
22 | "commit" return 'COMMIT';
23 | "branch" return 'BRANCH';
24 | "merge" return 'MERGE';
25 | "reset" return 'RESET';
26 | "checkout" return 'CHECKOUT';
27 | "LR" return 'DIR';
28 | "BT" return 'DIR';
29 | ":" return ':';
30 | "^" return 'CARET'
31 | "options"\r?\n this.begin("options");
32 | "end"\r?\n this.popState();
33 | [^\n]+\r?\n return 'OPT';
34 | ["] this.begin("string");
35 | ["] this.popState();
36 | [^"]* return 'STR';
37 | [a-zA-Z][a-zA-Z0-9_]+ return 'ID';
38 | <> return 'EOF';
39 |
40 | /lex
41 |
42 | %left '^'
43 |
44 | %start start
45 |
46 | %% /* language grammar */
47 |
48 | start
49 | : GG ':' document EOF{ return $3; }
50 | | GG DIR ':' document EOF {yy.setDirection($2); return $4;}
51 | ;
52 |
53 |
54 | document
55 | : /*empty*/
56 | | options body { yy.setOptions($1); $$ = $2}
57 | ;
58 |
59 | options
60 | : options OPT {$1 +=$2; $$=$1}
61 | | NL
62 | ;
63 | body
64 | : /*emmpty*/ {$$ = []}
65 | | body line {$1.push($2); $$=$1;}
66 | ;
67 | line
68 | : statement NL{$$ =$1}
69 | | NL
70 | ;
71 |
72 | statement
73 | : COMMIT commit_arg {yy.commit($2)}
74 | | BRANCH ID {yy.branch($2)}
75 | | CHECKOUT ID {yy.checkout($2)}
76 | | MERGE ID {yy.merge($2)}
77 | | RESET reset_arg {yy.reset($2)}
78 | ;
79 |
80 | commit_arg
81 | : /* empty */ {$$ = ""}
82 | | STR {$$=$1}
83 | ;
84 |
85 | reset_arg
86 | : 'HEAD' reset_parents{$$ = $1+ ":" + $2 }
87 | | ID reset_parents{$$ = $1+ ":" + yy.count; yy.count = 0}
88 | ;
89 | reset_parents
90 | : /* empty */ {yy.count = 0}
91 | | CARET reset_parents { yy.count += 1 }
92 | ;
93 |
--------------------------------------------------------------------------------
/assets/themes/gantt.scss:
--------------------------------------------------------------------------------
1 | /** Section styling */
2 |
3 | .section {
4 | stroke: none;
5 | opacity: 0.2;
6 | }
7 |
8 | .section0 {
9 | fill: $sectionBkgColor;
10 | }
11 |
12 | .section2 {
13 | fill: $sectionBkgColor2;
14 | }
15 |
16 | .section1,
17 | .section3 {
18 | fill: $altSectionBkgColor;
19 | opacity: 0.2;
20 | }
21 |
22 | .sectionTitle0 {
23 | fill: $titleColor;
24 | }
25 |
26 | .sectionTitle1 {
27 | fill: $titleColor;
28 | }
29 |
30 | .sectionTitle2 {
31 | fill: $titleColor;
32 | }
33 |
34 | .sectionTitle3 {
35 | fill: $titleColor;
36 | }
37 |
38 | .sectionTitle {
39 | text-anchor: start;
40 | font-size: 11px;
41 | text-height: 14px;
42 | }
43 |
44 |
45 | /* Grid and axis */
46 |
47 | .grid .tick {
48 | stroke: $gridColor;
49 | opacity: 0.3;
50 | shape-rendering: crispEdges;
51 | }
52 |
53 | .grid path {
54 | stroke-width: 0;
55 | }
56 |
57 |
58 | /* Today line */
59 |
60 | .today {
61 | fill: none;
62 | stroke: $todayLineColor;
63 | stroke-width: 2px;
64 | }
65 |
66 |
67 | /* Task styling */
68 |
69 |
70 | /* Default task */
71 |
72 | .task {
73 | stroke-width: 2;
74 | }
75 |
76 | .taskText {
77 | text-anchor: middle;
78 | font-size: 11px;
79 | }
80 |
81 | .taskTextOutsideRight {
82 | fill: $taskTextDarkColor;
83 | text-anchor: start;
84 | font-size: 11px;
85 | }
86 |
87 | .taskTextOutsideLeft {
88 | fill: $taskTextDarkColor;
89 | text-anchor: end;
90 | font-size: 11px;
91 | }
92 |
93 |
94 | /* Specific task settings for the sections*/
95 |
96 | .taskText0,
97 | .taskText1,
98 | .taskText2,
99 | .taskText3 {
100 | fill: $taskTextColor;
101 | }
102 |
103 | .task0,
104 | .task1,
105 | .task2,
106 | .task3 {
107 | fill: $taskBkgColor;
108 | stroke: $taskBorderColor;
109 | }
110 |
111 | .taskTextOutside0,
112 | .taskTextOutside2,
113 | {
114 | fill: $taskTextOutsideColor;
115 | }
116 |
117 | .taskTextOutside1,
118 | .taskTextOutside3 {
119 | fill: $taskTextOutsideColor;
120 | }
121 |
122 |
123 | /* Active task */
124 |
125 | .active0,
126 | .active1,
127 | .active2,
128 | .active3 {
129 | fill: $activeTaskBkgColor;
130 | stroke: $activeTaskBorderColor;
131 | }
132 |
133 | .activeText0,
134 | .activeText1,
135 | .activeText2,
136 | .activeText3 {
137 | fill: $taskTextDarkColor !important;
138 | }
139 |
140 |
141 | /* Completed task */
142 |
143 | .done0,
144 | .done1,
145 | .done2,
146 | .done3 {
147 | stroke: $doneTaskBorderColor;
148 | fill: $doneTaskBkgColor;
149 | stroke-width: 2;
150 | }
151 |
152 | .doneText0,
153 | .doneText1,
154 | .doneText2,
155 | .doneText3 {
156 | fill: $taskTextDarkColor !important;
157 | }
158 |
159 |
160 | /* Tasks on the critical line */
161 |
162 | .crit0,
163 | .crit1,
164 | .crit2,
165 | .crit3 {
166 | stroke: $critBorderColor;
167 | fill: $critBkgColor;
168 | stroke-width: 2;
169 | }
170 |
171 | .activeCrit0,
172 | .activeCrit1,
173 | .activeCrit2,
174 | .activeCrit3 {
175 | stroke: $critBorderColor;
176 | fill: $activeTaskBkgColor;
177 | stroke-width: 2;
178 | }
179 |
180 | .doneCrit0,
181 | .doneCrit1,
182 | .doneCrit2,
183 | .doneCrit3 {
184 | stroke: $critBorderColor;
185 | fill: $doneTaskBkgColor;
186 | stroke-width: 2;
187 | cursor: pointer;
188 | shape-rendering: crispEdges;
189 | }
190 |
191 | .doneCritText0,
192 | .doneCritText1,
193 | .doneCritText2,
194 | .doneCritText3 {
195 | fill: $taskTextDarkColor !important;
196 | }
197 |
198 | .activeCritText0,
199 | .activeCritText1,
200 | .activeCritText2,
201 | .activeCritText3 {
202 | fill: $taskTextDarkColor !important;
203 | }
204 |
205 | .titleText {
206 | text-anchor: middle;
207 | font-size: 18px;
208 | fill: $taskTextDarkColor;
209 | }
210 |
--------------------------------------------------------------------------------
/assets/diagrams/sequence/sequenceDb.js:
--------------------------------------------------------------------------------
1 | import { logger } from '../../logger'
2 |
3 | let actors = {}
4 | let messages = []
5 | const notes = []
6 | let title = ''
7 |
8 | export const addActor = function (id, name, description) {
9 | // Don't allow description nulling
10 | const old = actors[id]
11 | if (old && name === old.name && description == null) return
12 |
13 | // Don't allow null descriptions, either
14 | if (description == null) description = name
15 |
16 | actors[id] = { name: name, description: description }
17 | }
18 |
19 | export const addMessage = function (idFrom, idTo, message, answer) {
20 | messages.push({ from: idFrom, to: idTo, message: message, answer: answer })
21 | }
22 |
23 | export const addSignal = function (idFrom, idTo, message, messageType) {
24 | logger.debug('Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType)
25 | messages.push({ from: idFrom, to: idTo, message: message, type: messageType })
26 | }
27 |
28 | export const getMessages = function () {
29 | return messages
30 | }
31 |
32 | export const getActors = function () {
33 | return actors
34 | }
35 | export const getActor = function (id) {
36 | return actors[id]
37 | }
38 | export const getActorKeys = function () {
39 | return Object.keys(actors)
40 | }
41 | export const getTitle = function () {
42 | return title
43 | }
44 |
45 | export const clear = function () {
46 | actors = {}
47 | messages = []
48 | }
49 |
50 | export const LINETYPE = {
51 | SOLID: 0,
52 | DOTTED: 1,
53 | NOTE: 2,
54 | SOLID_CROSS: 3,
55 | DOTTED_CROSS: 4,
56 | SOLID_OPEN: 5,
57 | DOTTED_OPEN: 6,
58 | LOOP_START: 10,
59 | LOOP_END: 11,
60 | ALT_START: 12,
61 | ALT_ELSE: 13,
62 | ALT_END: 14,
63 | OPT_START: 15,
64 | OPT_END: 16,
65 | ACTIVE_START: 17,
66 | ACTIVE_END: 18,
67 | PAR_START: 19,
68 | PAR_AND: 20,
69 | PAR_END: 21
70 | }
71 |
72 | export const ARROWTYPE = {
73 | FILLED: 0,
74 | OPEN: 1
75 | }
76 |
77 | export const PLACEMENT = {
78 | LEFTOF: 0,
79 | RIGHTOF: 1,
80 | OVER: 2
81 | }
82 |
83 | export const addNote = function (actor, placement, message) {
84 | const note = { actor: actor, placement: placement, message: message }
85 |
86 | // Coerce actor into a [to, from, ...] array
87 | const actors = [].concat(actor, actor)
88 |
89 | notes.push(note)
90 | messages.push({ from: actors[0], to: actors[1], message: message, type: LINETYPE.NOTE, placement: placement })
91 | }
92 |
93 | export const setTitle = function (titleText) {
94 | title = titleText
95 | }
96 |
97 | export const apply = function (param) {
98 | if (param instanceof Array) {
99 | param.forEach(function (item) {
100 | apply(item)
101 | })
102 | } else {
103 | switch (param.type) {
104 | case 'addActor':
105 | addActor(param.actor, param.actor, param.description)
106 | break
107 | case 'activeStart':
108 | addSignal(param.actor, undefined, undefined, param.signalType)
109 | break
110 | case 'activeEnd':
111 | addSignal(param.actor, undefined, undefined, param.signalType)
112 | break
113 | case 'addNote':
114 | addNote(param.actor, param.placement, param.text)
115 | break
116 | case 'addMessage':
117 | addSignal(param.from, param.to, param.msg, param.signalType)
118 | break
119 | case 'loopStart':
120 | addSignal(undefined, undefined, param.loopText, param.signalType)
121 | break
122 | case 'loopEnd':
123 | addSignal(undefined, undefined, undefined, param.signalType)
124 | break
125 | case 'optStart':
126 | addSignal(undefined, undefined, param.optText, param.signalType)
127 | break
128 | case 'optEnd':
129 | addSignal(undefined, undefined, undefined, param.signalType)
130 | break
131 | case 'altStart':
132 | addSignal(undefined, undefined, param.altText, param.signalType)
133 | break
134 | case 'else':
135 | addSignal(undefined, undefined, param.altText, param.signalType)
136 | break
137 | case 'altEnd':
138 | addSignal(undefined, undefined, undefined, param.signalType)
139 | break
140 | case 'setTitle':
141 | setTitle(param.text)
142 | break
143 | case 'parStart':
144 | addSignal(undefined, undefined, param.parText, param.signalType)
145 | break
146 | case 'and':
147 | addSignal(undefined, undefined, param.parText, param.signalType)
148 | break
149 | case 'parEnd':
150 | addSignal(undefined, undefined, undefined, param.signalType)
151 | break
152 | }
153 | }
154 | }
155 |
156 | export default {
157 | addActor,
158 | addMessage,
159 | addSignal,
160 | getMessages,
161 | getActors,
162 | getActor,
163 | getActorKeys,
164 | getTitle,
165 | clear,
166 | LINETYPE,
167 | ARROWTYPE,
168 | PLACEMENT,
169 | addNote,
170 | setTitle,
171 | apply
172 | }
173 |
--------------------------------------------------------------------------------
/assets/mermaid.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid functionality and to render
3 | * the diagrams to svg code.
4 | */
5 | import he from 'he'
6 |
7 | import mermaidAPI from './mermaidAPI'
8 | import { logger } from './logger'
9 |
10 | /**
11 | * ## init
12 | * Function that goes through the document to find the chart definitions in there and render them.
13 | *
14 | * The function tags the processed attributes with the attribute data-processed and ignores found elements with the
15 | * attribute already set. This way the init function can be triggered several times.
16 | *
17 | * Optionally, `init` can accept in the second argument one of the following:
18 | * - a DOM Node
19 | * - an array of DOM nodes (as would come from a jQuery selector)
20 | * - a W3C selector, a la `.mermaid`
21 | *
22 | * ```mermaid
23 | * graph LR;
24 | * a(Find elements)-->b{Processed}
25 | * b-->|Yes|c(Leave element)
26 | * b-->|No |d(Transform)
27 | * ```
28 | * Renders the mermaid diagrams
29 | * @param nodes a css selector or an array of nodes
30 | */
31 | const init = function () {
32 | const conf = mermaidAPI.getConfig()
33 | logger.debug('Starting rendering diagrams')
34 | let nodes
35 | if (arguments.length >= 2) {
36 | /*! sequence config was passed as #1 */
37 | if (typeof arguments[0] !== 'undefined') {
38 | mermaid.sequenceConfig = arguments[0]
39 | }
40 |
41 | nodes = arguments[1]
42 | } else {
43 | nodes = arguments[0]
44 | }
45 |
46 | // if last argument is a function this is the callback function
47 | let callback
48 | if (typeof arguments[arguments.length - 1] === 'function') {
49 | callback = arguments[arguments.length - 1]
50 | logger.debug('Callback function found')
51 | } else {
52 | if (typeof conf.mermaid !== 'undefined') {
53 | if (typeof conf.mermaid.callback === 'function') {
54 | callback = conf.mermaid.callback
55 | logger.debug('Callback function found')
56 | } else {
57 | logger.debug('No Callback function found')
58 | }
59 | }
60 | }
61 | nodes = nodes === undefined ? document.querySelectorAll('.mermaid')
62 | : typeof nodes === 'string' ? document.querySelectorAll(nodes)
63 | : nodes instanceof window.Node ? [nodes]
64 | : nodes // Last case - sequence config was passed pick next
65 |
66 | logger.debug('Start On Load before: ' + mermaid.startOnLoad)
67 | if (typeof mermaid.startOnLoad !== 'undefined') {
68 | logger.debug('Start On Load inner: ' + mermaid.startOnLoad)
69 | mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad })
70 | }
71 |
72 | if (typeof mermaid.ganttConfig !== 'undefined') {
73 | mermaidAPI.initialize({ gantt: mermaid.ganttConfig })
74 | }
75 |
76 | let txt
77 |
78 | for (let i = 0; i < nodes.length; i++) {
79 | const element = nodes[i]
80 |
81 | /*! Check if previously processed */
82 | if (!element.getAttribute('data-processed')) {
83 | element.setAttribute('data-processed', true)
84 | } else {
85 | continue
86 | }
87 |
88 | const id = `mermaid-${Date.now()}`
89 |
90 | // Fetch the graph definition including tags
91 | txt = element.innerHTML
92 |
93 | // transforms the html to pure text
94 | txt = he.decode(txt).trim().replace(/
/ig, '
')
95 |
96 | mermaidAPI.render(id, txt, (svgCode, bindFunctions) => {
97 | element.innerHTML = svgCode
98 | if (typeof callback !== 'undefined') {
99 | callback(id)
100 | }
101 | bindFunctions(element)
102 | }, element)
103 | }
104 | }
105 |
106 | const initialize = function (config) {
107 | logger.debug('Initializing mermaid')
108 | if (typeof config.mermaid !== 'undefined') {
109 | if (typeof config.mermaid.startOnLoad !== 'undefined') {
110 | mermaid.startOnLoad = config.mermaid.startOnLoad
111 | }
112 | if (typeof config.mermaid.htmlLabels !== 'undefined') {
113 | mermaid.htmlLabels = config.mermaid.htmlLabels
114 | }
115 | }
116 | mermaidAPI.initialize(config)
117 | }
118 |
119 | /**
120 | * ##contentLoaded
121 | * Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and
122 | * calls init for rendering the mermaid diagrams on the page.
123 | */
124 | const contentLoaded = function () {
125 | let config
126 |
127 | if (mermaid.startOnLoad) {
128 | // No config found, do check API config
129 | config = mermaidAPI.getConfig()
130 | if (config.startOnLoad) {
131 | mermaid.init()
132 | }
133 | } else {
134 | if (typeof mermaid.startOnLoad === 'undefined') {
135 | logger.debug('In start, no config')
136 | config = mermaidAPI.getConfig()
137 | if (config.startOnLoad) {
138 | mermaid.init()
139 | }
140 | }
141 | }
142 | }
143 |
144 | if (typeof document !== 'undefined') {
145 | /*!
146 | * Wait for document loaded before starting the execution
147 | */
148 | window.addEventListener('load', function () {
149 | contentLoaded()
150 | }, false)
151 | }
152 |
153 | const mermaid = {
154 | startOnLoad: true,
155 | htmlLabels: true,
156 |
157 | mermaidAPI,
158 | parse: mermaidAPI.parse,
159 | render: mermaidAPI.render,
160 |
161 | init,
162 | initialize,
163 |
164 | contentLoaded
165 | }
166 |
167 | export default mermaid
168 |
--------------------------------------------------------------------------------
/assets/diagrams/sequence/parser/sequenceDiagram.jison:
--------------------------------------------------------------------------------
1 | /** mermaid
2 | * https://mermaidjs.github.io/
3 | * (c) 2014-2015 Knut Sveidqvist
4 | * MIT license.
5 | *
6 | * Based on js sequence diagrams jison grammr
7 | * http://bramp.github.io/js-sequence-diagrams/
8 | * (c) 2012-2013 Andrew Brampton (bramp.net)
9 | * Simplified BSD license.
10 | */
11 | %lex
12 |
13 | %options case-insensitive
14 |
15 | // Special states for recognizing aliases
16 | %x ID
17 | %x ALIAS
18 |
19 | // A special state for grabbing text up to the first comment/newline
20 | %x LINE
21 |
22 | %%
23 |
24 | [\n]+ return 'NL';
25 | \s+ /* skip all whitespace */
26 | ((?!\n)\s)+ /* skip same-line whitespace */
27 | \#[^\n]* /* skip comments */
28 | \%%[^\n]* /* skip comments */
29 | "participant" { this.begin('ID'); return 'participant'; }
30 | [^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { this.begin('ALIAS'); return 'ACTOR'; }
31 | "as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
32 | (?:) { this.popState(); this.popState(); return 'NL'; }
33 | "loop" { this.begin('LINE'); return 'loop'; }
34 | "opt" { this.begin('LINE'); return 'opt'; }
35 | "alt" { this.begin('LINE'); return 'alt'; }
36 | "else" { this.begin('LINE'); return 'else'; }
37 | "par" { this.begin('LINE'); return 'par'; }
38 | "and" { this.begin('LINE'); return 'and'; }
39 | [^#\n;]* { this.popState(); return 'restOfLine'; }
40 | "end" return 'end';
41 | "left of" return 'left_of';
42 | "right of" return 'right_of';
43 | "over" return 'over';
44 | "note" return 'note';
45 | "activate" { this.begin('ID'); return 'activate'; }
46 | "deactivate" { this.begin('ID'); return 'deactivate'; }
47 | "title" return 'title';
48 | "sequenceDiagram" return 'SD';
49 | "," return ',';
50 | ";" return 'NL';
51 | [^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
52 | "->>" return 'SOLID_ARROW';
53 | "-->>" return 'DOTTED_ARROW';
54 | "->" return 'SOLID_OPEN_ARROW';
55 | "-->" return 'DOTTED_OPEN_ARROW';
56 | \-[x] return 'SOLID_CROSS';
57 | \-\-[x] return 'DOTTED_CROSS';
58 | ":"[^#\n;]+ return 'TXT';
59 | "+" return '+';
60 | "-" return '-';
61 | <> return 'NL';
62 | . return 'INVALID';
63 |
64 | /lex
65 |
66 | %left '^'
67 |
68 | %start start
69 |
70 | %% /* language grammar */
71 |
72 | start
73 | : SPACE start
74 | | NL start
75 | | SD document { yy.apply($2);return $2; }
76 | ;
77 |
78 | document
79 | : /* empty */ { $$ = [] }
80 | | document line {$1.push($2);$$ = $1}
81 | ;
82 |
83 | line
84 | : SPACE statement { $$ = $2 }
85 | | statement { $$ = $1 }
86 | | NL { $$=[];}
87 | ;
88 |
89 | statement
90 | : 'participant' actor 'AS' restOfLine 'NL' {$2.description=$4; $$=$2;}
91 | | 'participant' actor 'NL' {$$=$2;}
92 | | signal 'NL'
93 | | 'activate' actor 'NL' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};}
94 | | 'deactivate' actor 'NL' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};}
95 | | note_statement 'NL'
96 | | title text2 'NL' {$$=[{type:'setTitle', text:$2}]}
97 | | 'loop' restOfLine document end
98 | {
99 | $3.unshift({type: 'loopStart', loopText:$2, signalType: yy.LINETYPE.LOOP_START});
100 | $3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END});
101 | $$=$3;}
102 | | opt restOfLine document end
103 | {
104 | $3.unshift({type: 'optStart', optText:$2, signalType: yy.LINETYPE.OPT_START});
105 | $3.push({type: 'optEnd', optText:$2, signalType: yy.LINETYPE.OPT_END});
106 | $$=$3;}
107 | | alt restOfLine else_sections end
108 | {
109 | // Alt start
110 | $3.unshift({type: 'altStart', altText:$2, signalType: yy.LINETYPE.ALT_START});
111 | // Content in alt is already in $3
112 | // End
113 | $3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END});
114 | $$=$3;}
115 | | par restOfLine par_sections end
116 | {
117 | // Parallel start
118 | $3.unshift({type: 'parStart', parText:$2, signalType: yy.LINETYPE.PAR_START});
119 | // Content in par is already in $3
120 | // End
121 | $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
122 | $$=$3;}
123 | ;
124 |
125 | par_sections
126 | : document
127 | | document and restOfLine par_sections
128 | { $$ = $1.concat([{type: 'and', parText:$3, signalType: yy.LINETYPE.PAR_AND}, $4]); }
129 | ;
130 |
131 | else_sections
132 | : document
133 | | document else restOfLine else_sections
134 | { $$ = $1.concat([{type: 'else', altText:$3, signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
135 | ;
136 |
137 | note_statement
138 | : 'note' placement actor text2
139 | {
140 | $$ = [$3, {type:'addNote', placement:$2, actor:$3.actor, text:$4}];}
141 | | 'note' 'over' actor_pair text2
142 | {
143 | // Coerce actor_pair into a [to, from, ...] array
144 | $2 = [].concat($3, $3).slice(0, 2);
145 | $2[0] = $2[0].actor;
146 | $2[1] = $2[1].actor;
147 | $$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$4}];}
148 | ;
149 |
150 | spaceList
151 | : SPACE spaceList
152 | | SPACE
153 | ;
154 | actor_pair
155 | : actor ',' actor { $$ = [$1, $3]; }
156 | | actor { $$ = $1; }
157 | ;
158 |
159 | placement
160 | : 'left_of' { $$ = yy.PLACEMENT.LEFTOF; }
161 | | 'right_of' { $$ = yy.PLACEMENT.RIGHTOF; }
162 | ;
163 |
164 | signal
165 | : actor signaltype '+' actor text2
166 | { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5},
167 | {type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $4}
168 | ]}
169 | | actor signaltype '-' actor text2
170 | { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5},
171 | {type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $1}
172 | ]}
173 | | actor signaltype actor text2
174 | { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
175 | ;
176 |
177 | actor
178 | : ACTOR {$$={type: 'addActor', actor:$1}}
179 | ;
180 |
181 | signaltype
182 | : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; }
183 | | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; }
184 | | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; }
185 | | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; }
186 | | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; }
187 | | DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; }
188 | ;
189 |
190 | text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ;
191 |
192 | %%
193 |
--------------------------------------------------------------------------------
/assets/diagrams/git/gitGraphAst.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import { logger } from '../../logger'
4 |
5 | let commits = {}
6 | let head = null
7 | let branches = { 'master': head }
8 | let curBranch = 'master'
9 | let direction = 'LR'
10 | let seq = 0
11 |
12 | function getRandomInt (min, max) {
13 | return Math.floor(Math.random() * (max - min)) + min
14 | }
15 |
16 | function getId () {
17 | const pool = '0123456789abcdef'
18 | let id = ''
19 | for (let i = 0; i < 7; i++) {
20 | id += pool[getRandomInt(0, 16)]
21 | }
22 | return id
23 | }
24 |
25 | function isfastforwardable (currentCommit, otherCommit) {
26 | logger.debug('Entering isfastforwardable:', currentCommit.id, otherCommit.id)
27 | while (currentCommit.seq <= otherCommit.seq && currentCommit !== otherCommit) {
28 | // only if other branch has more commits
29 | if (otherCommit.parent == null) break
30 | if (Array.isArray(otherCommit.parent)) {
31 | logger.debug('In merge commit:', otherCommit.parent)
32 | return isfastforwardable(currentCommit, commits[otherCommit.parent[0]]) ||
33 | isfastforwardable(currentCommit, commits[otherCommit.parent[1]])
34 | } else {
35 | otherCommit = commits[otherCommit.parent]
36 | }
37 | }
38 | logger.debug(currentCommit.id, otherCommit.id)
39 | return currentCommit.id === otherCommit.id
40 | }
41 |
42 | function isReachableFrom (currentCommit, otherCommit) {
43 | const currentSeq = currentCommit.seq
44 | const otherSeq = otherCommit.seq
45 | if (currentSeq > otherSeq) return isfastforwardable(otherCommit, currentCommit)
46 | return false
47 | }
48 |
49 | export const setDirection = function (dir) {
50 | direction = dir
51 | }
52 | let options = {}
53 | export const setOptions = function (rawOptString) {
54 | logger.debug('options str', rawOptString)
55 | rawOptString = rawOptString && rawOptString.trim()
56 | rawOptString = rawOptString || '{}'
57 | try {
58 | options = JSON.parse(rawOptString)
59 | } catch (e) {
60 | logger.error('error while parsing gitGraph options', e.message)
61 | }
62 | }
63 |
64 | export const getOptions = function () {
65 | return options
66 | }
67 |
68 | export const commit = function (msg) {
69 | const commit = {
70 | id: getId(),
71 | message: msg,
72 | seq: seq++,
73 | parent: head == null ? null : head.id
74 | }
75 | head = commit
76 | commits[commit.id] = commit
77 | branches[curBranch] = commit.id
78 | logger.debug('in pushCommit ' + commit.id)
79 | }
80 |
81 | export const branch = function (name) {
82 | branches[name] = head != null ? head.id : null
83 | logger.debug('in createBranch')
84 | }
85 |
86 | export const merge = function (otherBranch) {
87 | const currentCommit = commits[branches[curBranch]]
88 | const otherCommit = commits[branches[otherBranch]]
89 | if (isReachableFrom(currentCommit, otherCommit)) {
90 | logger.debug('Already merged')
91 | return
92 | }
93 | if (isfastforwardable(currentCommit, otherCommit)) {
94 | branches[curBranch] = branches[otherBranch]
95 | head = commits[branches[curBranch]]
96 | } else {
97 | // create merge commit
98 | const commit = {
99 | id: getId(),
100 | message: 'merged branch ' + otherBranch + ' into ' + curBranch,
101 | seq: seq++,
102 | parent: [head == null ? null : head.id, branches[otherBranch]]
103 | }
104 | head = commit
105 | commits[commit.id] = commit
106 | branches[curBranch] = commit.id
107 | }
108 | logger.debug(branches)
109 | logger.debug('in mergeBranch')
110 | }
111 |
112 | export const checkout = function (branch) {
113 | logger.debug('in checkout')
114 | curBranch = branch
115 | const id = branches[curBranch]
116 | head = commits[id]
117 | }
118 |
119 | export const reset = function (commitRef) {
120 | logger.debug('in reset', commitRef)
121 | const ref = commitRef.split(':')[0]
122 | let parentCount = parseInt(commitRef.split(':')[1])
123 | let commit = ref === 'HEAD' ? head : commits[branches[ref]]
124 | logger.debug(commit, parentCount)
125 | while (parentCount > 0) {
126 | commit = commits[commit.parent]
127 | parentCount--
128 | if (!commit) {
129 | const err = 'Critical error - unique parent commit not found during reset'
130 | logger.error(err)
131 | throw err
132 | }
133 | }
134 | head = commit
135 | branches[curBranch] = commit.id
136 | }
137 |
138 | function upsert (arr, key, newval) {
139 | const index = arr.indexOf(key)
140 | if (index === -1) {
141 | arr.push(newval)
142 | } else {
143 | arr.splice(index, 1, newval)
144 | }
145 | }
146 |
147 | function prettyPrintCommitHistory (commitArr) {
148 | const commit = _.maxBy(commitArr, 'seq')
149 | let line = ''
150 | commitArr.forEach(function (c) {
151 | if (c === commit) {
152 | line += '\t*'
153 | } else {
154 | line += '\t|'
155 | }
156 | })
157 | const label = [line, commit.id, commit.seq]
158 | _.each(branches, function (value, key) {
159 | if (value === commit.id) label.push(key)
160 | })
161 | logger.debug(label.join(' '))
162 | if (Array.isArray(commit.parent)) {
163 | const newCommit = commits[commit.parent[0]]
164 | upsert(commitArr, commit, newCommit)
165 | commitArr.push(commits[commit.parent[1]])
166 | } else if (commit.parent == null) {
167 | return
168 | } else {
169 | const nextCommit = commits[commit.parent]
170 | upsert(commitArr, commit, nextCommit)
171 | }
172 | commitArr = _.uniqBy(commitArr, 'id')
173 | prettyPrintCommitHistory(commitArr)
174 | }
175 |
176 | export const prettyPrint = function () {
177 | logger.debug(commits)
178 | const node = getCommitsArray()[0]
179 | prettyPrintCommitHistory([node])
180 | }
181 |
182 | export const clear = function () {
183 | commits = {}
184 | head = null
185 | branches = { 'master': head }
186 | curBranch = 'master'
187 | seq = 0
188 | }
189 |
190 | export const getBranchesAsObjArray = function () {
191 | const branchArr = _.map(branches, function (value, key) {
192 | return { 'name': key, 'commit': commits[value] }
193 | })
194 | return branchArr
195 | }
196 |
197 | export const getBranches = function () { return branches }
198 | export const getCommits = function () { return commits }
199 | export const getCommitsArray = function () {
200 | const commitArr = Object.keys(commits).map(function (key) {
201 | return commits[key]
202 | })
203 | commitArr.forEach(function (o) { logger.debug(o.id) })
204 | return _.orderBy(commitArr, ['seq'], ['desc'])
205 | }
206 | export const getCurrentBranch = function () { return curBranch }
207 | export const getDirection = function () { return direction }
208 | export const getHead = function () { return head }
209 |
210 | export default {
211 | setDirection,
212 | setOptions,
213 | getOptions,
214 | commit,
215 | branch,
216 | merge,
217 | checkout,
218 | reset,
219 | prettyPrint,
220 | clear,
221 | getBranchesAsObjArray,
222 | getBranches,
223 | getCommits,
224 | getCommitsArray,
225 | getCurrentBranch,
226 | getDirection,
227 | getHead
228 | }
229 |
--------------------------------------------------------------------------------
/assets/diagrams/class/classDiagram.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import { parser } from './parser/classDiagram'
3 | import classDb from './classDb'
4 |
5 | describe('class diagram, ', function () {
6 | describe('when parsing an info graph it', function () {
7 | beforeEach(function () {
8 | parser.yy = classDb
9 | })
10 |
11 | it('should handle relation definitions', function () {
12 | const str = 'classDiagram\n' +
13 | 'Class01 <|-- Class02\n' +
14 | 'Class03 *-- Class04\n' +
15 | 'Class05 o-- Class06\n' +
16 | 'Class07 .. Class08\n' +
17 | 'Class09 -- Class1'
18 |
19 | parser.parse(str)
20 | })
21 | it('should handle relation definition of different types and directions', function () {
22 | const str = 'classDiagram\n' +
23 | 'Class11 <|.. Class12\n' +
24 | 'Class13 --> Class14\n' +
25 | 'Class15 ..> Class16\n' +
26 | 'Class17 ..|> Class18\n' +
27 | 'Class19 <--* Class20'
28 |
29 | parser.parse(str)
30 | })
31 |
32 | it('should handle cardinality and labels', function () {
33 | const str = 'classDiagram\n' +
34 | 'Class01 "1" *-- "many" Class02 : contains\n' +
35 | 'Class03 o-- Class04 : aggregation\n' +
36 | 'Class05 --> "1" Class06'
37 |
38 | parser.parse(str)
39 | })
40 | it('should handle class definitions', function () {
41 | const str = 'classDiagram\n' +
42 | 'class Car\n' +
43 | 'Driver -- Car : drives >\n' +
44 | 'Car *-- Wheel : have 4 >\n' +
45 | 'Car -- Person : < owns'
46 |
47 | parser.parse(str)
48 | })
49 |
50 | it('should handle method statements', function () {
51 | const str = 'classDiagram\n' +
52 | 'Object <|-- ArrayList\n' +
53 | 'Object : equals()\n' +
54 | 'ArrayList : Object[] elementData\n' +
55 | 'ArrayList : size()'
56 |
57 | parser.parse(str)
58 | })
59 | it('should handle parsing of method statements grouped by brackets', function () {
60 | const str = 'classDiagram\n' +
61 | 'class Dummy {\n' +
62 | 'String data\n' +
63 | ' void methods()\n' +
64 | '}\n' +
65 | '\n' +
66 | 'class Flight {\n' +
67 | ' flightNumber : Integer\n' +
68 | ' departureTime : Date\n' +
69 | '}'
70 |
71 | parser.parse(str)
72 | })
73 |
74 | it('should handle parsing of separators', function () {
75 | const str = 'classDiagram\n' +
76 | 'class Foo1 {\n' +
77 | ' You can use\n' +
78 | ' several lines\n' +
79 | '..\n' +
80 | 'as you want\n' +
81 | 'and group\n' +
82 | '==\n' +
83 | 'things together.\n' +
84 | '__\n' +
85 | 'You can have as many groups\n' +
86 | 'as you want\n' +
87 | '--\n' +
88 | 'End of class\n' +
89 | '}\n' +
90 | '\n' +
91 | 'class User {\n' +
92 | '.. Simple Getter ..\n' +
93 | '+ getName()\n' +
94 | '+ getAddress()\n' +
95 | '.. Some setter ..\n' +
96 | '+ setName()\n' +
97 | '__ private data __\n' +
98 | 'int age\n' +
99 | '-- encrypted --\n' +
100 | 'String password\n' +
101 | '}'
102 |
103 | parser.parse(str)
104 | })
105 | })
106 |
107 | describe('when fetching data from an classDiagram graph it', function () {
108 | beforeEach(function () {
109 | parser.yy = classDb
110 | parser.yy.clear()
111 | })
112 | it('should handle relation definitions EXTENSION', function () {
113 | const str = 'classDiagram\n' +
114 | 'Class01 <|-- Class02'
115 |
116 | parser.parse(str)
117 |
118 | const relations = parser.yy.getRelations()
119 |
120 | expect(parser.yy.getClass('Class01').id).toBe('Class01')
121 | expect(parser.yy.getClass('Class02').id).toBe('Class02')
122 | expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION)
123 | expect(relations[0].relation.type2).toBe('none')
124 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
125 | })
126 | it('should handle relation definitions AGGREGATION and dotted line', function () {
127 | const str = 'classDiagram\n' +
128 | 'Class01 o.. Class02'
129 |
130 | parser.parse(str)
131 |
132 | const relations = parser.yy.getRelations()
133 |
134 | expect(parser.yy.getClass('Class01').id).toBe('Class01')
135 | expect(parser.yy.getClass('Class02').id).toBe('Class02')
136 | expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION)
137 | expect(relations[0].relation.type2).toBe('none')
138 | expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE)
139 | })
140 | it('should handle relation definitions COMPOSITION on both sides', function () {
141 | const str = 'classDiagram\n' +
142 | 'Class01 *--* Class02'
143 |
144 | parser.parse(str)
145 |
146 | const relations = parser.yy.getRelations()
147 |
148 | expect(parser.yy.getClass('Class01').id).toBe('Class01')
149 | expect(parser.yy.getClass('Class02').id).toBe('Class02')
150 | expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION)
151 | expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION)
152 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
153 | })
154 | it('should handle relation definitions no types', function () {
155 | const str = 'classDiagram\n' +
156 | 'Class01 -- Class02'
157 |
158 | parser.parse(str)
159 |
160 | const relations = parser.yy.getRelations()
161 |
162 | expect(parser.yy.getClass('Class01').id).toBe('Class01')
163 | expect(parser.yy.getClass('Class02').id).toBe('Class02')
164 | expect(relations[0].relation.type1).toBe('none')
165 | expect(relations[0].relation.type2).toBe('none')
166 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
167 | })
168 | it('should handle relation definitions with type only on right side', function () {
169 | const str = 'classDiagram\n' +
170 | 'Class01 --|> Class02'
171 |
172 | parser.parse(str)
173 |
174 | const relations = parser.yy.getRelations()
175 |
176 | expect(parser.yy.getClass('Class01').id).toBe('Class01')
177 | expect(parser.yy.getClass('Class02').id).toBe('Class02')
178 | expect(relations[0].relation.type1).toBe('none')
179 | expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION)
180 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
181 | })
182 |
183 | it('should handle multiple classes and relation definitions', function () {
184 | const str = 'classDiagram\n' +
185 | 'Class01 <|-- Class02\n' +
186 | 'Class03 *-- Class04\n' +
187 | 'Class05 o-- Class06\n' +
188 | 'Class07 .. Class08\n' +
189 | 'Class09 -- Class10'
190 |
191 | parser.parse(str)
192 |
193 | const relations = parser.yy.getRelations()
194 |
195 | expect(parser.yy.getClass('Class01').id).toBe('Class01')
196 | expect(parser.yy.getClass('Class10').id).toBe('Class10')
197 |
198 | expect(relations.length).toBe(5)
199 |
200 | expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION)
201 | expect(relations[0].relation.type2).toBe('none')
202 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
203 | expect(relations[3].relation.type1).toBe('none')
204 | expect(relations[3].relation.type2).toBe('none')
205 | expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE)
206 | })
207 | })
208 | })
209 |
--------------------------------------------------------------------------------
/achievibit-local.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Run Achievibit Locally
3 | authors:
4 | - Neil Kalman
5 | layout: default
6 | id: achievibit-local
7 | permalink: /achievibit-local
8 | ---
9 |
10 | # Welcome!
11 |
12 | We're so glad you're thinking about contributing to a `kibibit` open source project! If you're unsure or afraid of anything, just ask or submit the issue or pull request anyways. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that.
13 |
14 | Before contributing, we encourage you to read our **CONTRIBUTING** policy **(you are here)**, our **[LICENSE](LICENSE)**, our **[CODE OF CONDUCT](CODE_OF_CONDUCT.md)**, and our **[README](README.MD)**, all of which should be in this repository.
15 |
16 | ## Easy starting point
17 | Our list of [Easy Picks](https://github.com/Kibibit/achievibit/labels/Easy%20Pick) include specific issues that are a good starting point for new contributors.
18 |
19 | These are **easy to pick up tasks**, either because they are small in scope, or just don't involve much in-depth knowledge of the project.
20 |
21 | Based on [Jordi Boggiano](https://github.com/Seldaek)'s [blog post](https://seld.be/notes/encouraging-contributions-with-the-easy-pick-label)
22 |
23 | ## General explanation
24 |
25 | to run achievibit (locally or otherwise), you need three main things:
26 |
27 | 1. **a database** - achievibit expects to write and read data from a mongodb database.
28 | You can either create a real mongoDB, or use `monkey.js` to create a mock DB to run locally
29 | 2. **achievibit's server** - pretty self explanatory
30 | 3. **a static public ip address** - in order to connect achievibit to a github repository, you need to have a **url** you can paste into your webhook. On production, this is handled by our heroku hosting. To generate a static url, we use `ngrok` locally. This is done automatically when you run the server with the `--ngrok` command line param.
31 |
32 | ## Prerequisites
33 |
34 | ### Tools to install
35 |
36 | This project is run on `node.js`. install node.js using [their site](https://nodejs.org/en/).
37 | Besides that, nothing is required.
38 |
39 | ### Setting up a database
40 |
41 | There are 3 options for running achievibit with a database:
42 | - [run mongoDB locally](#run-mongodb-locally)
43 | - [mongoDB cloud service](#mongodb-cloud-service)
44 | - [mock mongoDB](#mock-mongodb)
45 |
46 | You can follow whichever you want, but the ocally run mongoDB is recommended since it's pretty easy to set-up and it tests your code E2E instead of using a mock DB
47 |
48 | #### Run MongoDB Locally
49 |
50 | The easiest way to do that, is to use **docker**.
51 |
52 | 1. [Install docker](https://docs.docker.com/install/)
53 | 2. Check that docker is installed by running `docker --version` in your command line
54 | 3. Create a folder on your machine in order to use that folder for the DB: `mkdir ~/data`
55 | 4. MongoDB conveniently provides us with an [official container](https://registry.hub.docker.com/_/mongo/):
56 | ```shell
57 | sudo docker run -d -p 27017:27017 -v ~/data:/data/db mongo
58 | ```
59 | 5. At this point, you should have a MongoDB instance listening on port 27017. Its data is stored in ~/data directory of the docker host.
60 | 6. your mongoDB uri is `localhost/achievibit`
61 | 7. If you want a visual UI to see the data in your mongoDB, your can install mongoUI (`npm i -g mongoui`) and run it (`mongoui`)
62 |
63 | ## Getting Started
64 |
65 | 1. clone achievibit locally (we prefer using [ungit](https://github.com/FredrikNoren/ungit) or [gitkraken](https://www.gitkraken.com/))
66 | 2. run `npm install` to install all dependencies
67 | 3. Now we need to run achievibit. achievibit looks for variables in the following order:
68 | 1. Command-line arguments
69 | 2. Environment variables
70 | 3. A file located at the root of the project called `privateConfig.json`
71 | You can run the command line with the `--savePrivate` argument, which will save the `privateConfig.json` file locally for you with the params you passed.
72 | Run the following command, replacing all the urls we got in the **prerequisites** step:
73 | ```shell
74 | node index.js --databaseUrl "" --ngrok
75 | ```
76 | If you chose to [run mongoDB locally](#run-mongodb-locally), use the following command:
77 | ```shell
78 | node index.js --databaseUrl locahost/achievibit --ngrok
79 | ```
80 | If you chose to run locally without a database, use the following command:
81 | ```shell
82 | node index.js --testDB --ngrok
83 | ```
84 | > To set global variables: In Unixy environments: `export ngrokToken=""` || In Windows **powershell**: `$env:ngrokToken=""`
85 |
86 | 4. if everything worked, you should see the `achievibit` logo, and an **ngrok url** under it.
87 | 5. follow the instructions in the [`README.MD`](/README.MD) file to connect a test repository to your local achievibit (replace the url with your ngrok url). Make sure **Content type** is set to **application\json**
88 | > 
89 |
90 | 6. check that the achievement works on your database (your user got your achievement on your test repository).
91 | if your achievement requires interaction with another user, talk to one of the developers and we'll help test it (reviewing a test pull request, etc.).
92 |
93 | ## Seeing logs
94 | Other then seeing the **logs** in your `console`, you can access your logs from anywhere on `/logs`.
95 | See [Supported Params](#supported-params) for more info
96 |
97 | ## Supported params
98 | `achievibit` supports the following params:
99 | - `--databaseUrl` **OR** `--testDB` (required) - either set-up achievibit against a real DB or a mock DB
100 | - `--ngrokToken` (required) - needed in order to connect to GitHub for testing
101 | - `--logsUsername` - set a username to the logs
102 | - `--logsPassword` - set a password to the logs (without the username, this is ignored)
103 | - `--stealth` - if you don't want to print the logo in the terminal. Useful when running in `watch` mode and you don't want the server to print the achievibit logo each time it restarts
104 | - `--githubUser` - `GitHub` user to make requests with
105 | - `--githubPassword` - `GitHub` password to make requests with
106 |
107 | ## Running tests (WIP)
108 |
109 | Currently, only unit-tests are implemented. To run them locally, run `npm test`
110 |
111 |
112 | ## DataBase Appendix
113 |
114 | #### Run MongoDB Locally
115 |
116 | The easiest way to do that, is to use **docker**.
117 |
118 | 1. [Install docker](https://docs.docker.com/install/)
119 | 2. Check that docker is installed by running `docker --version` in your command line
120 | 3. Create a folder on your machine in order to use that folder for the DB: `mkdir ~/data`
121 | 4. MongoDB conveniently provides us with an [official container](https://registry.hub.docker.com/_/mongo/):
122 | ```shell
123 | sudo docker run -d -p 27017:27017 -v ~/data:/data/db mongo
124 | ```
125 | 5. At this point, you should have a MongoDB instance listening on port 27017. Its data is stored in ~/data directory of the docker host.
126 | 6. your mongoDB uri is `localhost/achievibit`
127 | 7. If you want a visual UI to see the data in your mongoDB, your can install mongoUI (`npm i -g mongoui`) and run it (`mongoui`)
128 |
129 | #### MongoDB cloud service
130 |
131 | You can use https://mlab.com/ to create one in the cloud.
132 | If you're using `mlab`, you need to create a database, and then create a database user.
133 | After creating a database, click on Users **-->** Add database user
134 |
135 | 
136 |
137 | Now, go to your database homepage, and copy the database url. you need to enter your database username and password (**NOT YOUR MLAB ACCOUNT!**)
138 |
139 | 
140 |
141 | #### Mock mongoDB
142 |
143 | instead of running against a real DB, you can use our tests' mock DB with the cli param `--testDB`. This is less recommended since it means you don't test everything E2E. But it's the quickest method
144 |
--------------------------------------------------------------------------------
/assets/diagrams/gantt/ganttDb.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import moment from 'moment'
3 | import ganttDb from './ganttDb'
4 |
5 | describe('when using the ganttDb', function () {
6 | beforeEach(function () {
7 | ganttDb.clear()
8 | })
9 |
10 | it('should handle an fixed dates', function () {
11 | ganttDb.setDateFormat('YYYY-MM-DD')
12 | ganttDb.addSection('testa1')
13 | ganttDb.addTask('test1', 'id1,2013-01-01,2013-01-12')
14 | const tasks = ganttDb.getTasks()
15 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
16 | expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate())
17 | expect(tasks[0].id).toEqual('id1')
18 | expect(tasks[0].task).toEqual('test1')
19 | })
20 | it('should handle duration (days) instead of fixed date to determine end date', function () {
21 | ganttDb.setDateFormat('YYYY-MM-DD')
22 | ganttDb.addSection('testa1')
23 | ganttDb.addTask('test1', 'id1,2013-01-01,2d')
24 | const tasks = ganttDb.getTasks()
25 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
26 | expect(tasks[0].endTime).toEqual(moment('2013-01-03', 'YYYY-MM-DD').toDate())
27 | expect(tasks[0].id).toEqual('id1')
28 | expect(tasks[0].task).toEqual('test1')
29 | })
30 | it('should handle duration (hours) instead of fixed date to determine end date', function () {
31 | ganttDb.setDateFormat('YYYY-MM-DD')
32 | ganttDb.addSection('testa1')
33 | ganttDb.addTask('test1', 'id1,2013-01-01,2h')
34 | const tasks = ganttDb.getTasks()
35 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
36 | expect(tasks[0].endTime).toEqual(moment('2013-01-01 2:00', 'YYYY-MM-DD hh:mm').toDate())
37 | expect(tasks[0].id).toEqual('id1')
38 | expect(tasks[0].task).toEqual('test1')
39 | })
40 | it('should handle duration (minutes) instead of fixed date to determine end date', function () {
41 | ganttDb.setDateFormat('YYYY-MM-DD')
42 | ganttDb.addSection('testa1')
43 | ganttDb.addTask('test1', 'id1,2013-01-01,2m')
44 | const tasks = ganttDb.getTasks()
45 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
46 | expect(tasks[0].endTime).toEqual(moment('2013-01-01 00:02', 'YYYY-MM-DD hh:mm').toDate())
47 | expect(tasks[0].id).toEqual('id1')
48 | expect(tasks[0].task).toEqual('test1')
49 | })
50 | it('should handle duration (seconds) instead of fixed date to determine end date', function () {
51 | ganttDb.setDateFormat('YYYY-MM-DD')
52 | ganttDb.addSection('testa1')
53 | ganttDb.addTask('test1', 'id1,2013-01-01,2s')
54 | const tasks = ganttDb.getTasks()
55 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
56 | expect(tasks[0].endTime).toEqual(moment('2013-01-01 00:00:02', 'YYYY-MM-DD hh:mm:ss').toDate())
57 | expect(tasks[0].id).toEqual('id1')
58 | expect(tasks[0].task).toEqual('test1')
59 | })
60 | it('should handle duration (weeks) instead of fixed date to determine end date', function () {
61 | ganttDb.setDateFormat('YYYY-MM-DD')
62 | ganttDb.addSection('testa1')
63 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
64 | const tasks = ganttDb.getTasks()
65 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
66 | expect(tasks[0].endTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
67 | expect(tasks[0].id).toEqual('id1')
68 | expect(tasks[0].task).toEqual('test1')
69 | })
70 |
71 | it('should handle relative start date based on id', function () {
72 | ganttDb.setDateFormat('YYYY-MM-DD')
73 | ganttDb.addSection('testa1')
74 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
75 | ganttDb.addTask('test2', 'id2,after id1,1d')
76 |
77 | const tasks = ganttDb.getTasks()
78 |
79 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
80 | expect(tasks[1].id).toEqual('id2')
81 | expect(tasks[1].task).toEqual('test2')
82 | })
83 |
84 | it('should handle relative start date based on id when id is invalid', function () {
85 | ganttDb.setDateFormat('YYYY-MM-DD')
86 | ganttDb.addSection('testa1')
87 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
88 | ganttDb.addTask('test2', 'id2,after id3,1d')
89 | const tasks = ganttDb.getTasks()
90 | expect(tasks[1].startTime).toEqual(new Date((new Date()).setHours(0, 0, 0, 0)))
91 | expect(tasks[1].id).toEqual('id2')
92 | expect(tasks[1].task).toEqual('test2')
93 | })
94 |
95 | it('should handle fixed dates without id', function () {
96 | ganttDb.setDateFormat('YYYY-MM-DD')
97 | ganttDb.addSection('testa1')
98 | ganttDb.addTask('test1', '2013-01-01,2013-01-12')
99 | const tasks = ganttDb.getTasks()
100 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
101 | expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate())
102 | expect(tasks[0].id).toEqual('task1')
103 | expect(tasks[0].task).toEqual('test1')
104 | })
105 |
106 | it('should handle duration instead of a fixed date to determine end date without id', function () {
107 | ganttDb.setDateFormat('YYYY-MM-DD')
108 | ganttDb.addSection('testa1')
109 | ganttDb.addTask('test1', '2013-01-01,4d')
110 | const tasks = ganttDb.getTasks()
111 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
112 | expect(tasks[0].endTime).toEqual(moment('2013-01-05', 'YYYY-MM-DD').toDate())
113 | expect(tasks[0].id).toEqual('task1')
114 | expect(tasks[0].task).toEqual('test1')
115 | })
116 |
117 | it('should handle relative start date of a fixed date to determine end date without id', function () {
118 | ganttDb.setDateFormat('YYYY-MM-DD')
119 | ganttDb.addSection('testa1')
120 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
121 | ganttDb.addTask('test2', 'after id1,1d')
122 |
123 | const tasks = ganttDb.getTasks()
124 |
125 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
126 | expect(tasks[1].id).toEqual('task1')
127 | expect(tasks[1].task).toEqual('test2')
128 | })
129 | it('should handle a new task with only an end date as definition', function () {
130 | ganttDb.setDateFormat('YYYY-MM-DD')
131 | ganttDb.addSection('testa1')
132 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
133 | ganttDb.addTask('test2', '2013-01-26')
134 |
135 | const tasks = ganttDb.getTasks()
136 |
137 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
138 | expect(tasks[1].endTime).toEqual(moment('2013-01-26', 'YYYY-MM-DD').toDate())
139 | expect(tasks[1].id).toEqual('task1')
140 | expect(tasks[1].task).toEqual('test2')
141 | })
142 | it('should handle a new task with only an end date as definition', function () {
143 | ganttDb.setDateFormat('YYYY-MM-DD')
144 | ganttDb.addSection('testa1')
145 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
146 | ganttDb.addTask('test2', '2d')
147 |
148 | const tasks = ganttDb.getTasks()
149 |
150 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
151 | expect(tasks[1].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate())
152 | expect(tasks[1].id).toEqual('task1')
153 | expect(tasks[1].task).toEqual('test2')
154 | })
155 | it('should handle relative start date based on id regardless of sections', function () {
156 | ganttDb.setDateFormat('YYYY-MM-DD')
157 | ganttDb.addSection('testa1')
158 | ganttDb.addTask('test1', 'id1,2013-01-01,2w')
159 | ganttDb.addTask('test2', 'id2,after id3,1d')
160 | ganttDb.addSection('testa2')
161 | ganttDb.addTask('test3', 'id3,after id1,2d')
162 |
163 | const tasks = ganttDb.getTasks()
164 |
165 | expect(tasks[1].startTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate())
166 | expect(tasks[1].endTime).toEqual(moment('2013-01-18', 'YYYY-MM-DD').toDate())
167 | expect(tasks[1].id).toEqual('id2')
168 | expect(tasks[1].task).toEqual('test2')
169 |
170 | expect(tasks[2].id).toEqual('id3')
171 | expect(tasks[2].task).toEqual('test3')
172 | expect(tasks[2].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
173 | expect(tasks[2].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate())
174 | })
175 | })
176 |
--------------------------------------------------------------------------------
/assets/diagrams/git/gitGraphParser.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import gitGraphAst from './gitGraphAst'
3 | import { parser } from './parser/gitGraph'
4 |
5 | describe('when parsing a gitGraph', function () {
6 | beforeEach(function () {
7 | parser.yy = gitGraphAst
8 | parser.yy.clear()
9 | })
10 | it('should handle a gitGraph defintion', function () {
11 | const str = 'gitGraph:\n' +
12 | 'commit\n'
13 |
14 | parser.parse(str)
15 | const commits = parser.yy.getCommits()
16 |
17 | expect(Object.keys(commits).length).toBe(1)
18 | expect(parser.yy.getCurrentBranch()).toBe('master')
19 | expect(parser.yy.getDirection()).toBe('LR')
20 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
21 | })
22 |
23 | it('should handle a gitGraph defintion with empty options', function () {
24 | const str = 'gitGraph:\n' +
25 | 'options\n' +
26 | 'end\n' +
27 | 'commit\n'
28 |
29 | parser.parse(str)
30 | const commits = parser.yy.getCommits()
31 |
32 | expect(parser.yy.getOptions()).toEqual({})
33 | expect(Object.keys(commits).length).toBe(1)
34 | expect(parser.yy.getCurrentBranch()).toBe('master')
35 | expect(parser.yy.getDirection()).toBe('LR')
36 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
37 | })
38 |
39 | it('should handle a gitGraph defintion with valid options', function () {
40 | const str = 'gitGraph:\n' +
41 | 'options\n' +
42 | '{"key": "value"}\n' +
43 | 'end\n' +
44 | 'commit\n'
45 |
46 | parser.parse(str)
47 | const commits = parser.yy.getCommits()
48 | expect(parser.yy.getOptions()['key']).toBe('value')
49 | expect(Object.keys(commits).length).toBe(1)
50 | expect(parser.yy.getCurrentBranch()).toBe('master')
51 | expect(parser.yy.getDirection()).toBe('LR')
52 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
53 | })
54 |
55 | it('should not fail on a gitGraph with malformed json', function () {
56 | const str = 'gitGraph:\n' +
57 | 'options\n' +
58 | '{"key": "value"\n' +
59 | 'end\n' +
60 | 'commit\n'
61 |
62 | parser.parse(str)
63 | const commits = parser.yy.getCommits()
64 | expect(Object.keys(commits).length).toBe(1)
65 | expect(parser.yy.getCurrentBranch()).toBe('master')
66 | expect(parser.yy.getDirection()).toBe('LR')
67 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
68 | })
69 |
70 | it('should handle set direction', function () {
71 | const str = 'gitGraph BT:\n' +
72 | 'commit\n'
73 |
74 | parser.parse(str)
75 | const commits = parser.yy.getCommits()
76 |
77 | expect(Object.keys(commits).length).toBe(1)
78 | expect(parser.yy.getCurrentBranch()).toBe('master')
79 | expect(parser.yy.getDirection()).toBe('BT')
80 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
81 | })
82 |
83 | it('should checkout a branch', function () {
84 | const str = 'gitGraph:\n' +
85 | 'branch new\n' +
86 | 'checkout new\n'
87 |
88 | parser.parse(str)
89 | const commits = parser.yy.getCommits()
90 |
91 | expect(Object.keys(commits).length).toBe(0)
92 | expect(parser.yy.getCurrentBranch()).toBe('new')
93 | })
94 |
95 | it('should add commits to checked out branch', function () {
96 | const str = 'gitGraph:\n' +
97 | 'branch new\n' +
98 | 'checkout new\n' +
99 | 'commit\n' +
100 | 'commit\n'
101 |
102 | parser.parse(str)
103 | const commits = parser.yy.getCommits()
104 |
105 | expect(Object.keys(commits).length).toBe(2)
106 | expect(parser.yy.getCurrentBranch()).toBe('new')
107 | const branchCommit = parser.yy.getBranches()['new']
108 | expect(branchCommit).not.toBeNull()
109 | expect(commits[branchCommit].parent).not.toBeNull()
110 | })
111 | it('should handle commit with args', function () {
112 | const str = 'gitGraph:\n' +
113 | 'commit "a commit"\n'
114 |
115 | parser.parse(str)
116 | const commits = parser.yy.getCommits()
117 |
118 | expect(Object.keys(commits).length).toBe(1)
119 | const key = Object.keys(commits)[0]
120 | expect(commits[key].message).toBe('a commit')
121 | expect(parser.yy.getCurrentBranch()).toBe('master')
122 | })
123 |
124 | it('it should reset a branch', function () {
125 | const str = 'gitGraph:\n' +
126 | 'commit\n' +
127 | 'commit\n' +
128 | 'branch newbranch\n' +
129 | 'checkout newbranch\n' +
130 | 'commit\n' +
131 | 'reset master\n'
132 |
133 | parser.parse(str)
134 |
135 | const commits = parser.yy.getCommits()
136 | expect(Object.keys(commits).length).toBe(3)
137 | expect(parser.yy.getCurrentBranch()).toBe('newbranch')
138 | expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master'])
139 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch'])
140 | })
141 |
142 | it('reset can take an argument', function () {
143 | const str = 'gitGraph:\n' +
144 | 'commit\n' +
145 | 'commit\n' +
146 | 'branch newbranch\n' +
147 | 'checkout newbranch\n' +
148 | 'commit\n' +
149 | 'reset master^\n'
150 |
151 | parser.parse(str)
152 |
153 | const commits = parser.yy.getCommits()
154 | expect(Object.keys(commits).length).toBe(3)
155 | expect(parser.yy.getCurrentBranch()).toBe('newbranch')
156 | const master = commits[parser.yy.getBranches()['master']]
157 | expect(parser.yy.getHead().id).toEqual(master.parent)
158 | })
159 |
160 | it('it should handle fast forwardable merges', function () {
161 | const str = 'gitGraph:\n' +
162 | 'commit\n' +
163 | 'branch newbranch\n' +
164 | 'checkout newbranch\n' +
165 | 'commit\n' +
166 | 'commit\n' +
167 | 'checkout master\n' +
168 | 'merge newbranch\n'
169 |
170 | parser.parse(str)
171 |
172 | const commits = parser.yy.getCommits()
173 | expect(Object.keys(commits).length).toBe(3)
174 | expect(parser.yy.getCurrentBranch()).toBe('master')
175 | expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master'])
176 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch'])
177 | })
178 |
179 | it('it should handle cases when merge is a noop', function () {
180 | const str = 'gitGraph:\n' +
181 | 'commit\n' +
182 | 'branch newbranch\n' +
183 | 'checkout newbranch\n' +
184 | 'commit\n' +
185 | 'commit\n' +
186 | 'merge master\n'
187 |
188 | parser.parse(str)
189 |
190 | const commits = parser.yy.getCommits()
191 | expect(Object.keys(commits).length).toBe(3)
192 | expect(parser.yy.getCurrentBranch()).toBe('newbranch')
193 | expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master'])
194 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch'])
195 | })
196 |
197 | it('it should handle merge with 2 parents', function () {
198 | const str = 'gitGraph:\n' +
199 | 'commit\n' +
200 | 'branch newbranch\n' +
201 | 'checkout newbranch\n' +
202 | 'commit\n' +
203 | 'commit\n' +
204 | 'checkout master\n' +
205 | 'commit\n' +
206 | 'merge newbranch\n'
207 |
208 | parser.parse(str)
209 |
210 | const commits = parser.yy.getCommits()
211 | expect(Object.keys(commits).length).toBe(5)
212 | expect(parser.yy.getCurrentBranch()).toBe('master')
213 | expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master'])
214 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master'])
215 | })
216 |
217 | it('it should handle ff merge when history walk has two parents (merge commit)', function () {
218 | const str = 'gitGraph:\n' +
219 | 'commit\n' +
220 | 'branch newbranch\n' +
221 | 'checkout newbranch\n' +
222 | 'commit\n' +
223 | 'commit\n' +
224 | 'checkout master\n' +
225 | 'commit\n' +
226 | 'merge newbranch\n' +
227 | 'commit\n' +
228 | 'checkout newbranch\n' +
229 | 'merge master\n'
230 |
231 | parser.parse(str)
232 |
233 | const commits = parser.yy.getCommits()
234 | expect(Object.keys(commits).length).toBe(6)
235 | expect(parser.yy.getCurrentBranch()).toBe('newbranch')
236 | expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master'])
237 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master'])
238 |
239 | parser.yy.prettyPrint()
240 | })
241 | })
242 |
--------------------------------------------------------------------------------
/assets/mermaid.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import mermaid from './mermaid'
3 | import flowDb from './diagrams/flowchart/flowDb'
4 | import flowParser from './diagrams/flowchart/parser/flow'
5 | import flowRenderer from './diagrams/flowchart/flowRenderer'
6 |
7 | describe('when using mermaid and ', function () {
8 | describe('when detecting chart type ', function () {
9 | it('should not start rendering with mermaid.startOnLoad set to false', function () {
10 | mermaid.startOnLoad = false
11 | document.body.innerHTML = 'graph TD;\na;
'
12 | spyOn(mermaid, 'init')
13 | mermaid.contentLoaded()
14 | expect(mermaid.init).not.toHaveBeenCalled()
15 | })
16 |
17 | it('should start rendering with both startOnLoad set', function () {
18 | mermaid.startOnLoad = true
19 | document.body.innerHTML = 'graph TD;\na;
'
20 | spyOn(mermaid, 'init')
21 | mermaid.contentLoaded()
22 | expect(mermaid.init).toHaveBeenCalled()
23 | })
24 |
25 | it('should start rendering with mermaid.startOnLoad', function () {
26 | mermaid.startOnLoad = true
27 | document.body.innerHTML = 'graph TD;\na;
'
28 | spyOn(mermaid, 'init')
29 | mermaid.contentLoaded()
30 | expect(mermaid.init).toHaveBeenCalled()
31 | })
32 |
33 | it('should start rendering as a default with no changes performed', function () {
34 | document.body.innerHTML = 'graph TD;\na;
'
35 | spyOn(mermaid, 'init')
36 | mermaid.contentLoaded()
37 | expect(mermaid.init).toHaveBeenCalled()
38 | })
39 | })
40 |
41 | describe('when calling addEdges ', function () {
42 | beforeEach(function () {
43 | flowParser.parser.yy = flowDb
44 | flowDb.clear()
45 | })
46 | it('it should handle edges with text', function () {
47 | flowParser.parser.parse('graph TD;A-->|text ex|B;')
48 | flowParser.parser.yy.getVertices()
49 | const edges = flowParser.parser.yy.getEdges()
50 |
51 | const mockG = {
52 | setEdge: function (start, end, options) {
53 | expect(start).toBe('A')
54 | expect(end).toBe('B')
55 | expect(options.arrowhead).toBe('normal')
56 | expect(options.label.match('text ex')).toBeTruthy()
57 | }
58 | }
59 |
60 | flowRenderer.addEdges(edges, mockG)
61 | })
62 |
63 | it('should handle edges without text', function () {
64 | flowParser.parser.parse('graph TD;A-->B;')
65 | flowParser.parser.yy.getVertices()
66 | const edges = flowParser.parser.yy.getEdges()
67 |
68 | const mockG = {
69 | setEdge: function (start, end, options) {
70 | expect(start).toBe('A')
71 | expect(end).toBe('B')
72 | expect(options.arrowhead).toBe('normal')
73 | }
74 | }
75 |
76 | flowRenderer.addEdges(edges, mockG)
77 | })
78 |
79 | it('should handle open-ended edges', function () {
80 | flowParser.parser.parse('graph TD;A---B;')
81 | flowParser.parser.yy.getVertices()
82 | const edges = flowParser.parser.yy.getEdges()
83 |
84 | const mockG = {
85 | setEdge: function (start, end, options) {
86 | expect(start).toBe('A')
87 | expect(end).toBe('B')
88 | expect(options.arrowhead).toBe('none')
89 | }
90 | }
91 |
92 | flowRenderer.addEdges(edges, mockG)
93 | })
94 |
95 | it('should handle edges with styles defined', function () {
96 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;')
97 | flowParser.parser.yy.getVertices()
98 | const edges = flowParser.parser.yy.getEdges()
99 |
100 | const mockG = {
101 | setEdge: function (start, end, options) {
102 | expect(start).toBe('A')
103 | expect(end).toBe('B')
104 | expect(options.arrowhead).toBe('none')
105 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;')
106 | }
107 | }
108 |
109 | flowRenderer.addEdges(edges, mockG)
110 | })
111 | it('should handle edges with interpolation defined', function () {
112 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 interpolate basis')
113 | flowParser.parser.yy.getVertices()
114 | const edges = flowParser.parser.yy.getEdges()
115 |
116 | const mockG = {
117 | setEdge: function (start, end, options) {
118 | expect(start).toBe('A')
119 | expect(end).toBe('B')
120 | expect(options.arrowhead).toBe('none')
121 | expect(options.curve).toBe('basis') // mocked as string
122 | }
123 | }
124 |
125 | flowRenderer.addEdges(edges, mockG)
126 | })
127 | it('should handle edges with text and styles defined', function () {
128 | flowParser.parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;')
129 | flowParser.parser.yy.getVertices()
130 | const edges = flowParser.parser.yy.getEdges()
131 |
132 | const mockG = {
133 | setEdge: function (start, end, options) {
134 | expect(start).toBe('A')
135 | expect(end).toBe('B')
136 | expect(options.arrowhead).toBe('none')
137 | expect(options.label.match('the text')).toBeTruthy()
138 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;')
139 | }
140 | }
141 |
142 | flowRenderer.addEdges(edges, mockG)
143 | })
144 |
145 | it('should set fill to "none" by default when handling edges', function () {
146 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;')
147 | flowParser.parser.yy.getVertices()
148 | const edges = flowParser.parser.yy.getEdges()
149 |
150 | const mockG = {
151 | setEdge: function (start, end, options) {
152 | expect(start).toBe('A')
153 | expect(end).toBe('B')
154 | expect(options.arrowhead).toBe('none')
155 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;')
156 | }
157 | }
158 |
159 | flowRenderer.addEdges(edges, mockG)
160 | })
161 |
162 | it('should not set fill to none if fill is set in linkStyle', function () {
163 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;')
164 | flowParser.parser.yy.getVertices()
165 | const edges = flowParser.parser.yy.getEdges()
166 | const mockG = {
167 | setEdge: function (start, end, options) {
168 | expect(start).toBe('A')
169 | expect(end).toBe('B')
170 | expect(options.arrowhead).toBe('none')
171 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;')
172 | }
173 | }
174 |
175 | flowRenderer.addEdges(edges, mockG)
176 | })
177 | })
178 |
179 | describe('checking validity of input ', function () {
180 | it('it should throw for an invalid definiton', function () {
181 | expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow()
182 | })
183 |
184 | it('it should not throw for a valid flow definition', function () {
185 | expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow()
186 | })
187 | it('it should throw for an invalid flow definition', function () {
188 | expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow()
189 | })
190 |
191 | it('it should not throw for a valid sequenceDiagram definition', function () {
192 | const text = 'sequenceDiagram\n' +
193 | 'Alice->Bob: Hello Bob, how are you?\n\n' +
194 | '%% Comment\n' +
195 | 'Note right of Bob: Bob thinks\n' +
196 | 'alt isWell\n\n' +
197 | 'Bob-->Alice: I am good thanks!\n' +
198 | 'else isSick\n' +
199 | 'Bob-->Alice: Feel sick...\n' +
200 | 'end'
201 | expect(() => mermaid.parse(text)).not.toThrow()
202 | })
203 |
204 | it('it should throw for an invalid sequenceDiagram definition', function () {
205 | const text = 'sequenceDiagram\n' +
206 | 'Alice:->Bob: Hello Bob, how are you?\n\n' +
207 | '%% Comment\n' +
208 | 'Note right of Bob: Bob thinks\n' +
209 | 'alt isWell\n\n' +
210 | 'Bob-->Alice: I am good thanks!\n' +
211 | 'else isSick\n' +
212 | 'Bob-->Alice: Feel sick...\n' +
213 | 'end'
214 | expect(() => mermaid.parse(text)).toThrow()
215 | })
216 | })
217 | })
218 |
--------------------------------------------------------------------------------
/kibibit-bit.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "@kibibit/bit documentation"
3 | authors:
4 | - Neil Kalman
5 | layout: default
6 | id: kibibit-bit
7 | permalink: /kibibit-bit
8 | ---
9 |
10 | # @kibibit/bit
11 |
12 | @kibibit/bit is a **git-flow + GitHub** replacement for **git and git flow**
13 |
14 | git flow is great but it has much more missing to work against github considering
15 | it's a method for projects with "scheduled release cycle"
16 |
17 | Some missing things:
18 | - Work with pull requests on GitHub (for now. later we'll support more cloud gits)
19 | - Finishing a feature should open a PR instead of merging it locally to master
20 | - Initialization of a project (adding the necassery branches)
21 | - Creating a github project when `git init` a project
22 | - Integrate github with a set of corisponding tools for release and stuff
23 | - Support for convensional commit messages
24 | - Support for generating an automatic changlog
25 |
26 | **As a general rule**: This should make creating kibibit projects (and
27 | hopefully other people's projects) easy to create with all hooks and stuff
28 | as
29 | ```bash
30 | kibibit init
31 | # or bit init
32 | ```
33 |
34 | ## Existing solutions and external documentation
35 |
36 | - [gitflow](https://github.com/nvie/gitflow) (original gitflow extension)
37 | - [Introducing GitFlow](https://datasift.github.io/gitflow/IntroducingGitFlow.html)
38 | - [HubFlow: Tool for gitflow and GitHub](https://datasift.github.io/gitflow/) (not maintained anymore)
39 | - [gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)
40 | - [gitflow cheatsheet](https://danielkummer.github.io/git-flow-cheatsheet/)
41 |
42 | ## Some lessons learned from `tdd1t`
43 |
44 | Basically, two different flows can be used:
45 |
46 | - rebase every pull request into master (keeping all commits intact)
47 | - merge and squash the commits into a single "PR" commit for each PR
48 |
49 | With `tdd1t`, we used the first option, which looked like it was recieved
50 | poorly becuase the team needed to maintain **conventional commit messages
51 | for all commits in a branch**. I think it's better to go with the second option,
52 | which will force keeping only pull request title's and descriptions to conventions.
53 |
54 | This might be a bit harder since it will require a github integration with
55 | pull requests' webhooks in order to keep PR title + body to conventions instead of a githook.
56 |
57 | This can be done with a [probot bot installation](https://probot.github.io/apps/semantic-pull-requests/),
58 | we just need to check that it supports configuration and if not, add it.
59 |
60 | ## Project "Gotch'a"s
61 |
62 | Some things we need to consider while writing this:
63 |
64 | - Should be easily mainainable, preferrably with tests and such
65 | - Should work as a command line tool, and as an npm library
66 | > We need to write functions that will corespond to specific cli commands
67 | > that can take the flow and work with it **without the extra command line question steps**.
68 | - Should save sensitive data in appropriate locations (like keychain)
69 | - Should be as easy to integrate with as possible
70 | > We should support customization for advanced users, but also lots of defaults or user prompts
71 | > for first time users.
72 |
73 | ## Supported Commands
74 |
75 | ### Initialization `kibibit init`
76 |
77 | kibibit\bit should have an `init` command which will:
78 |
79 | - create a git repo and support command line params used by `git init`
80 | - should make sure this repo has a GitHub remote, and if not, ask to create it
81 | - Should configure a specific login account for this repo and github.
82 | > Basically, with `git`, you can have a user per project
83 | - should ensure that the two `gitflow` branches exist (master & develop by default)
84 | - should install a `.kibibit` file in the root of the repo with specific configurations
85 | > all configuration should be split between two places: the `.git` folder, and `.kibibit`.
86 | > `.kibibit` will basically hold configuration we want to make **consistant** since
87 | > `.git` folder is not being commited.
88 | > for example: the user to use should **not be consistant** becuase it can change locally,
89 | > but which branch is "production" and which one is "develop" should be kept in `.kibibit`,
90 | > and updated inside the `.git` folder on initial use.
91 |
92 | Currently, we'll support `node` applications specifically. So, these `node` integrations
93 | can also be initialized here:
94 |
95 | - create a `package.json` with all the attributes we know (remote repo, name, etc.)
96 | - install [husky](https://github.com/typicode/husky) for git hooks
97 | - install `kibibit`'s git hooks (need to understand which one's we need :-))
98 |
99 | ### Clone `kibibit clone`
100 |
101 | Should clone a git repository and also install all kibibit/bit tools (like hooks)
102 | and stuff. Also, update some `.git` config params based on the `.kibibit` file.
103 |
104 | A nice bonus feature might be to support forks. If a repo is a fork of another repo,
105 | Add the fork as `origin` remote, and the original repo as the `upstream` remote.
106 |
107 | Also, if you clone a repo of another user, the cli might suggest the user to fork
108 | it first.
109 |
110 | see [this as a reference](https://help.github.com/en/articles/fork-a-repo)
111 |
112 | ### Commit `kibibit commit`
113 |
114 | Should allow commits on all bracnehs besides `master` and `develop`.
115 | Will support command line params OR opening an editor to create a commit message.
116 |
117 | ### Status `kibibit status`
118 |
119 | Pretty much the same as `git status`. should show files in staging area and
120 | unstaged files, and maybe some info about which feature you're working on
121 | (connected github issue & PR status \ PR if exists)
122 |
123 | ### Feature `kibibit feature`
124 |
125 | start or continue a feature (will be prompted). If no featureName is given, returns all ongoing features
126 |
127 | ### Hotfix `kibibit hotfix`
128 |
129 | start or continue a hotfix (will be prompted). If no hotfixName is given, returns all ongoing hotfixes
130 |
131 | ### Finish `kibibit finish`
132 |
133 | use GitHub to issue a pull request to origin/develop.
134 |
135 | ### Update `kibibit update`
136 |
137 | keep up-to-date with completed features on GitHub.
138 |
139 | Should this happen automatically? what about conflicts?
140 |
141 | ### Save `kibibit push` OR `kibibit save`
142 |
143 | (the oposite of update)
144 |
145 | push your current feature branch back to GitHub as you make progress and want to save your work
146 |
147 | ### Checkout shared branches `kibibit master` AND `kibibit develop`
148 |
149 | checkout master or develop branches. should use the configured branch names
150 | from `kibibit init` which should be located in `.kibibit` and `.git`.
151 |
152 | These branches should not allow direct commits (maybe later we'll allow it for admins based on github).
153 |
154 | ### Open-Source `kibibit opensource`
155 |
156 | Check alignment with GitHub's [Community Profile](https://github.com/Kibibit/tdd1t/community)
157 | and help with generating some of those files
158 |
159 | Might be a bad name, we can change this to something else if we decide to include
160 | some more GitHub settings changes here.
161 |
162 | ## Some Technical recommendations
163 |
164 | Every function should return a promise so we can concatinate things like:
165 |
166 | ```javascript
167 | kibibit.init('project name', 'github.user@gmail.com', /* createPackageFile */ true)
168 | .then(() => kibibit.feature('my-feature'))
169 | .then(() => kibibit.add('new-file.ts'))
170 | .then(() => kibibit.commit())
171 | ```
172 |
173 | The only exception to this IMO is creating a new kibibit client:
174 | ```javascript
175 | const kibibit = new Kibibit('');
176 | ```
177 | Which will create a new kibibit object were you can use commands for the given path.
178 |
179 | `kibibit.` should allow passing variables similar to the command line.
180 |
181 | Using promises will allow use to use the cli like so:
182 |
183 | ```javascript
184 | function CLIFunctionInit(...params) {
185 | return kibibit.init(...params)
186 | .catch((err) => {
187 | if (err.code === ALREADY_INITIALIZED) {
188 | // deal with it!
189 | }
190 |
191 | if (err.code === NO_SIGNED_IN_USER) {
192 | // deal with it!
193 | }
194 | });
195 | }
196 |
197 | ```
198 | OR
199 | ```javascript
200 | function CLIFunctionInit(...params) {
201 |
202 | return Promise.resolve()
203 | .then(() => ensureRepoName(params))
204 | .then(() => kibibit.init(...params));
205 | }
206 |
207 | function ensureRepoName(params) {
208 | if (params.repoName) {
209 | return Promise.resolve();
210 | }
211 |
212 | return prompt('please provide a name for this repo')
213 | .then((response) => params.repoName = response);
214 | }
215 |
216 | ```
217 |
--------------------------------------------------------------------------------
/assets/diagrams/class/parser/classDiagram.jison:
--------------------------------------------------------------------------------
1 | /** mermaid
2 | * https://mermaidjs.github.io/
3 | * (c) 2015 Knut Sveidqvist
4 | * MIT license.
5 | */
6 |
7 | /* lexical grammar */
8 | %lex
9 | %x string struct
10 |
11 | %%
12 | \%\%[^\n]* /* do nothing */
13 | \n+ return 'NEWLINE';
14 | \s+ /* skip whitespace */
15 | "classDiagram" return 'CLASS_DIAGRAM';
16 | [\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';}
17 | \} { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
18 | [\n] /* nothing */
19 | [^\{\}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
20 |
21 |
22 |
23 | "class" return 'CLASS';
24 | ["] this.begin("string");
25 | ["] this.popState();
26 | [^"]* return "STR";
27 |
28 |
29 | \s*\<\| return 'EXTENSION';
30 | \s*\|\> return 'EXTENSION';
31 | \s*\> return 'DEPENDENCY';
32 | \s*\< return 'DEPENDENCY';
33 | \s*\* return 'COMPOSITION';
34 | \s*o return 'AGGREGATION';
35 | \-\- return 'LINE';
36 | \.\. return 'DOTTED_LINE';
37 | ":"[^#\n;]+ return 'LABEL';
38 | \- return 'MINUS';
39 | "." return 'DOT';
40 | \+ return 'PLUS';
41 | \% return 'PCT';
42 | "=" return 'EQUALS';
43 | \= return 'EQUALS';
44 | [A-Za-z]+ return 'ALPHA';
45 | [!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
46 | [0-9]+ return 'NUM';
47 | [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
48 | [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
49 | [\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
50 | [\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
51 | [\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|
52 | [\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|
53 | [\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|
54 | [\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|
55 | [\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|
56 | [\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|
57 | [\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|
58 | [\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|
59 | [\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|
60 | [\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|
61 | [\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|
62 | [\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|
63 | [\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|
64 | [\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|
65 | [\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|
66 | [\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|
67 | [\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|
68 | [\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|
69 | [\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|
70 | [\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|
71 | [\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|
72 | [\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|
73 | [\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|
74 | [\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|
75 | [\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|
76 | [\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|
77 | [\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|
78 | [\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|
79 | [\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|
80 | [\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|
81 | [\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|
82 | [\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|
83 | [\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|
84 | [\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|
85 | [\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|
86 | [\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|
87 | [\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|
88 | [\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|
89 | [\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|
90 | [\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|
91 | [\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|
92 | [\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|
93 | [\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|
94 | [\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|
95 | [\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|
96 | [\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|
97 | [\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|
98 | [\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|
99 | [\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|
100 | [\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|
101 | [\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|
102 | [\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|
103 | [\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|
104 | [\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|
105 | [\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
106 | [\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
107 | [\uFFD2-\uFFD7\uFFDA-\uFFDC]
108 | return 'UNICODE_TEXT';
109 | \s return 'SPACE';
110 | <> return 'EOF';
111 |
112 | /lex
113 |
114 | /* operator associations and precedence */
115 |
116 | %left '^'
117 |
118 | %start mermaidDoc
119 |
120 | %% /* language grammar */
121 |
122 | mermaidDoc: graphConfig;
123 |
124 | graphConfig
125 | : CLASS_DIAGRAM NEWLINE statements EOF
126 | ;
127 |
128 | statements
129 | : statement
130 | | statement NEWLINE statements
131 | ;
132 |
133 |
134 | className
135 | : alphaNumToken className { $$=$1+$2; }
136 | | alphaNumToken { $$=$1; }
137 | ;
138 |
139 | statement
140 | : relationStatement { yy.addRelation($1); }
141 | | relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
142 | | classStatement
143 | | methodStatement
144 | ;
145 |
146 | classStatement
147 | : CLASS className
148 | | CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addMembers($2,$4);}
149 | ;
150 |
151 | members
152 | : MEMBER { $$ = [$1]; }
153 | | MEMBER members { $2.push($1);$$=$2;}
154 | ;
155 |
156 | methodStatement
157 | : className {/*console.log('Rel found',$1);*/}
158 | | className LABEL {yy.addMembers($1,yy.cleanupLabel($2));}
159 | | MEMBER {console.warn('Member',$1);}
160 | | SEPARATOR {/*console.log('sep found',$1);*/}
161 | ;
162 |
163 | relationStatement
164 | : className relation className { $$ = {'id1':$1,'id2':$3, relation:$2, relationTitle1:'none', relationTitle2:'none'}; }
165 | | className STR relation className { $$ = {id1:$1, id2:$4, relation:$3, relationTitle1:$2, relationTitle2:'none'}}
166 | | className relation STR className { $$ = {id1:$1, id2:$4, relation:$2, relationTitle1:'none', relationTitle2:$3}; }
167 | | className STR relation STR className { $$ = {id1:$1, id2:$5, relation:$3, relationTitle1:$2, relationTitle2:$4} }
168 | ;
169 |
170 | relation
171 | : relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; }
172 | | lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; }
173 | | relationType lineType { $$={type1:$1,type2:'none',lineType:$2}; }
174 | | lineType { $$={type1:'none',type2:'none',lineType:$1}; }
175 | ;
176 |
177 | relationType
178 | : AGGREGATION { $$=yy.relationType.AGGREGATION;}
179 | | EXTENSION { $$=yy.relationType.EXTENSION;}
180 | | COMPOSITION { $$=yy.relationType.COMPOSITION;}
181 | | DEPENDENCY { $$=yy.relationType.DEPENDENCY;}
182 | ;
183 |
184 | lineType
185 | : LINE {$$=yy.lineType.LINE;}
186 | | DOTTED_LINE {$$=yy.lineType.DOTTED_LINE;}
187 | ;
188 |
189 | commentToken : textToken | graphCodeTokens ;
190 |
191 | textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT;
192 |
193 | textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
194 |
195 | alphaNumToken : UNICODE_TEXT | NUM | ALPHA;
196 | %%
197 |
--------------------------------------------------------------------------------
/assets/diagrams/gantt/ganttDb.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 | import { logger } from '../../logger'
3 |
4 | let dateFormat = ''
5 | let axisFormat = ''
6 | let title = ''
7 | let sections = []
8 | let tasks = []
9 | let currentSection = ''
10 |
11 | export const clear = function () {
12 | sections = []
13 | tasks = []
14 | currentSection = ''
15 | title = ''
16 | taskCnt = 0
17 | lastTask = undefined
18 | lastTaskID = undefined
19 | rawTasks = []
20 | }
21 |
22 | export const setAxisFormat = function (txt) {
23 | axisFormat = txt
24 | }
25 |
26 | export const getAxisFormat = function () {
27 | return axisFormat
28 | }
29 |
30 | export const setDateFormat = function (txt) {
31 | dateFormat = txt
32 | }
33 |
34 | export const setTitle = function (txt) {
35 | title = txt
36 | }
37 |
38 | export const getTitle = function () {
39 | return title
40 | }
41 |
42 | export const addSection = function (txt) {
43 | currentSection = txt
44 | sections.push(txt)
45 | }
46 |
47 | export const getTasks = function () {
48 | let allItemsPricessed = compileTasks()
49 | const maxDepth = 10
50 | let iterationCount = 0
51 | while (!allItemsPricessed && (iterationCount < maxDepth)) {
52 | allItemsPricessed = compileTasks()
53 | iterationCount++
54 | }
55 |
56 | tasks = rawTasks
57 |
58 | return tasks
59 | }
60 |
61 | const getStartDate = function (prevTime, dateFormat, str) {
62 | str = str.trim()
63 |
64 | // Test for after
65 | const re = /^after\s+([\d\w-]+)/
66 | const afterStatement = re.exec(str.trim())
67 |
68 | if (afterStatement !== null) {
69 | const task = findTaskById(afterStatement[1])
70 |
71 | if (typeof task === 'undefined') {
72 | const dt = new Date()
73 | dt.setHours(0, 0, 0, 0)
74 | return dt
75 | }
76 | return task.endTime
77 | }
78 |
79 | // Check for actual date set
80 | if (moment(str, dateFormat.trim(), true).isValid()) {
81 | return moment(str, dateFormat.trim(), true).toDate()
82 | } else {
83 | logger.debug('Invalid date:' + str)
84 | logger.debug('With date format:' + dateFormat.trim())
85 | }
86 |
87 | // Default date - now
88 | return new Date()
89 | }
90 |
91 | const getEndDate = function (prevTime, dateFormat, str) {
92 | str = str.trim()
93 |
94 | // Check for actual date
95 | if (moment(str, dateFormat.trim(), true).isValid()) {
96 | return moment(str, dateFormat.trim()).toDate()
97 | }
98 |
99 | const d = moment(prevTime)
100 | // Check for length
101 | const re = /^([\d]+)([wdhms])/
102 | const durationStatement = re.exec(str.trim())
103 |
104 | if (durationStatement !== null) {
105 | switch (durationStatement[2]) {
106 | case 's':
107 | d.add(durationStatement[1], 'seconds')
108 | break
109 | case 'm':
110 | d.add(durationStatement[1], 'minutes')
111 | break
112 | case 'h':
113 | d.add(durationStatement[1], 'hours')
114 | break
115 | case 'd':
116 | d.add(durationStatement[1], 'days')
117 | break
118 | case 'w':
119 | d.add(durationStatement[1], 'weeks')
120 | break
121 | }
122 | return d.toDate()
123 | }
124 | // Default date - now
125 | return d.toDate()
126 | }
127 |
128 | let taskCnt = 0
129 | const parseId = function (idStr) {
130 | if (typeof idStr === 'undefined') {
131 | taskCnt = taskCnt + 1
132 | return 'task' + taskCnt
133 | }
134 | return idStr
135 | }
136 | // id, startDate, endDate
137 | // id, startDate, length
138 | // id, after x, endDate
139 | // id, after x, length
140 | // startDate, endDate
141 | // startDate, length
142 | // after x, endDate
143 | // after x, length
144 | // endDate
145 | // length
146 |
147 | const compileData = function (prevTask, dataStr) {
148 | let ds
149 |
150 | if (dataStr.substr(0, 1) === ':') {
151 | ds = dataStr.substr(1, dataStr.length)
152 | } else {
153 | ds = dataStr
154 | }
155 |
156 | const data = ds.split(',')
157 |
158 | const task = {}
159 |
160 | // Get tags like active, done cand crit
161 | let matchFound = true
162 | while (matchFound) {
163 | matchFound = false
164 | if (data[0].match(/^\s*active\s*$/)) {
165 | task.active = true
166 | data.shift(1)
167 | matchFound = true
168 | }
169 | if (data[0].match(/^\s*done\s*$/)) {
170 | task.done = true
171 | data.shift(1)
172 | matchFound = true
173 | }
174 | if (data[0].match(/^\s*crit\s*$/)) {
175 | task.crit = true
176 | data.shift(1)
177 | matchFound = true
178 | }
179 | }
180 | for (let i = 0; i < data.length; i++) {
181 | data[i] = data[i].trim()
182 | }
183 |
184 | switch (data.length) {
185 | case 1:
186 | task.id = parseId()
187 | task.startTime = prevTask.endTime
188 | task.endTime = getEndDate(task.startTime, dateFormat, data[0])
189 | break
190 | case 2:
191 | task.id = parseId()
192 | task.startTime = getStartDate(undefined, dateFormat, data[0])
193 | task.endTime = getEndDate(task.startTime, dateFormat, data[1])
194 | break
195 | case 3:
196 | task.id = parseId(data[0])
197 | task.startTime = getStartDate(undefined, dateFormat, data[1])
198 | task.endTime = getEndDate(task.startTime, dateFormat, data[2])
199 | break
200 | default:
201 | }
202 |
203 | return task
204 | }
205 |
206 | const parseData = function (prevTaskId, dataStr) {
207 | let ds
208 | if (dataStr.substr(0, 1) === ':') {
209 | ds = dataStr.substr(1, dataStr.length)
210 | } else {
211 | ds = dataStr
212 | }
213 |
214 | const data = ds.split(',')
215 |
216 | const task = {}
217 |
218 | // Get tags like active, done cand crit
219 | let matchFound = true
220 | while (matchFound) {
221 | matchFound = false
222 | if (data[0].match(/^\s*active\s*$/)) {
223 | task.active = true
224 | data.shift(1)
225 | matchFound = true
226 | }
227 | if (data[0].match(/^\s*done\s*$/)) {
228 | task.done = true
229 | data.shift(1)
230 | matchFound = true
231 | }
232 | if (data[0].match(/^\s*crit\s*$/)) {
233 | task.crit = true
234 | data.shift(1)
235 | matchFound = true
236 | }
237 | }
238 | for (let i = 0; i < data.length; i++) {
239 | data[i] = data[i].trim()
240 | }
241 |
242 | switch (data.length) {
243 | case 1:
244 | task.id = parseId()
245 | task.startTime = { type: 'prevTaskEnd', id: prevTaskId }
246 | task.endTime = { data: data[0] }
247 | break
248 | case 2:
249 | task.id = parseId()
250 | task.startTime = { type: 'getStartDate', startData: data[0] }
251 | task.endTime = { data: data[1] }
252 | break
253 | case 3:
254 | task.id = parseId(data[0])
255 | task.startTime = { type: 'getStartDate', startData: data[1] }
256 | task.endTime = { data: data[2] }
257 | break
258 | default:
259 | }
260 |
261 | return task
262 | }
263 |
264 | let lastTask
265 | let lastTaskID
266 | let rawTasks = []
267 | const taskDb = {}
268 | export const addTask = function (descr, data) {
269 | const rawTask = {
270 | section: currentSection,
271 | type: currentSection,
272 | processed: false,
273 | raw: { data: data },
274 | task: descr
275 | }
276 | const taskInfo = parseData(lastTaskID, data)
277 | rawTask.raw.startTime = taskInfo.startTime
278 | rawTask.raw.endTime = taskInfo.endTime
279 | rawTask.id = taskInfo.id
280 | rawTask.prevTaskId = lastTaskID
281 | rawTask.active = taskInfo.active
282 | rawTask.done = taskInfo.done
283 | rawTask.crit = taskInfo.crit
284 |
285 | const pos = rawTasks.push(rawTask)
286 |
287 | lastTaskID = rawTask.id
288 | // Store cross ref
289 | taskDb[rawTask.id] = pos - 1
290 | }
291 |
292 | export const findTaskById = function (id) {
293 | const pos = taskDb[id]
294 | return rawTasks[pos]
295 | }
296 |
297 | export const addTaskOrg = function (descr, data) {
298 | const newTask = {
299 | section: currentSection,
300 | type: currentSection,
301 | description: descr,
302 | task: descr
303 | }
304 | const taskInfo = compileData(lastTask, data)
305 | newTask.startTime = taskInfo.startTime
306 | newTask.endTime = taskInfo.endTime
307 | newTask.id = taskInfo.id
308 | newTask.active = taskInfo.active
309 | newTask.done = taskInfo.done
310 | newTask.crit = taskInfo.crit
311 | lastTask = newTask
312 | tasks.push(newTask)
313 | }
314 |
315 | const compileTasks = function () {
316 | const compileTask = function (pos) {
317 | const task = rawTasks[pos]
318 | let startTime = ''
319 | switch (rawTasks[pos].raw.startTime.type) {
320 | case 'prevTaskEnd':
321 | const prevTask = findTaskById(task.prevTaskId)
322 | task.startTime = prevTask.endTime
323 | break
324 | case 'getStartDate':
325 | startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData)
326 | if (startTime) {
327 | rawTasks[pos].startTime = startTime
328 | }
329 | break
330 | }
331 |
332 | if (rawTasks[pos].startTime) {
333 | rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data)
334 | if (rawTasks[pos].endTime) {
335 | rawTasks[pos].processed = true
336 | }
337 | }
338 |
339 | return rawTasks[pos].processed
340 | }
341 |
342 | let allProcessed = true
343 | for (let i = 0; i < rawTasks.length; i++) {
344 | compileTask(i)
345 |
346 | allProcessed = allProcessed && rawTasks[i].processed
347 | }
348 | return allProcessed
349 | }
350 |
351 | export default {
352 | clear,
353 | setDateFormat,
354 | setAxisFormat,
355 | getAxisFormat,
356 | setTitle,
357 | getTitle,
358 | addSection,
359 | getTasks,
360 | addTask,
361 | findTaskById,
362 | addTaskOrg
363 | }
364 |
--------------------------------------------------------------------------------
/assets/diagrams/sequence/svgDraw.js:
--------------------------------------------------------------------------------
1 | export const drawRect = function (elem, rectData) {
2 | const rectElem = elem.append('rect')
3 | rectElem.attr('x', rectData.x)
4 | rectElem.attr('y', rectData.y)
5 | rectElem.attr('fill', rectData.fill)
6 | rectElem.attr('stroke', rectData.stroke)
7 | rectElem.attr('width', rectData.width)
8 | rectElem.attr('height', rectData.height)
9 | rectElem.attr('rx', rectData.rx)
10 | rectElem.attr('ry', rectData.ry)
11 |
12 | if (typeof rectData.class !== 'undefined') {
13 | rectElem.attr('class', rectData.class)
14 | }
15 |
16 | return rectElem
17 | }
18 |
19 | export const drawText = function (elem, textData, width) {
20 | // Remove and ignore br:s
21 | const nText = textData.text.replace(/
/ig, ' ')
22 |
23 | const textElem = elem.append('text')
24 | textElem.attr('x', textData.x)
25 | textElem.attr('y', textData.y)
26 | textElem.style('text-anchor', textData.anchor)
27 | textElem.attr('fill', textData.fill)
28 | if (typeof textData.class !== 'undefined') {
29 | textElem.attr('class', textData.class)
30 | }
31 |
32 | const span = textElem.append('tspan')
33 | span.attr('x', textData.x + textData.textMargin * 2)
34 | span.attr('fill', textData.fill)
35 | span.text(nText)
36 |
37 | return textElem
38 | }
39 |
40 | export const drawLabel = function (elem, txtObject) {
41 | function genPoints (x, y, width, height, cut) {
42 | return x + ',' + y + ' ' +
43 | (x + width) + ',' + y + ' ' +
44 | (x + width) + ',' + (y + height - cut) + ' ' +
45 | (x + width - cut * 1.2) + ',' + (y + height) + ' ' +
46 | (x) + ',' + (y + height)
47 | }
48 | const polygon = elem.append('polygon')
49 | polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7))
50 | polygon.attr('class', 'labelBox')
51 |
52 | txtObject.y = txtObject.y + txtObject.labelMargin
53 | txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin
54 | drawText(elem, txtObject)
55 | }
56 |
57 | let actorCnt = -1
58 | /**
59 | * Draws an actor in the diagram with the attaced line
60 | * @param center - The center of the the actor
61 | * @param pos The position if the actor in the liost of actors
62 | * @param description The text in the box
63 | */
64 | export const drawActor = function (elem, left, verticalPos, description, conf) {
65 | const center = left + (conf.width / 2)
66 | const g = elem.append('g')
67 | if (verticalPos === 0) {
68 | actorCnt++
69 | g.append('line')
70 | .attr('id', 'actor' + actorCnt)
71 | .attr('x1', center)
72 | .attr('y1', 5)
73 | .attr('x2', center)
74 | .attr('y2', 2000)
75 | .attr('class', 'actor-line')
76 | .attr('stroke-width', '0.5px')
77 | .attr('stroke', '#999')
78 | }
79 |
80 | const rect = getNoteRect()
81 | rect.x = left
82 | rect.y = verticalPos
83 | rect.fill = '#eaeaea'
84 | rect.width = conf.width
85 | rect.height = conf.height
86 | rect.class = 'actor'
87 | rect.rx = 3
88 | rect.ry = 3
89 | drawRect(g, rect)
90 |
91 | _drawTextCandidateFunc(conf)(description, g,
92 | rect.x, rect.y, rect.width, rect.height, { 'class': 'actor' })
93 | }
94 |
95 | export const anchorElement = function (elem) {
96 | return elem.append('g')
97 | }
98 | /**
99 | * Draws an actor in the diagram with the attaced line
100 | * @param elem - element to append activation rect
101 | * @param bounds - activation box bounds
102 | * @param verticalPos - precise y cooridnate of bottom activation box edge
103 | */
104 | export const drawActivation = function (elem, bounds, verticalPos) {
105 | const rect = getNoteRect()
106 | const g = bounds.anchored
107 | rect.x = bounds.startx
108 | rect.y = bounds.starty
109 | rect.fill = '#f4f4f4'
110 | rect.width = bounds.stopx - bounds.startx
111 | rect.height = verticalPos - bounds.starty
112 | drawRect(g, rect)
113 | }
114 |
115 | /**
116 | * Draws an actor in the diagram with the attaced line
117 | * @param center - The center of the the actor
118 | * @param pos The position if the actor in the list of actors
119 | * @param description The text in the box
120 | */
121 | export const drawLoop = function (elem, bounds, labelText, conf) {
122 | const g = elem.append('g')
123 | const drawLoopLine = function (startx, starty, stopx, stopy) {
124 | return g.append('line')
125 | .attr('x1', startx)
126 | .attr('y1', starty)
127 | .attr('x2', stopx)
128 | .attr('y2', stopy)
129 | .attr('class', 'loopLine')
130 | }
131 | drawLoopLine(bounds.startx, bounds.starty, bounds.stopx, bounds.starty)
132 | drawLoopLine(bounds.stopx, bounds.starty, bounds.stopx, bounds.stopy)
133 | drawLoopLine(bounds.startx, bounds.stopy, bounds.stopx, bounds.stopy)
134 | drawLoopLine(bounds.startx, bounds.starty, bounds.startx, bounds.stopy)
135 | if (typeof bounds.sections !== 'undefined') {
136 | bounds.sections.forEach(function (item) {
137 | drawLoopLine(bounds.startx, item, bounds.stopx, item).style('stroke-dasharray', '3, 3')
138 | })
139 | }
140 |
141 | let txt = getTextObj()
142 | txt.text = labelText
143 | txt.x = bounds.startx
144 | txt.y = bounds.starty
145 | txt.labelMargin = 1.5 * 10 // This is the small box that says "loop"
146 | txt.class = 'labelText' // Its size & position are fixed.
147 |
148 | drawLabel(g, txt)
149 |
150 | txt = getTextObj()
151 | txt.text = '[ ' + bounds.title + ' ]'
152 | txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2
153 | txt.y = bounds.starty + 1.5 * conf.boxMargin
154 | txt.anchor = 'middle'
155 | txt.class = 'loopText'
156 |
157 | drawText(g, txt)
158 |
159 | if (typeof bounds.sectionTitles !== 'undefined') {
160 | bounds.sectionTitles.forEach(function (item, idx) {
161 | if (item !== '') {
162 | txt.text = '[ ' + item + ' ]'
163 | txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin
164 | drawText(g, txt)
165 | }
166 | })
167 | }
168 | }
169 |
170 | /**
171 | * Setup arrow head and define the marker. The result is appended to the svg.
172 | */
173 | export const insertArrowHead = function (elem) {
174 | elem.append('defs').append('marker')
175 | .attr('id', 'arrowhead')
176 | .attr('refX', 5)
177 | .attr('refY', 2)
178 | .attr('markerWidth', 6)
179 | .attr('markerHeight', 4)
180 | .attr('orient', 'auto')
181 | .append('path')
182 | .attr('d', 'M 0,0 V 4 L6,2 Z') // this is actual shape for arrowhead
183 | }
184 | /**
185 | * Setup arrow head and define the marker. The result is appended to the svg.
186 | */
187 | export const insertArrowCrossHead = function (elem) {
188 | const defs = elem.append('defs')
189 | const marker = defs.append('marker')
190 | .attr('id', 'crosshead')
191 | .attr('markerWidth', 15)
192 | .attr('markerHeight', 8)
193 | .attr('orient', 'auto')
194 | .attr('refX', 16)
195 | .attr('refY', 4)
196 |
197 | // The arrow
198 | marker.append('path')
199 | .attr('fill', 'black')
200 | .attr('stroke', '#000000')
201 | .style('stroke-dasharray', ('0, 0'))
202 | .attr('stroke-width', '1px')
203 | .attr('d', 'M 9,2 V 6 L16,4 Z')
204 |
205 | // The cross
206 | marker.append('path')
207 | .attr('fill', 'none')
208 | .attr('stroke', '#000000')
209 | .style('stroke-dasharray', ('0, 0'))
210 | .attr('stroke-width', '1px')
211 | .attr('d', 'M 0,1 L 6,7 M 6,1 L 0,7')
212 | // this is actual shape for arrowhead
213 | }
214 |
215 | export const getTextObj = function () {
216 | const txt = {
217 | x: 0,
218 | y: 0,
219 | 'fill': 'black',
220 | 'text-anchor': 'start',
221 | style: '#666',
222 | width: 100,
223 | height: 100,
224 | textMargin: 0,
225 | rx: 0,
226 | ry: 0
227 | }
228 | return txt
229 | }
230 |
231 | export const getNoteRect = function () {
232 | const rect = {
233 | x: 0,
234 | y: 0,
235 | fill: '#EDF2AE',
236 | stroke: '#666',
237 | width: 100,
238 | anchor: 'start',
239 | height: 100,
240 | rx: 0,
241 | ry: 0
242 | }
243 | return rect
244 | }
245 |
246 | const _drawTextCandidateFunc = (function () {
247 | function byText (content, g, x, y, width, height, textAttrs) {
248 | const text = g.append('text')
249 | .attr('x', x + width / 2).attr('y', y + height / 2 + 5)
250 | .style('text-anchor', 'middle')
251 | .text(content)
252 | _setTextAttrs(text, textAttrs)
253 | }
254 |
255 | function byTspan (content, g, x, y, width, height, textAttrs) {
256 | const text = g.append('text')
257 | .attr('x', x + width / 2).attr('y', y)
258 | .style('text-anchor', 'middle')
259 | text.append('tspan')
260 | .attr('x', x + width / 2).attr('dy', '0')
261 | .text(content)
262 |
263 | text.attr('y', y + height / 2.0)
264 | .attr('dominant-baseline', 'central')
265 | .attr('alignment-baseline', 'central')
266 |
267 | _setTextAttrs(text, textAttrs)
268 | }
269 |
270 | function byFo (content, g, x, y, width, height, textAttrs) {
271 | const s = g.append('switch')
272 | const f = s.append('foreignObject')
273 | .attr('x', x).attr('y', y)
274 | .attr('width', width).attr('height', height)
275 |
276 | const text = f.append('div').style('display', 'table')
277 | .style('height', '100%').style('width', '100%')
278 |
279 | text.append('div').style('display', 'table-cell')
280 | .style('text-align', 'center').style('vertical-align', 'middle')
281 | .text(content)
282 |
283 | byTspan(content, s, x, y, width, height, textAttrs)
284 | _setTextAttrs(text, textAttrs)
285 | }
286 |
287 | function _setTextAttrs (toText, fromTextAttrsDict) {
288 | for (const key in fromTextAttrsDict) {
289 | if (fromTextAttrsDict.hasOwnProperty(key)) {
290 | toText.attr(key, fromTextAttrsDict[key])
291 | }
292 | }
293 | }
294 |
295 | return function (conf) {
296 | return conf.textPlacement === 'fo' ? byFo : (
297 | conf.textPlacement === 'old' ? byText : byTspan)
298 | }
299 | })()
300 |
301 | export default {
302 | drawRect,
303 | drawText,
304 | drawLabel,
305 | drawActor,
306 | anchorElement,
307 | drawActivation,
308 | drawLoop,
309 | insertArrowHead,
310 | insertArrowCrossHead,
311 | getTextObj,
312 | getNoteRect
313 | }
314 |
--------------------------------------------------------------------------------
/assets/diagrams/git/gitGraphRenderer.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import * as d3 from 'd3'
3 |
4 | import db from './gitGraphAst'
5 | import gitGraphParser from './parser/gitGraph'
6 | import { logger } from '../../logger'
7 | import { interpolateToCurve } from '../../utils'
8 |
9 | let allCommitsDict = {}
10 | let branchNum
11 | let config = {
12 | nodeSpacing: 150,
13 | nodeFillColor: 'yellow',
14 | nodeStrokeWidth: 2,
15 | nodeStrokeColor: 'grey',
16 | lineStrokeWidth: 4,
17 | branchOffset: 50,
18 | lineColor: 'grey',
19 | leftMargin: 50,
20 | branchColors: ['#442f74', '#983351', '#609732', '#AA9A39'],
21 | nodeRadius: 10,
22 | nodeLabel: {
23 | width: 75,
24 | height: 100,
25 | x: -25,
26 | y: 0
27 | }
28 | }
29 | let apiConfig = {}
30 | export const setConf = function (c) {
31 | apiConfig = c
32 | }
33 |
34 | function svgCreateDefs (svg) {
35 | svg
36 | .append('defs')
37 | .append('g')
38 | .attr('id', 'def-commit')
39 | .append('circle')
40 | .attr('r', config.nodeRadius)
41 | .attr('cx', 0)
42 | .attr('cy', 0)
43 | svg.select('#def-commit')
44 | .append('foreignObject')
45 | .attr('width', config.nodeLabel.width)
46 | .attr('height', config.nodeLabel.height)
47 | .attr('x', config.nodeLabel.x)
48 | .attr('y', config.nodeLabel.y)
49 | .attr('class', 'node-label')
50 | .attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility')
51 | .append('p')
52 | .html('')
53 | }
54 |
55 | function svgDrawLine (svg, points, colorIdx, interpolate) {
56 | const curve = interpolateToCurve(interpolate, d3.curveBasis)
57 | const color = config.branchColors[colorIdx % config.branchColors.length]
58 | const lineGen = d3.line()
59 | .x(function (d) {
60 | return Math.round(d.x)
61 | })
62 | .y(function (d) {
63 | return Math.round(d.y)
64 | })
65 | .curve(curve)
66 |
67 | svg
68 | .append('svg:path')
69 | .attr('d', lineGen(points))
70 | .style('stroke', color)
71 | .style('stroke-width', config.lineStrokeWidth)
72 | .style('fill', 'none')
73 | }
74 |
75 | // Pass in the element and its pre-transform coords
76 | function getElementCoords (element, coords) {
77 | coords = coords || element.node().getBBox()
78 | const ctm = element.node().getCTM()
79 | const xn = ctm.e + coords.x * ctm.a
80 | const yn = ctm.f + coords.y * ctm.d
81 | return {
82 | left: xn,
83 | top: yn,
84 | width: coords.width,
85 | height: coords.height
86 | }
87 | }
88 |
89 | function svgDrawLineForCommits (svg, fromId, toId, direction, color) {
90 | logger.debug('svgDrawLineForCommits: ', fromId, toId)
91 | const fromBbox = getElementCoords(svg.select('#node-' + fromId + ' circle'))
92 | const toBbox = getElementCoords(svg.select('#node-' + toId + ' circle'))
93 | switch (direction) {
94 | case 'LR':
95 | // (toBbox)
96 | // +--------
97 | // + (fromBbox)
98 | if (fromBbox.left - toBbox.left > config.nodeSpacing) {
99 | const lineStart = { x: fromBbox.left - config.nodeSpacing, y: toBbox.top + toBbox.height / 2 }
100 | const lineEnd = { x: toBbox.left + toBbox.width, y: toBbox.top + toBbox.height / 2 }
101 | svgDrawLine(svg, [lineStart, lineEnd], color, 'linear')
102 | svgDrawLine(svg, [
103 | { x: fromBbox.left, y: fromBbox.top + fromBbox.height / 2 },
104 | { x: fromBbox.left - config.nodeSpacing / 2, y: fromBbox.top + fromBbox.height / 2 },
105 | { x: fromBbox.left - config.nodeSpacing / 2, y: lineStart.y },
106 | lineStart], color)
107 | } else {
108 | svgDrawLine(svg, [{
109 | 'x': fromBbox.left,
110 | 'y': fromBbox.top + fromBbox.height / 2
111 | }, {
112 | 'x': fromBbox.left - config.nodeSpacing / 2,
113 | 'y': fromBbox.top + fromBbox.height / 2
114 | }, {
115 | 'x': fromBbox.left - config.nodeSpacing / 2,
116 | 'y': toBbox.top + toBbox.height / 2
117 | }, {
118 | 'x': toBbox.left + toBbox.width,
119 | 'y': toBbox.top + toBbox.height / 2
120 | }], color)
121 | }
122 | break
123 | case 'BT':
124 | // + (fromBbox)
125 | // |
126 | // |
127 | // + (toBbox)
128 | if (toBbox.top - fromBbox.top > config.nodeSpacing) {
129 | const lineStart = { x: toBbox.left + toBbox.width / 2, y: fromBbox.top + fromBbox.height + config.nodeSpacing }
130 | const lineEnd = { x: toBbox.left + toBbox.width / 2, y: toBbox.top }
131 | svgDrawLine(svg, [lineStart, lineEnd], color, 'linear')
132 | svgDrawLine(svg, [
133 | { x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height },
134 | { x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height + config.nodeSpacing / 2 },
135 | { x: toBbox.left + toBbox.width / 2, y: lineStart.y - config.nodeSpacing / 2 },
136 | lineStart], color)
137 | } else {
138 | svgDrawLine(svg, [{
139 | 'x': fromBbox.left + fromBbox.width / 2,
140 | 'y': fromBbox.top + fromBbox.height
141 | }, {
142 | 'x': fromBbox.left + fromBbox.width / 2,
143 | 'y': fromBbox.top + config.nodeSpacing / 2
144 | }, {
145 | 'x': toBbox.left + toBbox.width / 2,
146 | 'y': toBbox.top - config.nodeSpacing / 2
147 | }, {
148 | 'x': toBbox.left + toBbox.width / 2,
149 | 'y': toBbox.top
150 | }], color)
151 | }
152 | break
153 | }
154 | }
155 |
156 | function cloneNode (svg, selector) {
157 | return svg.select(selector).node().cloneNode(true)
158 | }
159 |
160 | function renderCommitHistory (svg, commitid, branches, direction) {
161 | let commit
162 | const numCommits = Object.keys(allCommitsDict).length
163 | if (_.isString(commitid)) {
164 | do {
165 | commit = allCommitsDict[commitid]
166 | logger.debug('in renderCommitHistory', commit.id, commit.seq)
167 | if (svg.select('#node-' + commitid).size() > 0) {
168 | return
169 | }
170 | svg
171 | .append(function () {
172 | return cloneNode(svg, '#def-commit')
173 | })
174 | .attr('class', 'commit')
175 | .attr('id', function () {
176 | return 'node-' + commit.id
177 | })
178 | .attr('transform', function () {
179 | switch (direction) {
180 | case 'LR':
181 | return 'translate(' + (commit.seq * config.nodeSpacing + config.leftMargin) + ', ' +
182 | (branchNum * config.branchOffset) + ')'
183 | case 'BT':
184 | return 'translate(' + (branchNum * config.branchOffset + config.leftMargin) + ', ' +
185 | ((numCommits - commit.seq) * config.nodeSpacing) + ')'
186 | }
187 | })
188 | .attr('fill', config.nodeFillColor)
189 | .attr('stroke', config.nodeStrokeColor)
190 | .attr('stroke-width', config.nodeStrokeWidth)
191 |
192 | const branch = _.find(branches, ['commit', commit])
193 | if (branch) {
194 | logger.debug('found branch ', branch.name)
195 | svg.select('#node-' + commit.id + ' p')
196 | .append('xhtml:span')
197 | .attr('class', 'branch-label')
198 | .text(branch.name + ', ')
199 | }
200 | svg.select('#node-' + commit.id + ' p')
201 | .append('xhtml:span')
202 | .attr('class', 'commit-id')
203 | .text(commit.id)
204 | if (commit.message !== '' && direction === 'BT') {
205 | svg.select('#node-' + commit.id + ' p')
206 | .append('xhtml:span')
207 | .attr('class', 'commit-msg')
208 | .text(', ' + commit.message)
209 | }
210 | commitid = commit.parent
211 | } while (commitid && allCommitsDict[commitid])
212 | }
213 |
214 | if (_.isArray(commitid)) {
215 | logger.debug('found merge commmit', commitid)
216 | renderCommitHistory(svg, commitid[0], branches, direction)
217 | branchNum++
218 | renderCommitHistory(svg, commitid[1], branches, direction)
219 | branchNum--
220 | }
221 | }
222 |
223 | function renderLines (svg, commit, direction, branchColor) {
224 | branchColor = branchColor || 0
225 | while (commit.seq > 0 && !commit.lineDrawn) {
226 | if (_.isString(commit.parent)) {
227 | svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor)
228 | commit.lineDrawn = true
229 | commit = allCommitsDict[commit.parent]
230 | } else if (_.isArray(commit.parent)) {
231 | svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor)
232 | svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1)
233 | renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1)
234 | commit.lineDrawn = true
235 | commit = allCommitsDict[commit.parent[0]]
236 | }
237 | }
238 | }
239 |
240 | export const draw = function (txt, id, ver) {
241 | try {
242 | const parser = gitGraphParser.parser
243 | parser.yy = db
244 |
245 | logger.debug('in gitgraph renderer', txt, id, ver)
246 | // Parse the graph definition
247 | parser.parse(txt + '\n')
248 |
249 | config = _.extend(config, apiConfig, db.getOptions())
250 | logger.debug('effective options', config)
251 | const direction = db.getDirection()
252 | allCommitsDict = db.getCommits()
253 | const branches = db.getBranchesAsObjArray()
254 | if (direction === 'BT') {
255 | config.nodeLabel.x = branches.length * config.branchOffset
256 | config.nodeLabel.width = '100%'
257 | config.nodeLabel.y = -1 * 2 * config.nodeRadius
258 | }
259 | const svg = d3.select(`[id="${id}"]`)
260 | svgCreateDefs(svg)
261 | branchNum = 1
262 | _.each(branches, function (v) {
263 | renderCommitHistory(svg, v.commit.id, branches, direction)
264 | renderLines(svg, v.commit, direction)
265 | branchNum++
266 | })
267 | svg.attr('height', function () {
268 | if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing
269 | return (branches.length + 1) * config.branchOffset
270 | })
271 | } catch (e) {
272 | logger.error('Error while rendering gitgraph')
273 | logger.error(e.message)
274 | }
275 | }
276 |
277 | export default {
278 | setConf,
279 | draw
280 | }
281 |
--------------------------------------------------------------------------------
/assets/diagrams/gantt/ganttRenderer.js:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3'
2 |
3 | import { parser } from './parser/gantt'
4 | import ganttDb from './ganttDb'
5 |
6 | parser.yy = ganttDb
7 |
8 | const conf = {
9 | titleTopMargin: 25,
10 | barHeight: 20,
11 | barGap: 4,
12 | topPadding: 50,
13 | rightPadding: 75,
14 | leftPadding: 75,
15 | gridLineStartPadding: 35,
16 | fontSize: 11,
17 | fontFamily: '"Open-Sans", "sans-serif"'
18 | }
19 | export const setConf = function (cnf) {
20 | const keys = Object.keys(cnf)
21 |
22 | keys.forEach(function (key) {
23 | conf[key] = cnf[key]
24 | })
25 | }
26 | let w
27 | export const draw = function (text, id) {
28 | parser.yy.clear()
29 | parser.parse(text)
30 |
31 | const elem = document.getElementById(id)
32 | w = elem.parentElement.offsetWidth
33 |
34 | if (typeof w === 'undefined') {
35 | w = 1200
36 | }
37 |
38 | if (typeof conf.useWidth !== 'undefined') {
39 | w = conf.useWidth
40 | }
41 |
42 | const taskArray = parser.yy.getTasks()
43 |
44 | // Set height based on number of tasks
45 | const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding
46 |
47 | elem.setAttribute('height', '100%')
48 | // Set viewBox
49 | elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h)
50 | const svg = d3.select(`[id="${id}"]`)
51 |
52 | // Set timescale
53 | const timeScale = d3.scaleTime()
54 | .domain([d3.min(taskArray, function (d) {
55 | return d.startTime
56 | }),
57 | d3.max(taskArray, function (d) {
58 | return d.endTime
59 | })])
60 | .rangeRound([0, w - conf.leftPadding - conf.rightPadding])
61 |
62 | let categories = []
63 |
64 | for (let i = 0; i < taskArray.length; i++) {
65 | categories.push(taskArray[i].type)
66 | }
67 |
68 | const catsUnfiltered = categories // for vert labels
69 |
70 | categories = checkUnique(categories)
71 |
72 | makeGant(taskArray, w, h)
73 | if (typeof conf.useWidth !== 'undefined') {
74 | elem.setAttribute('width', w)
75 | }
76 |
77 | svg.append('text')
78 | .text(parser.yy.getTitle())
79 | .attr('x', w / 2)
80 | .attr('y', conf.titleTopMargin)
81 | .attr('class', 'titleText')
82 |
83 | function makeGant (tasks, pageWidth, pageHeight) {
84 | const barHeight = conf.barHeight
85 | const gap = barHeight + conf.barGap
86 | const topPadding = conf.topPadding
87 | const leftPadding = conf.leftPadding
88 |
89 | const colorScale = d3.scaleLinear()
90 | .domain([0, categories.length])
91 | .range(['#00B9FA', '#F95002'])
92 | .interpolate(d3.interpolateHcl)
93 |
94 | makeGrid(leftPadding, topPadding, pageWidth, pageHeight)
95 | drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight)
96 | vertLabels(gap, topPadding, leftPadding, barHeight, colorScale)
97 | drawToday(leftPadding, topPadding, pageWidth, pageHeight)
98 | }
99 |
100 | function drawRects (theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
101 | svg.append('g')
102 | .selectAll('rect')
103 | .data(theArray)
104 | .enter()
105 | .append('rect')
106 | .attr('x', 0)
107 | .attr('y', function (d, i) {
108 | return i * theGap + theTopPad - 2
109 | })
110 | .attr('width', function () {
111 | return w - conf.rightPadding / 2
112 | })
113 | .attr('height', theGap)
114 | .attr('class', function (d) {
115 | for (let i = 0; i < categories.length; i++) {
116 | if (d.type === categories[i]) {
117 | return 'section section' + (i % conf.numberSectionStyles)
118 | }
119 | }
120 | return 'section section0'
121 | })
122 |
123 | const rectangles = svg.append('g')
124 | .selectAll('rect')
125 | .data(theArray)
126 | .enter()
127 |
128 | rectangles.append('rect')
129 | .attr('rx', 3)
130 | .attr('ry', 3)
131 | .attr('x', function (d) {
132 | return timeScale(d.startTime) + theSidePad
133 | })
134 | .attr('y', function (d, i) {
135 | return i * theGap + theTopPad
136 | })
137 | .attr('width', function (d) {
138 | return (timeScale(d.endTime) - timeScale(d.startTime))
139 | })
140 | .attr('height', theBarHeight)
141 | .attr('class', function (d) {
142 | const res = 'task '
143 |
144 | let secNum = 0
145 | for (let i = 0; i < categories.length; i++) {
146 | if (d.type === categories[i]) {
147 | secNum = (i % conf.numberSectionStyles)
148 | }
149 | }
150 |
151 | if (d.active) {
152 | if (d.crit) {
153 | return res + ' activeCrit' + secNum
154 | } else {
155 | return res + ' active' + secNum
156 | }
157 | }
158 |
159 | if (d.done) {
160 | if (d.crit) {
161 | return res + ' doneCrit' + secNum
162 | } else {
163 | return res + ' done' + secNum
164 | }
165 | }
166 |
167 | if (d.crit) {
168 | return res + ' crit' + secNum
169 | }
170 |
171 | return res + ' task' + secNum
172 | })
173 |
174 | rectangles.append('text')
175 | .text(function (d) {
176 | return d.task
177 | })
178 | .attr('font-size', conf.fontSize)
179 | .attr('x', function (d) {
180 | const startX = timeScale(d.startTime)
181 | const endX = timeScale(d.endTime)
182 | const textWidth = this.getBBox().width
183 |
184 | // Check id text width > width of rectangle
185 | if (textWidth > (endX - startX)) {
186 | if (endX + textWidth + 1.5 * conf.leftPadding > w) {
187 | return startX + theSidePad - 5
188 | } else {
189 | return endX + theSidePad + 5
190 | }
191 | } else {
192 | return (endX - startX) / 2 + startX + theSidePad
193 | }
194 | })
195 | .attr('y', function (d, i) {
196 | return i * theGap + (conf.barHeight / 2) + (conf.fontSize / 2 - 2) + theTopPad
197 | })
198 | .attr('text-height', theBarHeight)
199 | .attr('class', function (d) {
200 | const startX = timeScale(d.startTime)
201 | const endX = timeScale(d.endTime)
202 | const textWidth = this.getBBox().width
203 | let secNum = 0
204 | for (let i = 0; i < categories.length; i++) {
205 | if (d.type === categories[i]) {
206 | secNum = (i % conf.numberSectionStyles)
207 | }
208 | }
209 |
210 | let taskType = ''
211 | if (d.active) {
212 | if (d.crit) {
213 | taskType = 'activeCritText' + secNum
214 | } else {
215 | taskType = 'activeText' + secNum
216 | }
217 | }
218 |
219 | if (d.done) {
220 | if (d.crit) {
221 | taskType = taskType + ' doneCritText' + secNum
222 | } else {
223 | taskType = taskType + ' doneText' + secNum
224 | }
225 | } else {
226 | if (d.crit) {
227 | taskType = taskType + ' critText' + secNum
228 | }
229 | }
230 |
231 | // Check id text width > width of rectangle
232 | if (textWidth > (endX - startX)) {
233 | if (endX + textWidth + 1.5 * conf.leftPadding > w) {
234 | return 'taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType
235 | } else {
236 | return 'taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType
237 | }
238 | } else {
239 | return 'taskText taskText' + secNum + ' ' + taskType
240 | }
241 | })
242 | }
243 |
244 | function makeGrid (theSidePad, theTopPad, w, h) {
245 | let xAxis = d3.axisBottom(timeScale)
246 | .tickSize(-h + theTopPad + conf.gridLineStartPadding)
247 | .tickFormat(d3.timeFormat(parser.yy.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'))
248 |
249 | svg.append('g')
250 | .attr('class', 'grid')
251 | .attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')')
252 | .call(xAxis)
253 | .selectAll('text')
254 | .style('text-anchor', 'middle')
255 | .attr('fill', '#000')
256 | .attr('stroke', 'none')
257 | .attr('font-size', 10)
258 | .attr('dy', '1em')
259 | }
260 |
261 | function vertLabels (theGap, theTopPad) {
262 | const numOccurances = []
263 | let prevGap = 0
264 |
265 | for (let i = 0; i < categories.length; i++) {
266 | numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)]
267 | }
268 |
269 | svg.append('g') // without doing this, impossible to put grid lines behind text
270 | .selectAll('text')
271 | .data(numOccurances)
272 | .enter()
273 | .append('text')
274 | .text(function (d) {
275 | return d[0]
276 | })
277 | .attr('x', 10)
278 | .attr('y', function (d, i) {
279 | if (i > 0) {
280 | for (let j = 0; j < i; j++) {
281 | prevGap += numOccurances[i - 1][1]
282 | return d[1] * theGap / 2 + prevGap * theGap + theTopPad
283 | }
284 | } else {
285 | return d[1] * theGap / 2 + theTopPad
286 | }
287 | })
288 | .attr('class', function (d) {
289 | for (let i = 0; i < categories.length; i++) {
290 | if (d[0] === categories[i]) {
291 | return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles)
292 | }
293 | }
294 | return 'sectionTitle'
295 | })
296 | }
297 |
298 | function drawToday (theSidePad, theTopPad, w, h) {
299 | const todayG = svg.append('g')
300 | .attr('class', 'today')
301 |
302 | const today = new Date()
303 |
304 | todayG.append('line')
305 | .attr('x1', timeScale(today) + theSidePad)
306 | .attr('x2', timeScale(today) + theSidePad)
307 | .attr('y1', conf.titleTopMargin)
308 | .attr('y2', h - conf.titleTopMargin)
309 | .attr('class', 'today')
310 | }
311 |
312 | // from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
313 | function checkUnique (arr) {
314 | const hash = {}
315 | const result = []
316 | for (let i = 0, l = arr.length; i < l; ++i) {
317 | if (!hash.hasOwnProperty(arr[i])) { // it works with objects! in FF, at least
318 | hash[arr[i]] = true
319 | result.push(arr[i])
320 | }
321 | }
322 | return result
323 | }
324 |
325 | // from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
326 | function getCounts (arr) {
327 | let i = arr.length // const to loop over
328 | const obj = {} // obj to store results
329 | while (i) {
330 | obj[arr[--i]] = (obj[arr[i]] || 0) + 1 // count occurrences
331 | }
332 | return obj
333 | }
334 |
335 | // get specific from everything
336 | function getCount (word, arr) {
337 | return getCounts(arr)[word] || 0
338 | }
339 | }
340 |
341 | export default {
342 | setConf,
343 | draw
344 | }
345 |
--------------------------------------------------------------------------------
/assets/diagrams/flowchart/flowDb.js:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3'
2 |
3 | import { logger } from '../../logger'
4 | import utils from '../../utils'
5 |
6 | let vertices = {}
7 | let edges = []
8 | let classes = []
9 | let subGraphs = []
10 | let tooltips = {}
11 | let subCount = 0
12 | let direction
13 | // Functions to be run after graph rendering
14 | let funs = []
15 | /**
16 | * Function called by parser when a node definition has been found
17 | * @param id
18 | * @param text
19 | * @param type
20 | * @param style
21 | */
22 | export const addVertex = function (id, text, type, style) {
23 | let txt
24 |
25 | if (typeof id === 'undefined') {
26 | return
27 | }
28 | if (id.trim().length === 0) {
29 | return
30 | }
31 |
32 | if (typeof vertices[id] === 'undefined') {
33 | vertices[id] = { id: id, styles: [], classes: [] }
34 | }
35 | if (typeof text !== 'undefined') {
36 | txt = text.trim()
37 |
38 | // strip quotes if string starts and exnds with a quote
39 | if (txt[0] === '"' && txt[txt.length - 1] === '"') {
40 | txt = txt.substring(1, txt.length - 1)
41 | }
42 |
43 | vertices[id].text = txt
44 | }
45 | if (typeof type !== 'undefined') {
46 | vertices[id].type = type
47 | }
48 | if (typeof type !== 'undefined') {
49 | vertices[id].type = type
50 | }
51 | if (typeof style !== 'undefined') {
52 | if (style !== null) {
53 | style.forEach(function (s) {
54 | vertices[id].styles.push(s)
55 | })
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Function called by parser when a link/edge definition has been found
62 | * @param start
63 | * @param end
64 | * @param type
65 | * @param linktext
66 | */
67 | export const addLink = function (start, end, type, linktext) {
68 | logger.info('Got edge...', start, end)
69 | const edge = { start: start, end: end, type: undefined, text: '' }
70 | linktext = type.text
71 |
72 | if (typeof linktext !== 'undefined') {
73 | edge.text = linktext.trim()
74 |
75 | // strip quotes if string starts and exnds with a quote
76 | if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
77 | edge.text = edge.text.substring(1, edge.text.length - 1)
78 | }
79 | }
80 |
81 | if (typeof type !== 'undefined') {
82 | edge.type = type.type
83 | edge.stroke = type.stroke
84 | }
85 | edges.push(edge)
86 | }
87 |
88 | /**
89 | * Updates a link's line interpolation algorithm
90 | * @param pos
91 | * @param interpolate
92 | */
93 | export const updateLinkInterpolate = function (pos, interp) {
94 | if (pos === 'default') {
95 | edges.defaultInterpolate = interp
96 | } else {
97 | edges[pos].interpolate = interp
98 | }
99 | }
100 |
101 | /**
102 | * Updates a link with a style
103 | * @param pos
104 | * @param style
105 | */
106 | export const updateLink = function (pos, style) {
107 | if (pos === 'default') {
108 | edges.defaultStyle = style
109 | } else {
110 | if (utils.isSubstringInArray('fill', style) === -1) {
111 | style.push('fill:none')
112 | }
113 | edges[pos].style = style
114 | }
115 | }
116 |
117 | export const addClass = function (id, style) {
118 | if (typeof classes[id] === 'undefined') {
119 | classes[id] = { id: id, styles: [] }
120 | }
121 |
122 | if (typeof style !== 'undefined') {
123 | if (style !== null) {
124 | style.forEach(function (s) {
125 | classes[id].styles.push(s)
126 | })
127 | }
128 | }
129 | }
130 |
131 | /**
132 | * Called by parser when a graph definition is found, stores the direction of the chart.
133 | * @param dir
134 | */
135 | export const setDirection = function (dir) {
136 | direction = dir
137 | }
138 |
139 | /**
140 | * Called by parser when a graph definition is found, stores the direction of the chart.
141 | * @param dir
142 | */
143 | export const setClass = function (id, className) {
144 | if (id.indexOf(',') > 0) {
145 | id.split(',').forEach(function (id2) {
146 | if (typeof vertices[id2] !== 'undefined') {
147 | vertices[id2].classes.push(className)
148 | }
149 | })
150 | } else {
151 | if (typeof vertices[id] !== 'undefined') {
152 | vertices[id].classes.push(className)
153 | }
154 | }
155 | }
156 |
157 | const setTooltip = function (id, tooltip) {
158 | if (typeof tooltip !== 'undefined') {
159 | tooltips[id] = tooltip
160 | }
161 | }
162 |
163 | const setClickFun = function (id, functionName) {
164 | if (typeof functionName === 'undefined') {
165 | return
166 | }
167 | if (typeof vertices[id] !== 'undefined') {
168 | funs.push(function (element) {
169 | const elem = d3.select(element).select(`[id="${id}"]`)
170 | if (elem !== null) {
171 | elem.on('click', function () {
172 | window[functionName](id)
173 | })
174 | }
175 | })
176 | }
177 | }
178 |
179 | const setLink = function (id, linkStr) {
180 | if (typeof linkStr === 'undefined') {
181 | return
182 | }
183 | if (typeof vertices[id] !== 'undefined') {
184 | funs.push(function (element) {
185 | const elem = d3.select(element).select(`[id="${id}"]`)
186 | if (elem !== null) {
187 | elem.on('click', function () {
188 | window.open(linkStr, 'newTab')
189 | })
190 | }
191 | })
192 | }
193 | }
194 | export const getTooltip = function (id) {
195 | return tooltips[id]
196 | }
197 |
198 | /**
199 | * Called by parser when a graph definition is found, stores the direction of the chart.
200 | * @param dir
201 | */
202 | export const setClickEvent = function (id, functionName, link, tooltip) {
203 | if (id.indexOf(',') > 0) {
204 | id.split(',').forEach(function (id2) {
205 | setTooltip(id2, tooltip)
206 | setClickFun(id2, functionName)
207 | setLink(id2, link)
208 | setClass(id, 'clickable')
209 | })
210 | } else {
211 | setTooltip(id, tooltip)
212 | setClickFun(id, functionName)
213 | setLink(id, link)
214 | setClass(id, 'clickable')
215 | }
216 | }
217 |
218 | export const bindFunctions = function (element) {
219 | funs.forEach(function (fun) {
220 | fun(element)
221 | })
222 | }
223 | export const getDirection = function () {
224 | return direction
225 | }
226 | /**
227 | * Retrieval function for fetching the found nodes after parsing has completed.
228 | * @returns {{}|*|vertices}
229 | */
230 | export const getVertices = function () {
231 | return vertices
232 | }
233 |
234 | /**
235 | * Retrieval function for fetching the found links after parsing has completed.
236 | * @returns {{}|*|edges}
237 | */
238 | export const getEdges = function () {
239 | return edges
240 | }
241 |
242 | /**
243 | * Retrieval function for fetching the found class definitions after parsing has completed.
244 | * @returns {{}|*|classes}
245 | */
246 | export const getClasses = function () {
247 | return classes
248 | }
249 |
250 | const setupToolTips = function (element) {
251 | let tooltipElem = d3.select('.mermaidTooltip')
252 | if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
253 | tooltipElem = d3.select('body')
254 | .append('div')
255 | .attr('class', 'mermaidTooltip')
256 | .style('opacity', 0)
257 | }
258 |
259 | const svg = d3.select(element).select('svg')
260 |
261 | const nodes = svg.selectAll('g.node')
262 | nodes
263 | .on('mouseover', function () {
264 | const el = d3.select(this)
265 | const title = el.attr('title')
266 | // Dont try to draw a tooltip if no data is provided
267 | if (title === null) {
268 | return
269 | }
270 | const rect = this.getBoundingClientRect()
271 |
272 | tooltipElem.transition()
273 | .duration(200)
274 | .style('opacity', '.9')
275 | tooltipElem.html(el.attr('title'))
276 | .style('left', (rect.left + (rect.right - rect.left) / 2) + 'px')
277 | .style('top', (rect.top - 14 + document.body.scrollTop) + 'px')
278 | el.classed('hover', true)
279 | })
280 | .on('mouseout', function () {
281 | tooltipElem.transition()
282 | .duration(500)
283 | .style('opacity', 0)
284 | const el = d3.select(this)
285 | el.classed('hover', false)
286 | })
287 | }
288 | funs.push(setupToolTips)
289 |
290 | /**
291 | * Clears the internal graph db so that a new graph can be parsed.
292 | */
293 | export const clear = function () {
294 | vertices = {}
295 | classes = {}
296 | edges = []
297 | funs = []
298 | funs.push(setupToolTips)
299 | subGraphs = []
300 | subCount = 0
301 | tooltips = []
302 | }
303 | /**
304 | *
305 | * @returns {string}
306 | */
307 | export const defaultStyle = function () {
308 | return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;'
309 | }
310 |
311 | /**
312 | * Clears the internal graph db so that a new graph can be parsed.
313 | */
314 | export const addSubGraph = function (list, title) {
315 | function uniq (a) {
316 | const prims = { 'boolean': {}, 'number': {}, 'string': {} }
317 | const objs = []
318 |
319 | return a.filter(function (item) {
320 | const type = typeof item
321 | if (item.trim() === '') {
322 | return false
323 | }
324 | if (type in prims) { return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true) } else { return objs.indexOf(item) >= 0 ? false : objs.push(item) }
325 | })
326 | }
327 |
328 | let nodeList = []
329 |
330 | nodeList = uniq(nodeList.concat.apply(nodeList, list))
331 |
332 | const subGraph = { id: 'subGraph' + subCount, nodes: nodeList, title: title.trim() }
333 | subGraphs.push(subGraph)
334 | subCount = subCount + 1
335 | return subGraph.id
336 | }
337 |
338 | const getPosForId = function (id) {
339 | for (let i = 0; i < subGraphs.length; i++) {
340 | if (subGraphs[i].id === id) {
341 | return i
342 | }
343 | }
344 | return -1
345 | }
346 | let secCount = -1
347 | const posCrossRef = []
348 | const indexNodes2 = function (id, pos) {
349 | const nodes = subGraphs[pos].nodes
350 | secCount = secCount + 1
351 | if (secCount > 2000) {
352 | return
353 | }
354 | posCrossRef[secCount] = pos
355 | // Check if match
356 | if (subGraphs[pos].id === id) {
357 | return {
358 | result: true,
359 | count: 0
360 | }
361 | }
362 |
363 | let count = 0
364 | let posCount = 1
365 | while (count < nodes.length) {
366 | const childPos = getPosForId(nodes[count])
367 | // Ignore regular nodes (pos will be -1)
368 | if (childPos >= 0) {
369 | const res = indexNodes2(id, childPos)
370 | if (res.result) {
371 | return {
372 | result: true,
373 | count: posCount + res.count
374 | }
375 | } else {
376 | posCount = posCount + res.count
377 | }
378 | }
379 | count = count + 1
380 | }
381 |
382 | return {
383 | result: false,
384 | count: posCount
385 | }
386 | }
387 |
388 | export const getDepthFirstPos = function (pos) {
389 | return posCrossRef[pos]
390 | }
391 | export const indexNodes = function () {
392 | secCount = -1
393 | if (subGraphs.length > 0) {
394 | indexNodes2('none', subGraphs.length - 1, 0)
395 | }
396 | }
397 |
398 | export const getSubGraphs = function () {
399 | return subGraphs
400 | }
401 |
402 | export default {
403 | addVertex,
404 | addLink,
405 | updateLinkInterpolate,
406 | updateLink,
407 | addClass,
408 | setDirection,
409 | setClass,
410 | getTooltip,
411 | setClickEvent,
412 | bindFunctions,
413 | getDirection,
414 | getVertices,
415 | getEdges,
416 | getClasses,
417 | clear,
418 | defaultStyle,
419 | addSubGraph,
420 | getDepthFirstPos,
421 | indexNodes,
422 | getSubGraphs
423 | }
424 |
--------------------------------------------------------------------------------
/assets/diagrams/class/classRenderer.js:
--------------------------------------------------------------------------------
1 | import dagre from 'dagre-layout'
2 | import graphlib from 'graphlibrary'
3 | import * as d3 from 'd3'
4 |
5 | import classDb from './classDb'
6 | import { logger } from '../../logger'
7 | import { parser } from './parser/classDiagram'
8 |
9 | parser.yy = classDb
10 |
11 | const idCache = {}
12 |
13 | let classCnt = 0
14 | const conf = {
15 | dividerMargin: 10,
16 | padding: 5,
17 | textHeight: 10
18 | }
19 |
20 | // Todo optimize
21 | const getGraphId = function (label) {
22 | const keys = Object.keys(idCache)
23 |
24 | for (let i = 0; i < keys.length; i++) {
25 | if (idCache[keys[i]].label === label) {
26 | return keys[i]
27 | }
28 | }
29 |
30 | return undefined
31 | }
32 |
33 | /**
34 | * Setup arrow head and define the marker. The result is appended to the svg.
35 | */
36 | const insertMarkers = function (elem) {
37 | elem.append('defs').append('marker')
38 | .attr('id', 'extensionStart')
39 | .attr('class', 'extension')
40 | .attr('refX', 0)
41 | .attr('refY', 7)
42 | .attr('markerWidth', 190)
43 | .attr('markerHeight', 240)
44 | .attr('orient', 'auto')
45 | .append('path')
46 | .attr('d', 'M 1,7 L18,13 V 1 Z')
47 |
48 | elem.append('defs').append('marker')
49 | .attr('id', 'extensionEnd')
50 | .attr('refX', 19)
51 | .attr('refY', 7)
52 | .attr('markerWidth', 20)
53 | .attr('markerHeight', 28)
54 | .attr('orient', 'auto')
55 | .append('path')
56 | .attr('d', 'M 1,1 V 13 L18,7 Z') // this is actual shape for arrowhead
57 |
58 | elem.append('defs').append('marker')
59 | .attr('id', 'compositionStart')
60 | .attr('class', 'extension')
61 | .attr('refX', 0)
62 | .attr('refY', 7)
63 | .attr('markerWidth', 190)
64 | .attr('markerHeight', 240)
65 | .attr('orient', 'auto')
66 | .append('path')
67 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
68 |
69 | elem.append('defs').append('marker')
70 | .attr('id', 'compositionEnd')
71 | .attr('refX', 19)
72 | .attr('refY', 7)
73 | .attr('markerWidth', 20)
74 | .attr('markerHeight', 28)
75 | .attr('orient', 'auto')
76 | .append('path')
77 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
78 |
79 | elem.append('defs').append('marker')
80 | .attr('id', 'aggregationStart')
81 | .attr('class', 'extension')
82 | .attr('refX', 0)
83 | .attr('refY', 7)
84 | .attr('markerWidth', 190)
85 | .attr('markerHeight', 240)
86 | .attr('orient', 'auto')
87 | .append('path')
88 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
89 |
90 | elem.append('defs').append('marker')
91 | .attr('id', 'aggregationEnd')
92 | .attr('refX', 19)
93 | .attr('refY', 7)
94 | .attr('markerWidth', 20)
95 | .attr('markerHeight', 28)
96 | .attr('orient', 'auto')
97 | .append('path')
98 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
99 |
100 | elem.append('defs').append('marker')
101 | .attr('id', 'dependencyStart')
102 | .attr('class', 'extension')
103 | .attr('refX', 0)
104 | .attr('refY', 7)
105 | .attr('markerWidth', 190)
106 | .attr('markerHeight', 240)
107 | .attr('orient', 'auto')
108 | .append('path')
109 | .attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z')
110 |
111 | elem.append('defs').append('marker')
112 | .attr('id', 'dependencyEnd')
113 | .attr('refX', 19)
114 | .attr('refY', 7)
115 | .attr('markerWidth', 20)
116 | .attr('markerHeight', 28)
117 | .attr('orient', 'auto')
118 | .append('path')
119 | .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z')
120 | }
121 |
122 | let edgeCount = 0
123 | const drawEdge = function (elem, path, relation) {
124 | const getRelationType = function (type) {
125 | switch (type) {
126 | case classDb.relationType.AGGREGATION:
127 | return 'aggregation'
128 | case classDb.relationType.EXTENSION:
129 | return 'extension'
130 | case classDb.relationType.COMPOSITION:
131 | return 'composition'
132 | case classDb.relationType.DEPENDENCY:
133 | return 'dependency'
134 | }
135 | }
136 |
137 | // The data for our line
138 | const lineData = path.points
139 |
140 | // This is the accessor function we talked about above
141 | const lineFunction = d3.line()
142 | .x(function (d) {
143 | return d.x
144 | })
145 | .y(function (d) {
146 | return d.y
147 | })
148 | .curve(d3.curveBasis)
149 |
150 | const svgPath = elem.append('path')
151 | .attr('d', lineFunction(lineData))
152 | .attr('id', 'edge' + edgeCount)
153 | .attr('class', 'relation')
154 | let url = ''
155 | if (conf.arrowMarkerAbsolute) {
156 | url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
157 | url = url.replace(/\(/g, '\\(')
158 | url = url.replace(/\)/g, '\\)')
159 | }
160 |
161 | if (relation.relation.type1 !== 'none') {
162 | svgPath.attr('marker-start', 'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')')
163 | }
164 | if (relation.relation.type2 !== 'none') {
165 | svgPath.attr('marker-end', 'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')')
166 | }
167 |
168 | let x, y
169 | const l = path.points.length
170 | if ((l % 2) !== 0) {
171 | const p1 = path.points[Math.floor(l / 2)]
172 | const p2 = path.points[Math.ceil(l / 2)]
173 | x = (p1.x + p2.x) / 2
174 | y = (p1.y + p2.y) / 2
175 | } else {
176 | const p = path.points[Math.floor(l / 2)]
177 | x = p.x
178 | y = p.y
179 | }
180 |
181 | if (typeof relation.title !== 'undefined') {
182 | const g = elem.append('g')
183 | .attr('class', 'classLabel')
184 | const label = g.append('text')
185 | .attr('class', 'label')
186 | .attr('x', x)
187 | .attr('y', y)
188 | .attr('fill', 'red')
189 | .attr('text-anchor', 'middle')
190 | .text(relation.title)
191 |
192 | window.label = label
193 | const bounds = label.node().getBBox()
194 |
195 | g.insert('rect', ':first-child')
196 | .attr('class', 'box')
197 | .attr('x', bounds.x - conf.padding / 2)
198 | .attr('y', bounds.y - conf.padding / 2)
199 | .attr('width', bounds.width + conf.padding)
200 | .attr('height', bounds.height + conf.padding)
201 | }
202 |
203 | edgeCount++
204 | }
205 |
206 | const drawClass = function (elem, classDef) {
207 | logger.info('Rendering class ' + classDef)
208 |
209 | const addTspan = function (textEl, txt, isFirst) {
210 | const tSpan = textEl.append('tspan')
211 | .attr('x', conf.padding)
212 | .text(txt)
213 | if (!isFirst) {
214 | tSpan.attr('dy', conf.textHeight)
215 | }
216 | }
217 |
218 | const id = 'classId' + classCnt
219 | const classInfo = {
220 | id: id,
221 | label: classDef.id,
222 | width: 0,
223 | height: 0
224 | }
225 |
226 | const g = elem.append('g')
227 | .attr('id', id)
228 | .attr('class', 'classGroup')
229 | const title = g.append('text')
230 | .attr('x', conf.padding)
231 | .attr('y', conf.textHeight + conf.padding)
232 | .text(classDef.id)
233 |
234 | const titleHeight = title.node().getBBox().height
235 |
236 | const membersLine = g.append('line') // text label for the x axis
237 | .attr('x1', 0)
238 | .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
239 | .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2)
240 |
241 | const members = g.append('text') // text label for the x axis
242 | .attr('x', conf.padding)
243 | .attr('y', titleHeight + (conf.dividerMargin) + conf.textHeight)
244 | .attr('fill', 'white')
245 | .attr('class', 'classText')
246 |
247 | let isFirst = true
248 | classDef.members.forEach(function (member) {
249 | addTspan(members, member, isFirst)
250 | isFirst = false
251 | })
252 |
253 | const membersBox = members.node().getBBox()
254 |
255 | const methodsLine = g.append('line') // text label for the x axis
256 | .attr('x1', 0)
257 | .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
258 | .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
259 |
260 | const methods = g.append('text') // text label for the x axis
261 | .attr('x', conf.padding)
262 | .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
263 | .attr('fill', 'white')
264 | .attr('class', 'classText')
265 |
266 | isFirst = true
267 |
268 | classDef.methods.forEach(function (method) {
269 | addTspan(methods, method, isFirst)
270 | isFirst = false
271 | })
272 |
273 | const classBox = g.node().getBBox()
274 | g.insert('rect', ':first-child')
275 | .attr('x', 0)
276 | .attr('y', 0)
277 | .attr('width', classBox.width + 2 * conf.padding)
278 | .attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin)
279 |
280 | membersLine.attr('x2', classBox.width + 2 * conf.padding)
281 | methodsLine.attr('x2', classBox.width + 2 * conf.padding)
282 |
283 | classInfo.width = classBox.width + 2 * conf.padding
284 | classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin
285 |
286 | idCache[id] = classInfo
287 | classCnt++
288 | return classInfo
289 | }
290 |
291 | export const setConf = function (cnf) {
292 | const keys = Object.keys(cnf)
293 |
294 | keys.forEach(function (key) {
295 | conf[key] = cnf[key]
296 | })
297 | }
298 | /**
299 | * Draws a flowchart in the tag with id: id based on the graph definition in text.
300 | * @param text
301 | * @param id
302 | */
303 | export const draw = function (text, id) {
304 | parser.yy.clear()
305 | parser.parse(text)
306 |
307 | logger.info('Rendering diagram ' + text)
308 |
309 | /// / Fetch the default direction, use TD if none was found
310 | const diagram = d3.select(`[id="${id}"]`)
311 | insertMarkers(diagram)
312 |
313 | // Layout graph, Create a new directed graph
314 | const g = new graphlib.Graph({
315 | multigraph: true
316 | })
317 |
318 | // Set an object for the graph label
319 | g.setGraph({
320 | isMultiGraph: true
321 | })
322 |
323 | // Default to assigning a new object as a label for each new edge.
324 | g.setDefaultEdgeLabel(function () {
325 | return {}
326 | })
327 |
328 | const classes = classDb.getClasses()
329 | const keys = Object.keys(classes)
330 | for (let i = 0; i < keys.length; i++) {
331 | const classDef = classes[keys[i]]
332 | const node = drawClass(diagram, classDef)
333 | // Add nodes to the graph. The first argument is the node id. The second is
334 | // metadata about the node. In this case we're going to add labels to each of
335 | // our nodes.
336 | g.setNode(node.id, node)
337 | logger.info('Org height: ' + node.height)
338 | }
339 |
340 | const relations = classDb.getRelations()
341 | relations.forEach(function (relation) {
342 | logger.info('tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation))
343 | g.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), { relation: relation })
344 | })
345 | dagre.layout(g)
346 | g.nodes().forEach(function (v) {
347 | if (typeof v !== 'undefined') {
348 | logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)))
349 | d3.select('#' + v).attr('transform', 'translate(' + (g.node(v).x - (g.node(v).width / 2)) + ',' + (g.node(v).y - (g.node(v).height / 2)) + ' )')
350 | }
351 | })
352 | g.edges().forEach(function (e) {
353 | logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e)))
354 | drawEdge(diagram, g.edge(e), g.edge(e).relation)
355 | })
356 |
357 | diagram.attr('height', '100%')
358 | diagram.attr('width', '100%')
359 | diagram.attr('viewBox', '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20))
360 | }
361 |
362 | export default {
363 | setConf,
364 | draw
365 | }
366 |
--------------------------------------------------------------------------------
/assets/mermaidAPI.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ---
3 | * title: mermaidAPI
4 | * order: 5
5 | * ---
6 | * # mermaidAPI
7 | * This is the api to be used when handling the integration with the web page instead of using the default integration
8 | * (mermaid.js).
9 | *
10 | * The core of this api is the **render** function that given a graph definitionas text renders the graph/diagram and
11 | * returns a svg element for the graph. It is is then up to the user of the API to make use of the svg, either insert it
12 | * somewhere in the page or something completely different.
13 | */
14 | import * as d3 from 'd3'
15 | import scope from 'scope-css'
16 |
17 | import { logger, setLogLevel } from './logger'
18 | import utils from './utils'
19 | import flowRenderer from './diagrams/flowchart/flowRenderer'
20 | import flowParser from './diagrams/flowchart/parser/flow'
21 | import flowDb from './diagrams/flowchart/flowDb'
22 | import sequenceRenderer from './diagrams/sequence/sequenceRenderer'
23 | import sequenceParser from './diagrams/sequence/parser/sequenceDiagram'
24 | import sequenceDb from './diagrams/sequence/sequenceDb'
25 | import ganttRenderer from './diagrams/gantt/ganttRenderer'
26 | import ganttParser from './diagrams/gantt/parser/gantt'
27 | import ganttDb from './diagrams/gantt/ganttDb'
28 | import classRenderer from './diagrams/class/classRenderer'
29 | import classParser from './diagrams/class/parser/classDiagram'
30 | import classDb from './diagrams/class/classDb'
31 | import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
32 | import gitGraphParser from './diagrams/git/parser/gitGraph'
33 | import gitGraphAst from './diagrams/git/gitGraphAst'
34 |
35 | const themes = {}
36 | for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
37 | themes[themeName] = require(`./themes/${themeName}/index.scss`)
38 | }
39 |
40 | /**
41 | * ## Configuration
42 | * These are the default options which can be overridden with the initialization call as in the example below:
43 | * ```
44 | * mermaid.initialize({
45 | * flowchart:{
46 | * htmlLabels: false
47 | * }
48 | * });
49 | * ```
50 | */
51 | const config = {
52 | theme: 'default',
53 | themeCSS: undefined,
54 |
55 | /**
56 | * logLevel , decides the amount of logging to be used.
57 | * * debug: 1
58 | * * info: 2
59 | * * warn: 3
60 | * * error: 4
61 | * * fatal: 5
62 | */
63 | logLevel: 5,
64 |
65 | /**
66 | * **startOnLoad** - This options controls whether or mermaid starts when the page loads
67 | */
68 | startOnLoad: true,
69 |
70 | /**
71 | * **arrowMarkerAbsolute** - This options controls whether or arrow markers in html code will be absolute paths or
72 | * an anchor, #. This matters if you are using base tag settings.
73 | */
74 | arrowMarkerAbsolute: false,
75 |
76 | /**
77 | * ### flowchart
78 | * *The object containing configurations specific for flowcharts*
79 | */
80 | flowchart: {
81 | /**
82 | * **htmlLabels** - Flag for setting whether or not a html tag should be used for rendering labels
83 | * on the edges
84 | */
85 | htmlLabels: true,
86 |
87 | curve: 'linear'
88 | },
89 |
90 | /**
91 | * ### sequenceDiagram
92 | * The object containing configurations specific for sequence diagrams
93 | */
94 | sequence: {
95 |
96 | /**
97 | * **diagramMarginX** - margin to the right and left of the sequence diagram
98 | */
99 | diagramMarginX: 50,
100 |
101 | /**
102 | * **diagramMarginY** - margin to the over and under the sequence diagram
103 | */
104 | diagramMarginY: 10,
105 |
106 | /**
107 | * **actorMargin** - Margin between actors
108 | */
109 | actorMargin: 50,
110 |
111 | /**
112 | * **width** - Width of actor boxes
113 | */
114 | width: 150,
115 |
116 | /**
117 | * **height** - Height of actor boxes
118 | */
119 | height: 65,
120 |
121 | /**
122 | * **boxMargin** - Margin around loop boxes
123 | */
124 | boxMargin: 10,
125 |
126 | /**
127 | * **boxTextMargin** - margin around the text in loop/alt/opt boxes
128 | */
129 | boxTextMargin: 5,
130 |
131 | /**
132 | * **noteMargin** - margin around notes
133 | */
134 | noteMargin: 10,
135 |
136 | /**
137 | * **messageMargin** - Space between messages
138 | */
139 | messageMargin: 35,
140 |
141 | /**
142 | * **mirrorActors** - mirror actors under diagram
143 | */
144 | mirrorActors: true,
145 |
146 | /**
147 | * **bottomMarginAdj** - Depending on css styling this might need adjustment.
148 | * Prolongs the edge of the diagram downwards
149 | */
150 | bottomMarginAdj: 1,
151 |
152 | /**
153 | * **useMaxWidth** - when this flag is set the height and width is set to 100% and is then scaling with the
154 | * available space if not the absolute space required is used
155 | */
156 | useMaxWidth: true
157 | },
158 |
159 | /** ### gantt
160 | * The object containing configurations specific for gantt diagrams*
161 | */
162 | gantt: {
163 | /**
164 | * **titleTopMargin** - margin top for the text over the gantt diagram
165 | */
166 | titleTopMargin: 25,
167 |
168 | /**
169 | * **barHeight** - the height of the bars in the graph
170 | */
171 | barHeight: 20,
172 |
173 | /**
174 | * **barGap** - the margin between the different activities in the gantt diagram
175 | */
176 | barGap: 4,
177 |
178 | /**
179 | * **topPadding** - margin between title and gantt diagram and between axis and gantt diagram.
180 | */
181 | topPadding: 50,
182 |
183 | /**
184 | * **leftPadding** - the space allocated for the section name to the left of the activities.
185 | */
186 | leftPadding: 75,
187 |
188 | /**
189 | * **gridLineStartPadding** - Vertical starting position of the grid lines
190 | */
191 | gridLineStartPadding: 35,
192 |
193 | /**
194 | * **fontSize** - font size ...
195 | */
196 | fontSize: 11,
197 |
198 | /**
199 | * **fontFamily** - font family ...
200 | */
201 | fontFamily: '"Open-Sans", "sans-serif"',
202 |
203 | /**
204 | * **numberSectionStyles** - the number of alternating section styles
205 | */
206 | numberSectionStyles: 4,
207 |
208 | /**
209 | * **axisFormat** - datetime format of the axis, this might need adjustment to match your locale and preferences
210 | */
211 | axisFormat: '%Y-%m-%d'
212 | },
213 | class: {},
214 | git: {}
215 | }
216 |
217 | setLogLevel(config.logLevel)
218 |
219 | function parse (text) {
220 | const graphType = utils.detectType(text)
221 | let parser
222 |
223 | switch (graphType) {
224 | case 'git':
225 | parser = gitGraphParser
226 | parser.parser.yy = gitGraphAst
227 | break
228 | case 'flowchart':
229 | parser = flowParser
230 | parser.parser.yy = flowDb
231 | break
232 | case 'sequence':
233 | parser = sequenceParser
234 | parser.parser.yy = sequenceDb
235 | break
236 | case 'gantt':
237 | parser = ganttParser
238 | parser.parser.yy = ganttDb
239 | break
240 | case 'class':
241 | parser = classParser
242 | parser.parser.yy = classDb
243 | break
244 | }
245 |
246 | parser.parser.yy.parseError = (str, hash) => {
247 | const error = { str, hash }
248 | throw error
249 | }
250 |
251 | parser.parse(text)
252 | }
253 |
254 | export const encodeEntities = function (text) {
255 | let txt = text
256 |
257 | txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
258 | const innerTxt = s.substring(0, s.length - 1)
259 | return innerTxt
260 | })
261 | txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
262 | const innerTxt = s.substring(0, s.length - 1)
263 | return innerTxt
264 | })
265 |
266 | txt = txt.replace(/#\w+;/g, function (s) {
267 | const innerTxt = s.substring(1, s.length - 1)
268 |
269 | const isInt = /^\+?\d+$/.test(innerTxt)
270 | if (isInt) {
271 | return 'fl°°' + innerTxt + '¶ß'
272 | } else {
273 | return 'fl°' + innerTxt + '¶ß'
274 | }
275 | })
276 |
277 | return txt
278 | }
279 |
280 | export const decodeEntities = function (text) {
281 | let txt = text
282 |
283 | txt = txt.replace(/fl°°/g, function () {
284 | return ''
285 | })
286 | txt = txt.replace(/fl°/g, function () {
287 | return '&'
288 | })
289 | txt = txt.replace(/¶ß/g, function () {
290 | return ';'
291 | })
292 |
293 | return txt
294 | }
295 | /**
296 | * ##render
297 | * Function that renders an svg with a graph from a chart definition. Usage example below.
298 | *
299 | * ```
300 | * mermaidAPI.initialize({
301 | * startOnLoad:true
302 | * });
303 | * $(function(){
304 | * const graphDefinition = 'graph TB\na-->b';
305 | * const cb = function(svgGraph){
306 | * console.log(svgGraph);
307 | * };
308 | * mermaidAPI.render('id1',graphDefinition,cb);
309 | * });
310 | *```
311 | * @param id the id of the element to be rendered
312 | * @param txt the graph definition
313 | * @param cb callback which is called after rendering is finished with the svg code as inparam.
314 | * @param container selector to element in which a div with the graph temporarily will be inserted. In one is
315 | * provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
316 | * completed.
317 | */
318 | const render = function (id, txt, cb, container) {
319 | if (typeof container !== 'undefined') {
320 | container.innerHTML = ''
321 |
322 | d3.select(container).append('div')
323 | .attr('id', 'd' + id)
324 | .append('svg')
325 | .attr('id', id)
326 | .attr('width', '100%')
327 | .attr('xmlns', 'http://www.w3.org/2000/svg')
328 | .append('g')
329 | } else {
330 | const element = document.querySelector('#' + 'd' + id)
331 | if (element) {
332 | element.innerHTML = ''
333 | }
334 |
335 | d3.select('body').append('div')
336 | .attr('id', 'd' + id)
337 | .append('svg')
338 | .attr('id', id)
339 | .attr('width', '100%')
340 | .attr('xmlns', 'http://www.w3.org/2000/svg')
341 | .append('g')
342 | }
343 |
344 | window.txt = txt
345 | txt = encodeEntities(txt)
346 |
347 | const element = d3.select('#d' + id).node()
348 | const graphType = utils.detectType(txt)
349 |
350 | // insert inline style into svg
351 | const svg = element.firstChild
352 | const firstChild = svg.firstChild
353 |
354 | // pre-defined theme
355 | let style = themes[config.theme]
356 | if (style === undefined) {
357 | style = ''
358 | }
359 |
360 | // user provided theme CSS
361 | if (config.themeCSS !== undefined) {
362 | style += `\n${config.themeCSS}`
363 | }
364 |
365 | // classDef
366 | if (graphType === 'flowchart') {
367 | const classes = flowRenderer.getClasses(txt)
368 | for (const className in classes) {
369 | style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }`
370 | }
371 | }
372 |
373 | const style1 = document.createElement('style')
374 | style1.innerHTML = scope(style, `#${id}`)
375 | svg.insertBefore(style1, firstChild)
376 |
377 | const style2 = document.createElement('style')
378 | const cs = window.getComputedStyle(svg)
379 | style2.innerHTML = `#${id} {
380 | color: ${cs.color};
381 | font: ${cs.font};
382 | }`
383 | svg.insertBefore(style2, firstChild)
384 |
385 | switch (graphType) {
386 | case 'git':
387 | config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
388 | gitGraphRenderer.setConf(config.git)
389 | gitGraphRenderer.draw(txt, id, false)
390 | break
391 | case 'flowchart':
392 | config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
393 | flowRenderer.setConf(config.flowchart)
394 | flowRenderer.draw(txt, id, false)
395 | break
396 | case 'sequence':
397 | config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute
398 | if (config.sequenceDiagram) { // backwards compatibility
399 | sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram))
400 | console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.')
401 | } else {
402 | sequenceRenderer.setConf(config.sequence)
403 | }
404 | sequenceRenderer.draw(txt, id)
405 | break
406 | case 'gantt':
407 | config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute
408 | ganttRenderer.setConf(config.gantt)
409 | ganttRenderer.draw(txt, id)
410 | break
411 | case 'class':
412 | config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
413 | classRenderer.setConf(config.class)
414 | classRenderer.draw(txt, id)
415 | break
416 | }
417 |
418 | d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')
419 |
420 | let url = ''
421 | if (config.arrowMarkerAbsolute) {
422 | url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
423 | url = url.replace(/\(/g, '\\(')
424 | url = url.replace(/\)/g, '\\)')
425 | }
426 |
427 | // Fix for when the base tag is used
428 | let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g')
429 |
430 | svgCode = decodeEntities(svgCode)
431 |
432 | if (typeof cb !== 'undefined') {
433 | cb(svgCode, flowDb.bindFunctions)
434 | } else {
435 | logger.warn('CB = undefined!')
436 | }
437 |
438 | const node = d3.select('#d' + id).node()
439 | if (node !== null && typeof node.remove === 'function') {
440 | d3.select('#d' + id).node().remove()
441 | }
442 |
443 | return svgCode
444 | }
445 |
446 | const setConf = function (cnf) {
447 | // Top level initially mermaid, gflow, sequenceDiagram and gantt
448 | const lvl1Keys = Object.keys(cnf)
449 | for (let i = 0; i < lvl1Keys.length; i++) {
450 | if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
451 | const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])
452 |
453 | for (let j = 0; j < lvl2Keys.length; j++) {
454 | logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
455 | if (typeof config[lvl1Keys[i]] === 'undefined') {
456 | config[lvl1Keys[i]] = {}
457 | }
458 | logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
459 | config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
460 | }
461 | } else {
462 | config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
463 | }
464 | }
465 | }
466 |
467 | function initialize (options) {
468 | logger.debug('Initializing mermaidAPI')
469 | // Update default config with options supplied at initialization
470 | if (typeof options === 'object') {
471 | setConf(options)
472 | }
473 | setLogLevel(config.logLevel)
474 | }
475 |
476 | function getConfig () {
477 | return config
478 | }
479 |
480 | const mermaidAPI = {
481 | render,
482 | parse,
483 | initialize,
484 | getConfig
485 | }
486 |
487 | export default mermaidAPI
488 |
--------------------------------------------------------------------------------
/assets/diagrams/flowchart/flowRenderer.js:
--------------------------------------------------------------------------------
1 | import graphlib from 'graphlibrary'
2 | import * as d3 from 'd3'
3 |
4 | import flowDb from './flowDb'
5 | import flow from './parser/flow'
6 | import dagreD3 from 'dagre-d3-renderer'
7 | import { logger } from '../../logger'
8 | import { interpolateToCurve } from '../../utils'
9 |
10 | const conf = {
11 | }
12 | export const setConf = function (cnf) {
13 | const keys = Object.keys(cnf)
14 | for (let i = 0; i < keys.length; i++) {
15 | conf[keys[i]] = cnf[keys[i]]
16 | }
17 | }
18 |
19 | /**
20 | * Function that adds the vertices found in the graph definition to the graph to be rendered.
21 | * @param vert Object containing the vertices.
22 | * @param g The graph that is to be drawn.
23 | */
24 | export const addVertices = function (vert, g) {
25 | const keys = Object.keys(vert)
26 |
27 | const styleFromStyleArr = function (styleStr, arr) {
28 | // Create a compound style definition from the style definitions found for the node in the graph definition
29 | for (let i = 0; i < arr.length; i++) {
30 | if (typeof arr[i] !== 'undefined') {
31 | styleStr = styleStr + arr[i] + ';'
32 | }
33 | }
34 |
35 | return styleStr
36 | }
37 |
38 | // Iterate through each item in the vertice object (containing all the vertices found) in the graph definition
39 | keys.forEach(function (id) {
40 | const vertice = vert[id]
41 | let verticeText
42 |
43 | /**
44 | * Variable for storing the classes for the vertice
45 | * @type {string}
46 | */
47 | let classStr = ''
48 | if (vertice.classes.length > 0) {
49 | classStr = vertice.classes.join(' ')
50 | }
51 |
52 | /**
53 | * Variable for storing the extracted style for the vertice
54 | * @type {string}
55 | */
56 | let style = ''
57 | // Create a compound style definition from the style definitions found for the node in the graph definition
58 | style = styleFromStyleArr(style, vertice.styles)
59 |
60 | // Use vertice id as text in the box if no text is provided by the graph definition
61 | if (typeof vertice.text === 'undefined') {
62 | verticeText = vertice.id
63 | } else {
64 | verticeText = vertice.text
65 | }
66 |
67 | let labelTypeStr = ''
68 | if (conf.htmlLabels) {
69 | labelTypeStr = 'html'
70 | verticeText = verticeText.replace(/fa:fa[\w-]+/g, function (s) {
71 | return ''
72 | })
73 | } else {
74 | const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text')
75 |
76 | const rows = verticeText.split(/
/)
77 |
78 | for (let j = 0; j < rows.length; j++) {
79 | const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
80 | tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
81 | tspan.setAttribute('dy', '1em')
82 | tspan.setAttribute('x', '1')
83 | tspan.textContent = rows[j]
84 | svgLabel.appendChild(tspan)
85 | }
86 |
87 | labelTypeStr = 'svg'
88 | verticeText = svgLabel
89 | }
90 |
91 | let radious = 0
92 | let _shape = ''
93 | // Set the shape based parameters
94 | switch (vertice.type) {
95 | case 'round':
96 | radious = 5
97 | _shape = 'rect'
98 | break
99 | case 'square':
100 | _shape = 'rect'
101 | break
102 | case 'diamond':
103 | _shape = 'question'
104 | break
105 | case 'odd':
106 | _shape = 'rect_left_inv_arrow'
107 | break
108 | case 'odd_right':
109 | _shape = 'rect_left_inv_arrow'
110 | break
111 | case 'circle':
112 | _shape = 'circle'
113 | break
114 | case 'ellipse':
115 | _shape = 'ellipse'
116 | break
117 | case 'group':
118 | _shape = 'rect'
119 | // Need to create a text node if using svg labels, see #367
120 | verticeText = conf.htmlLabels ? '' : document.createElementNS('http://www.w3.org/2000/svg', 'text')
121 | break
122 | default:
123 | _shape = 'rect'
124 | }
125 | // Add the node
126 | g.setNode(vertice.id, { labelType: labelTypeStr, shape: _shape, label: verticeText, rx: radious, ry: radious, 'class': classStr, style: style, id: vertice.id })
127 | })
128 | }
129 |
130 | /**
131 | * Add edges to graph based on parsed graph defninition
132 | * @param {Object} edges The edges to add to the graph
133 | * @param {Object} g The graph object
134 | */
135 | export const addEdges = function (edges, g) {
136 | let cnt = 0
137 |
138 | let defaultStyle
139 | if (typeof edges.defaultStyle !== 'undefined') {
140 | defaultStyle = edges.defaultStyle.toString().replace(/,/g, ';')
141 | }
142 |
143 | edges.forEach(function (edge) {
144 | cnt++
145 | const edgeData = {}
146 |
147 | // Set link type for rendering
148 | if (edge.type === 'arrow_open') {
149 | edgeData.arrowhead = 'none'
150 | } else {
151 | edgeData.arrowhead = 'normal'
152 | }
153 |
154 | let style = ''
155 | if (typeof edge.style !== 'undefined') {
156 | edge.style.forEach(function (s) {
157 | style = style + s + ';'
158 | })
159 | } else {
160 | switch (edge.stroke) {
161 | case 'normal':
162 | style = 'fill:none'
163 | if (typeof defaultStyle !== 'undefined') {
164 | style = defaultStyle
165 | }
166 | break
167 | case 'dotted':
168 | style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;'
169 | break
170 | case 'thick':
171 | style = 'stroke: #333; stroke-width: 3.5px;fill:none'
172 | break
173 | }
174 | }
175 | edgeData.style = style
176 |
177 | if (typeof edge.interpolate !== 'undefined') {
178 | edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear)
179 | } else if (typeof edges.defaultInterpolate !== 'undefined') {
180 | edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear)
181 | } else {
182 | edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear)
183 | }
184 |
185 | if (typeof edge.text === 'undefined') {
186 | if (typeof edge.style !== 'undefined') {
187 | edgeData.arrowheadStyle = 'fill: #333'
188 | }
189 | } else {
190 | edgeData.arrowheadStyle = 'fill: #333'
191 | if (typeof edge.style === 'undefined') {
192 | edgeData.labelpos = 'c'
193 | if (conf.htmlLabels) {
194 | edgeData.labelType = 'html'
195 | edgeData.label = '' + edge.text + ''
196 | } else {
197 | edgeData.labelType = 'text'
198 | edgeData.style = 'stroke: #333; stroke-width: 1.5px;fill:none'
199 | edgeData.label = edge.text.replace(/
/g, '\n')
200 | }
201 | } else {
202 | edgeData.label = edge.text.replace(/
/g, '\n')
203 | }
204 | }
205 | // Add the edge to the graph
206 | g.setEdge(edge.start, edge.end, edgeData, cnt)
207 | })
208 | }
209 |
210 | /**
211 | * Returns the all the styles from classDef statements in the graph definition.
212 | * @returns {object} classDef styles
213 | */
214 | export const getClasses = function (text) {
215 | flowDb.clear()
216 | const parser = flow.parser
217 | parser.yy = flowDb
218 |
219 | // Parse the graph definition
220 | parser.parse(text)
221 | return flowDb.getClasses()
222 | }
223 |
224 | /**
225 | * Draws a flowchart in the tag with id: id based on the graph definition in text.
226 | * @param text
227 | * @param id
228 | */
229 | export const draw = function (text, id) {
230 | logger.debug('Drawing flowchart')
231 | flowDb.clear()
232 | const parser = flow.parser
233 | parser.yy = flowDb
234 |
235 | // Parse the graph definition
236 | try {
237 | parser.parse(text)
238 | } catch (err) {
239 | logger.debug('Parsing failed')
240 | }
241 |
242 | // Fetch the default direction, use TD if none was found
243 | let dir = flowDb.getDirection()
244 | if (typeof dir === 'undefined') {
245 | dir = 'TD'
246 | }
247 |
248 | // Create the input mermaid.graph
249 | const g = new graphlib.Graph({
250 | multigraph: true,
251 | compound: true
252 | })
253 | .setGraph({
254 | rankdir: dir,
255 | marginx: 20,
256 | marginy: 20
257 |
258 | })
259 | .setDefaultEdgeLabel(function () {
260 | return {}
261 | })
262 |
263 | let subG
264 | const subGraphs = flowDb.getSubGraphs()
265 | for (let i = subGraphs.length - 1; i >= 0; i--) {
266 | subG = subGraphs[i]
267 | flowDb.addVertex(subG.id, subG.title, 'group', undefined)
268 | }
269 |
270 | // Fetch the verices/nodes and edges/links from the parsed graph definition
271 | const vert = flowDb.getVertices()
272 |
273 | const edges = flowDb.getEdges()
274 |
275 | let i = 0
276 | for (i = subGraphs.length - 1; i >= 0; i--) {
277 | subG = subGraphs[i]
278 |
279 | d3.selectAll('cluster').append('text')
280 |
281 | for (let j = 0; j < subG.nodes.length; j++) {
282 | g.setParent(subG.nodes[j], subG.id)
283 | }
284 | }
285 | addVertices(vert, g)
286 | addEdges(edges, g)
287 |
288 | // Create the renderer
289 | const Render = dagreD3.render
290 | const render = new Render()
291 |
292 | // Add custom shape for rhombus type of boc (decision)
293 | render.shapes().question = function (parent, bbox, node) {
294 | const w = bbox.width
295 | const h = bbox.height
296 | const s = (w + h) * 0.9
297 | const points = [
298 | { x: s / 2, y: 0 },
299 | { x: s, y: -s / 2 },
300 | { x: s / 2, y: -s },
301 | { x: 0, y: -s / 2 }
302 | ]
303 | const shapeSvg = parent.insert('polygon', ':first-child')
304 | .attr('points', points.map(function (d) {
305 | return d.x + ',' + d.y
306 | }).join(' '))
307 | .attr('rx', 5)
308 | .attr('ry', 5)
309 | .attr('transform', 'translate(' + (-s / 2) + ',' + (s * 2 / 4) + ')')
310 | node.intersect = function (point) {
311 | return dagreD3.intersect.polygon(node, points, point)
312 | }
313 | return shapeSvg
314 | }
315 |
316 | // Add custom shape for box with inverted arrow on left side
317 | render.shapes().rect_left_inv_arrow = function (parent, bbox, node) {
318 | const w = bbox.width
319 | const h = bbox.height
320 | const points = [
321 | { x: -h / 2, y: 0 },
322 | { x: w, y: 0 },
323 | { x: w, y: -h },
324 | { x: -h / 2, y: -h },
325 | { x: 0, y: -h / 2 }
326 | ]
327 | const shapeSvg = parent.insert('polygon', ':first-child')
328 | .attr('points', points.map(function (d) {
329 | return d.x + ',' + d.y
330 | }).join(' '))
331 | .attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
332 | node.intersect = function (point) {
333 | return dagreD3.intersect.polygon(node, points, point)
334 | }
335 | return shapeSvg
336 | }
337 |
338 | // Add custom shape for box with inverted arrow on right side
339 | render.shapes().rect_right_inv_arrow = function (parent, bbox, node) {
340 | const w = bbox.width
341 | const h = bbox.height
342 | const points = [
343 | { x: 0, y: 0 },
344 | { x: w + h / 2, y: 0 },
345 | { x: w, y: -h / 2 },
346 | { x: w + h / 2, y: -h },
347 | { x: 0, y: -h }
348 | ]
349 | const shapeSvg = parent.insert('polygon', ':first-child')
350 | .attr('points', points.map(function (d) {
351 | return d.x + ',' + d.y
352 | }).join(' '))
353 | .attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
354 | node.intersect = function (point) {
355 | return dagreD3.intersect.polygon(node, points, point)
356 | }
357 | return shapeSvg
358 | }
359 |
360 | // Add our custom arrow - an empty arrowhead
361 | render.arrows().none = function normal (parent, id, edge, type) {
362 | const marker = parent.append('marker')
363 | .attr('id', id)
364 | .attr('viewBox', '0 0 10 10')
365 | .attr('refX', 9)
366 | .attr('refY', 5)
367 | .attr('markerUnits', 'strokeWidth')
368 | .attr('markerWidth', 8)
369 | .attr('markerHeight', 6)
370 | .attr('orient', 'auto')
371 |
372 | const path = marker.append('path')
373 | .attr('d', 'M 0 0 L 0 0 L 0 0 z')
374 | dagreD3.util.applyStyle(path, edge[type + 'Style'])
375 | }
376 |
377 | // Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
378 | render.arrows().normal = function normal (parent, id, edge, type) {
379 | const marker = parent.append('marker')
380 | .attr('id', id)
381 | .attr('viewBox', '0 0 10 10')
382 | .attr('refX', 9)
383 | .attr('refY', 5)
384 | .attr('markerUnits', 'strokeWidth')
385 | .attr('markerWidth', 8)
386 | .attr('markerHeight', 6)
387 | .attr('orient', 'auto')
388 |
389 | marker.append('path')
390 | .attr('d', 'M 0 0 L 10 5 L 0 10 z')
391 | .attr('class', 'arrowheadPath')
392 | .style('stroke-width', 1)
393 | .style('stroke-dasharray', '1,0')
394 | }
395 |
396 | // Set up an SVG group so that we can translate the final graph.
397 | const svg = d3.select(`[id="${id}"]`)
398 |
399 | // Run the renderer. This is what draws the final graph.
400 | const element = d3.select('#' + id + ' g')
401 | render(element, g)
402 |
403 | element.selectAll('g.node')
404 | .attr('title', function () {
405 | return flowDb.getTooltip(this.id)
406 | })
407 |
408 | const padding = 8
409 | const width = g.maxX - g.minX + padding * 2
410 | const height = g.maxY - g.minY + padding * 2
411 | svg.attr('width', '100%')
412 | svg.attr('style', `max-width: ${width}px;`)
413 | svg.attr('viewBox', `0 0 ${width} ${height}`)
414 | svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`)
415 |
416 | // Index nodes
417 | flowDb.indexNodes('subGraph' + i)
418 |
419 | for (i = 0; i < subGraphs.length; i++) {
420 | subG = subGraphs[i]
421 |
422 | if (subG.title !== 'undefined') {
423 | const clusterRects = document.querySelectorAll('#' + id + ' #' + subG.id + ' rect')
424 | const clusterEl = document.querySelectorAll('#' + id + ' #' + subG.id)
425 |
426 | const xPos = clusterRects[0].x.baseVal.value
427 | const yPos = clusterRects[0].y.baseVal.value
428 | const width = clusterRects[0].width.baseVal.value
429 | const cluster = d3.select(clusterEl[0])
430 | const te = cluster.append('text')
431 | te.attr('x', xPos + width / 2)
432 | te.attr('y', yPos + 14)
433 | te.attr('fill', 'black')
434 | te.attr('stroke', 'none')
435 | te.attr('id', id + 'Text')
436 | te.style('text-anchor', 'middle')
437 |
438 | if (typeof subG.title === 'undefined') {
439 | te.text('Undef')
440 | } else {
441 | te.text(subG.title)
442 | }
443 | }
444 | }
445 |
446 | // Add label rects for non html labels
447 | if (!conf.htmlLabels) {
448 | const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label')
449 | for (let k = 0; k < labels.length; k++) {
450 | const label = labels[k]
451 |
452 | // Get dimensions of label
453 | const dim = label.getBBox()
454 |
455 | const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
456 | rect.setAttribute('rx', 0)
457 | rect.setAttribute('ry', 0)
458 | rect.setAttribute('width', dim.width)
459 | rect.setAttribute('height', dim.height)
460 | rect.setAttribute('style', 'fill:#e8e8e8;')
461 |
462 | label.insertBefore(rect, label.firstChild)
463 | }
464 | }
465 | }
466 |
467 | export default {
468 | setConf,
469 | addVertices,
470 | addEdges,
471 | getClasses,
472 | draw
473 | }
474 |
--------------------------------------------------------------------------------
/assets/diagrams/flowchart/parser/flow.jison:
--------------------------------------------------------------------------------
1 | /** mermaid
2 | * https://mermaidjs.github.io/
3 | * (c) 2015 Knut Sveidqvist
4 | * MIT license.
5 | */
6 |
7 | /* lexical grammar */
8 | %lex
9 | %x string
10 |
11 | %%
12 | \%\%[^\n]* /* do nothing */
13 | ["] this.begin("string");
14 | ["] this.popState();
15 | [^"]* return "STR";
16 | "style" return 'STYLE';
17 | "default" return 'DEFAULT';
18 | "linkStyle" return 'LINKSTYLE';
19 | "interpolate" return 'INTERPOLATE';
20 | "classDef" return 'CLASSDEF';
21 | "class" return 'CLASS';
22 | "click" return 'CLICK';
23 | "graph" return 'GRAPH';
24 | "subgraph" return 'subgraph';
25 | "end"\b\s* return 'end';
26 | "LR" return 'DIR';
27 | "RL" return 'DIR';
28 | "TB" return 'DIR';
29 | "BT" return 'DIR';
30 | "TD" return 'DIR';
31 | "BR" return 'DIR';
32 | [0-9]+ return 'NUM';
33 | \# return 'BRKT';
34 | ":" return 'COLON';
35 | ";" return 'SEMI';
36 | "," return 'COMMA';
37 | "*" return 'MULT';
38 | "<" return 'TAGSTART';
39 | ">" return 'TAGEND';
40 | "^" return 'UP';
41 | "v" return 'DOWN';
42 | \s*\-\-[x]\s* return 'ARROW_CROSS';
43 | \s*\-\-\>\s* return 'ARROW_POINT';
44 | \s*\-\-[o]\s* return 'ARROW_CIRCLE';
45 | \s*\-\-\-\s* return 'ARROW_OPEN';
46 | \s*\-\.\-[x]\s* return 'DOTTED_ARROW_CROSS';
47 | \s*\-\.\-\>\s* return 'DOTTED_ARROW_POINT';
48 | \s*\-\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE';
49 | \s*\-\.\-\s* return 'DOTTED_ARROW_OPEN';
50 | \s*.\-[x]\s* return 'DOTTED_ARROW_CROSS';
51 | \s*\.\-\>\s* return 'DOTTED_ARROW_POINT';
52 | \s*\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE';
53 | \s*\.\-\s* return 'DOTTED_ARROW_OPEN';
54 | \s*\=\=[x]\s* return 'THICK_ARROW_CROSS';
55 | \s*\=\=\>\s* return 'THICK_ARROW_POINT';
56 | \s*\=\=[o]\s* return 'THICK_ARROW_CIRCLE';
57 | \s*\=\=[\=]\s* return 'THICK_ARROW_OPEN';
58 | \s*\-\-\s* return '--';
59 | \s*\-\.\s* return '-.';
60 | \s*\=\=\s* return '==';
61 | "(-" return '(-';
62 | "-)" return '-)';
63 | \- return 'MINUS';
64 | "." return 'DOT';
65 | \+ return 'PLUS';
66 | \% return 'PCT';
67 | "=" return 'EQUALS';
68 | \= return 'EQUALS';
69 | [A-Za-z]+ return 'ALPHA';
70 | [!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
71 | [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
72 | [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
73 | [\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
74 | [\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
75 | [\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|
76 | [\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|
77 | [\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|
78 | [\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|
79 | [\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|
80 | [\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|
81 | [\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|
82 | [\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|
83 | [\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|
84 | [\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|
85 | [\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|
86 | [\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|
87 | [\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|
88 | [\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|
89 | [\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|
90 | [\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|
91 | [\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|
92 | [\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|
93 | [\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|
94 | [\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|
95 | [\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|
96 | [\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|
97 | [\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|
98 | [\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|
99 | [\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|
100 | [\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|
101 | [\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|
102 | [\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|
103 | [\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|
104 | [\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|
105 | [\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|
106 | [\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|
107 | [\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|
108 | [\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|
109 | [\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|
110 | [\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|
111 | [\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|
112 | [\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|
113 | [\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|
114 | [\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|
115 | [\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|
116 | [\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|
117 | [\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|
118 | [\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|
119 | [\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|
120 | [\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|
121 | [\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|
122 | [\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|
123 | [\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|
124 | [\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|
125 | [\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|
126 | [\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|
127 | [\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|
128 | [\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|
129 | [\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
130 | [\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
131 | [\uFFD2-\uFFD7\uFFDA-\uFFDC]
132 | return 'UNICODE_TEXT';
133 | "|" return 'PIPE';
134 | "(" return 'PS';
135 | ")" return 'PE';
136 | "[" return 'SQS';
137 | "]" return 'SQE';
138 | "{" return 'DIAMOND_START'
139 | "}" return 'DIAMOND_STOP'
140 | "\"" return 'QUOTE';
141 | \n+ return 'NEWLINE';
142 | \s return 'SPACE';
143 | <> return 'EOF';
144 |
145 | /lex
146 |
147 | /* operator associations and precedence */
148 |
149 | %left '^'
150 |
151 | %start mermaidDoc
152 |
153 | %% /* language grammar */
154 |
155 | mermaidDoc: graphConfig document;
156 |
157 | document
158 | : /* empty */
159 | { $$ = [];}
160 | | document line
161 | {
162 | if($2 !== []){
163 | $1.push($2);
164 | }
165 | $$=$1;}
166 | ;
167 |
168 | line
169 | : statement
170 | {$$=$1;}
171 | | SEMI
172 | | NEWLINE
173 | | SPACE
174 | | EOF
175 | ;
176 |
177 | graphConfig
178 | : SPACE graphConfig
179 | | NEWLINE graphConfig
180 | | GRAPH SPACE DIR FirstStmtSeperator
181 | { yy.setDirection($3);$$ = $3;}
182 | | GRAPH SPACE TAGEND FirstStmtSeperator
183 | { yy.setDirection("LR");$$ = $3;}
184 | | GRAPH SPACE TAGSTART FirstStmtSeperator
185 | { yy.setDirection("RL");$$ = $3;}
186 | | GRAPH SPACE UP FirstStmtSeperator
187 | { yy.setDirection("BT");$$ = $3;}
188 | | GRAPH SPACE DOWN FirstStmtSeperator
189 | { yy.setDirection("TB");$$ = $3;}
190 | ;
191 |
192 | ending: endToken ending
193 | | endToken
194 | ;
195 |
196 | endToken: NEWLINE | SPACE | EOF;
197 |
198 | FirstStmtSeperator
199 | : SEMI | NEWLINE | spaceList NEWLINE ;
200 |
201 |
202 | spaceListNewline
203 | : SPACE spaceListNewline
204 | | NEWLINE spaceListNewline
205 | | NEWLINE
206 | | SPACE
207 | ;
208 |
209 |
210 | spaceList
211 | : SPACE spaceList
212 | | SPACE
213 | ;
214 |
215 | statement
216 | : verticeStatement separator
217 | {$$=$1}
218 | | styleStatement separator
219 | {$$=[];}
220 | | linkStyleStatement separator
221 | {$$=[];}
222 | | classDefStatement separator
223 | {$$=[];}
224 | | classStatement separator
225 | {$$=[];}
226 | | clickStatement separator
227 | {$$=[];}
228 | | subgraph text separator document end
229 | {$$=yy.addSubGraph($4,$2);}
230 | | subgraph separator document end
231 | {$$=yy.addSubGraph($3,undefined);}
232 | ;
233 |
234 | separator: NEWLINE | SEMI | EOF ;
235 |
236 | verticeStatement:
237 | vertex link vertex
238 | { yy.addLink($1,$3,$2);$$ = [$1,$3];}
239 | | vertex
240 | {$$ = [$1];}
241 | ;
242 |
243 | vertex: alphaNum SQS text SQE
244 | {$$ = $1;yy.addVertex($1,$3,'square');}
245 | | alphaNum SQS text SQE spaceList
246 | {$$ = $1;yy.addVertex($1,$3,'square');}
247 | | alphaNum PS PS text PE PE
248 | {$$ = $1;yy.addVertex($1,$4,'circle');}
249 | | alphaNum PS PS text PE PE spaceList
250 | {$$ = $1;yy.addVertex($1,$4,'circle');}
251 | | alphaNum '(-' text '-)'
252 | {$$ = $1;yy.addVertex($1,$3,'ellipse');}
253 | | alphaNum '(-' text '-)' spaceList
254 | {$$ = $1;yy.addVertex($1,$3,'ellipse');}
255 | | alphaNum PS text PE
256 | {$$ = $1;yy.addVertex($1,$3,'round');}
257 | | alphaNum PS text PE spaceList
258 | {$$ = $1;yy.addVertex($1,$3,'round');}
259 | | alphaNum DIAMOND_START text DIAMOND_STOP
260 | {$$ = $1;yy.addVertex($1,$3,'diamond');}
261 | | alphaNum DIAMOND_START text DIAMOND_STOP spaceList
262 | {$$ = $1;yy.addVertex($1,$3,'diamond');}
263 | | alphaNum TAGEND text SQE
264 | {$$ = $1;yy.addVertex($1,$3,'odd');}
265 | | alphaNum TAGEND text SQE spaceList
266 | {$$ = $1;yy.addVertex($1,$3,'odd');}
267 | /* | alphaNum SQS text TAGSTART
268 | {$$ = $1;yy.addVertex($1,$3,'odd_right');}
269 | | alphaNum SQS text TAGSTART spaceList
270 | {$$ = $1;yy.addVertex($1,$3,'odd_right');} */
271 | | alphaNum
272 | {$$ = $1;yy.addVertex($1);}
273 | | alphaNum spaceList
274 | {$$ = $1;yy.addVertex($1);}
275 | ;
276 |
277 | alphaNum
278 | : alphaNumStatement
279 | {$$=$1;}
280 | | alphaNum alphaNumStatement
281 | {$$=$1+''+$2;}
282 | ;
283 |
284 | alphaNumStatement
285 | : DIR
286 | {$$=$1;}
287 | | alphaNumToken
288 | {$$=$1;}
289 | | DOWN
290 | {$$='v';}
291 | | MINUS
292 | {$$='-';}
293 | ;
294 |
295 | link: linkStatement arrowText
296 | {$1.text = $2;$$ = $1;}
297 | | linkStatement TESTSTR SPACE
298 | {$1.text = $2;$$ = $1;}
299 | | linkStatement arrowText SPACE
300 | {$1.text = $2;$$ = $1;}
301 | | linkStatement
302 | {$$ = $1;}
303 | | '--' text ARROW_POINT
304 | {$$ = {"type":"arrow","stroke":"normal","text":$2};}
305 | | '--' text ARROW_CIRCLE
306 | {$$ = {"type":"arrow_circle","stroke":"normal","text":$2};}
307 | | '--' text ARROW_CROSS
308 | {$$ = {"type":"arrow_cross","stroke":"normal","text":$2};}
309 | | '--' text ARROW_OPEN
310 | {$$ = {"type":"arrow_open","stroke":"normal","text":$2};}
311 | | '-.' text DOTTED_ARROW_POINT
312 | {$$ = {"type":"arrow","stroke":"dotted","text":$2};}
313 | | '-.' text DOTTED_ARROW_CIRCLE
314 | {$$ = {"type":"arrow_circle","stroke":"dotted","text":$2};}
315 | | '-.' text DOTTED_ARROW_CROSS
316 | {$$ = {"type":"arrow_cross","stroke":"dotted","text":$2};}
317 | | '-.' text DOTTED_ARROW_OPEN
318 | {$$ = {"type":"arrow_open","stroke":"dotted","text":$2};}
319 | | '==' text THICK_ARROW_POINT
320 | {$$ = {"type":"arrow","stroke":"thick","text":$2};}
321 | | '==' text THICK_ARROW_CIRCLE
322 | {$$ = {"type":"arrow_circle","stroke":"thick","text":$2};}
323 | | '==' text THICK_ARROW_CROSS
324 | {$$ = {"type":"arrow_cross","stroke":"thick","text":$2};}
325 | | '==' text THICK_ARROW_OPEN
326 | {$$ = {"type":"arrow_open","stroke":"thick","text":$2};}
327 | ;
328 |
329 | linkStatement: ARROW_POINT
330 | {$$ = {"type":"arrow","stroke":"normal"};}
331 | | ARROW_CIRCLE
332 | {$$ = {"type":"arrow_circle","stroke":"normal"};}
333 | | ARROW_CROSS
334 | {$$ = {"type":"arrow_cross","stroke":"normal"};}
335 | | ARROW_OPEN
336 | {$$ = {"type":"arrow_open","stroke":"normal"};}
337 | | DOTTED_ARROW_POINT
338 | {$$ = {"type":"arrow","stroke":"dotted"};}
339 | | DOTTED_ARROW_CIRCLE
340 | {$$ = {"type":"arrow_circle","stroke":"dotted"};}
341 | | DOTTED_ARROW_CROSS
342 | {$$ = {"type":"arrow_cross","stroke":"dotted"};}
343 | | DOTTED_ARROW_OPEN
344 | {$$ = {"type":"arrow_open","stroke":"dotted"};}
345 | | THICK_ARROW_POINT
346 | {$$ = {"type":"arrow","stroke":"thick"};}
347 | | THICK_ARROW_CIRCLE
348 | {$$ = {"type":"arrow_circle","stroke":"thick"};}
349 | | THICK_ARROW_CROSS
350 | {$$ = {"type":"arrow_cross","stroke":"thick"};}
351 | | THICK_ARROW_OPEN
352 | {$$ = {"type":"arrow_open","stroke":"thick"};}
353 | ;
354 |
355 | arrowText:
356 | PIPE text PIPE
357 | {$$ = $2;}
358 | ;
359 |
360 | text: textToken
361 | {$$=$1;}
362 | | text textToken
363 | {$$=$1+''+$2;}
364 | | STR
365 | {$$=$1;}
366 | ;
367 |
368 |
369 |
370 | commentText: commentToken
371 | {$$=$1;}
372 | | commentText commentToken
373 | {$$=$1+''+$2;}
374 | ;
375 |
376 |
377 | keywords
378 | : STYLE | LINKSTYLE | CLASSDEF | CLASS | CLICK | GRAPH | DIR | subgraph | end | DOWN | UP;
379 |
380 |
381 | textNoTags: textNoTagsToken
382 | {$$=$1;}
383 | | textNoTags textNoTagsToken
384 | {$$=$1+''+$2;}
385 | ;
386 |
387 |
388 | classDefStatement:CLASSDEF SPACE DEFAULT SPACE stylesOpt
389 | {$$ = $1;yy.addClass($3,$5);}
390 | | CLASSDEF SPACE alphaNum SPACE stylesOpt
391 | {$$ = $1;yy.addClass($3,$5);}
392 | ;
393 |
394 | classStatement:CLASS SPACE alphaNum SPACE alphaNum
395 | {$$ = $1;yy.setClass($3, $5);}
396 | ;
397 |
398 | clickStatement
399 | : CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined, undefined);}
400 | | CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, undefined, $7) ;}
401 | | CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, undefined);}
402 | | CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, $7 );}
403 | ;
404 |
405 | styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
406 | {$$ = $1;yy.addVertex($3,undefined,undefined,$5);}
407 | | STYLE SPACE HEX SPACE stylesOpt
408 | {$$ = $1;yy.updateLink($3,$5);}
409 | ;
410 |
411 | linkStyleStatement
412 | : LINKSTYLE SPACE DEFAULT SPACE stylesOpt
413 | {$$ = $1;yy.updateLink($3,$5);}
414 | | LINKSTYLE SPACE NUM SPACE stylesOpt
415 | {$$ = $1;yy.updateLink($3,$5);}
416 | | LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
417 | {$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
418 | | LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
419 | {$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
420 | | LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum
421 | {$$ = $1;yy.updateLinkInterpolate($3,$7);}
422 | | LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum
423 | {$$ = $1;yy.updateLinkInterpolate($3,$7);}
424 | ;
425 |
426 | commentStatement: PCT PCT commentText;
427 |
428 | stylesOpt: style
429 | {$$ = [$1]}
430 | | stylesOpt COMMA style
431 | {$1.push($3);$$ = $1;}
432 | ;
433 |
434 | style: styleComponent
435 | |style styleComponent
436 | {$$ = $1 + $2;}
437 | ;
438 |
439 | styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT | STYLE | PCT ;
440 |
441 | /* Token lists */
442 |
443 | commentToken : textToken | graphCodeTokens ;
444 |
445 | textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT;
446 |
447 | textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
448 |
449 | alphaNumToken : ALPHA | PUNCTUATION | UNICODE_TEXT | NUM | COLON | COMMA | PLUS | EQUALS | MULT | DOT | BRKT ;
450 |
451 | graphCodeTokens: PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAG_START | TAG_END | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI ;
452 | %%
453 |
--------------------------------------------------------------------------------