Merge remote-tracking branch 'origin/master' into flow/trapezoid

This commit is contained in:
Adam Wulf 2019-06-25 22:36:13 -05:00
commit 24600355c8
81 changed files with 8414 additions and 4300 deletions

View File

@ -1,5 +0,0 @@
{
"presets": [
"env"
]
}

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ dist/*.js
dist/*.map
yarn-error.log
.npmrc

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"typescript.format.enable": false,
"typescript.reportStyleChecksAsWarnings": false,
"typescript.validate.enable": false,
"javascript.validate.enable": false,
"editor.formatOnSave": false
}

View File

@ -22,6 +22,8 @@
**Merged pull requests:**
- Adding weekend ignore do Gantt [\$314] (https://github.com/knsv/mermaid/issues/314)
- Adding init argument to the global API [\#137](https://github.com/knsv/mermaid/pull/137) ([bollwyvl](https://github.com/bollwyvl))
- Add description of manual calling of init [\#136](https://github.com/knsv/mermaid/pull/136) ([bollwyvl](https://github.com/bollwyvl))

View File

@ -12,6 +12,23 @@ Ever wanted to simplify documentation and avoid heavy tools like Visio when expl
This is why mermaid was born, a simple markdown-like script language for generating charts from text via javascript.
**Mermaid was nomiated and won the JS Open Source Awards (2019) in the catory The most existing use of technology!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintin the project.**
### Are you someone who wants to take an active role in improving mermaid?
Look at the list of areas we need help with:
* Development - help solving issues
* Development - work with the build environment, with JS we keep updating the tools we use
* Development - new diagram types
* Development - Handling Pull Requests
* Test - testing in connection with realeases, regression testing
* Test - verification of fixed issues
* Test - test of pull requests and verification testing
* Release management - more of a PL role, make roadmap for the project, coordinating the work
* Release management - classification and monitoring of incoming issues
If you think lending a hand to one or more of these areas would be fun, please send an email tp knsv@sveido.com!
### Flowchart
@ -35,7 +52,7 @@ sequenceDiagram
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts <br/>prevail...
Note right of John: Rational thoughts <br/>prevail!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
@ -49,6 +66,7 @@ sequenceDiagram
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
@ -147,7 +165,7 @@ As part of this team you would get write access to the repository and would
represent the project when answering questions and issues.
Together we could continue the work with things like:
* adding more typers of diagrams like mindmaps, ert digrams etc
* adding more types of diagrams like mindmaps, ert diagrams etc
* improving existing diagrams
Don't hesitate to contact me if you want to get involved.

3
__mocks__/MERMAID.js Normal file
View File

@ -0,0 +1,3 @@
export const curveBasis = 'basis'
export const curveLinear = 'linear'
export const curveCardinal = 'cardinal'

12
babel.config.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
]
]
}

53
dist/index.html vendored
View File

@ -207,6 +207,35 @@ graph TB
a1-->a2
end
</div>
<div class="mermaid">
graph TB
A
B
subgraph foo[Foo SubGraph]
C
D
end
subgraph bar[Bar SubGraph]
E
F
end
G
A-->B
B-->C
C-->D
B-->D
D-->E
E-->A
E-->F
F-->D
F-->G
B-->G
G-->D
style foo fill:#F99,stroke-width:2px,stroke:#F0F
style bar fill:#999,stroke-width:10px,stroke:#0F0
</div>
<div class="mermaid">
graph LR
456ac9b0d15a8b7f1e71073221059886[1051 AAA fa:fa-check]
@ -239,6 +268,9 @@ class A someclass;
<div class="mermaid">
sequenceDiagram
participant Alice
participant Bob
participant John as John<br/>Second Line
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
@ -266,6 +298,7 @@ gantt
dateFormat YYYY-MM-DD
axisFormat %d/%m
title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
@ -286,6 +319,13 @@ Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Clickable
Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10
Calling a Callback (look at the console log) :cl2, after cl1, 3d
click cl1 href "https://mermaidjs.github.io/"
click cl2 call ganttTestClick("test", test, test)
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page : 20h
@ -318,11 +358,11 @@ merge newbranch
<div class="mermaid">
classDiagram
Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04
Class05 o-- Class06
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
@ -344,6 +384,11 @@ Class08 <--> C2: Cool label
});
</script>
<script>
function ganttTestClick(a, b, c){
console.log("a:", a)
console.log("b:", b)
console.log("c:", c)
}
function testClick(nodeId) {
console.log("clicked", nodeId)
var originalBgColor = document.querySelector('body').style.backgroundColor

41
dist/info.html vendored Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">
</head>
<body>
<div class="mermaid">info
showInfo
</div>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
theme: 'forest',
// themeCSS: '.node rect { fill: red; }',
logLevel: 1,
flowchart: { curve: 'linear' },
gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50 },
// sequenceDiagram: { actorMargin: 300 } // deprecated
});
</script>
<script>
function ganttTestClick(a, b, c){
console.log("a:", a)
console.log("b:", b)
console.log("c:", c)
}
function testClick(nodeId) {
console.log("clicked", nodeId)
var originalBgColor = document.querySelector('body').style.backgroundColor
document.querySelector('body').style.backgroundColor = 'yellow'
setTimeout(function() {
document.querySelector('body').style.backgroundColor = originalBgColor
}, 100)
}
</script>
</body>
</html>

9
e2e/README.md Normal file
View File

@ -0,0 +1,9 @@
# End to end tests
These tests are end to end tests in the sense that they actually render a full diagram in the browser. The purpose of these tests is to simplify handling of merge requests and releases by highlighting possible unexpected side-effects.
Apart from beeing rendered in a browser the tests perform image snapshots of the diagrams. The tests is handled in the same way as regular jest snapshots tests with the difference that an image comparison is performed instead of a comparison of the generated code.
## To run the tests
1. Start the dev server by running **yarn dev**
2. Run yarn e2e to run the tests

26
e2e/helpers/util.js Normal file
View File

@ -0,0 +1,26 @@
/* eslint-env jest */
import { Base64 } from 'js-base64'
export const mermaidUrl = (graphStr, options) => {
const obj = {
code: graphStr,
mermaid: options
}
const objStr = JSON.stringify(obj)
// console.log(Base64)
return 'http://localhost:9000/e2e.html?graph=' + Base64.encodeURI(objStr)
}
export const imgSnapshotTest = async (page, graphStr, options) => {
return new Promise(async resolve => {
const url = mermaidUrl(graphStr, options)
await page.goto(url)
const image = await page.screenshot()
expect(image).toMatchImageSnapshot()
resolve()
})
// page.close()
}

11
e2e/jest.config.js Normal file
View File

@ -0,0 +1,11 @@
// jest.config.js
module.exports = {
// verbose: true,
transform: {
'^.+\\.jsx?$': '../transformer.js'
},
preset: 'jest-puppeteer',
'globalSetup': 'jest-environment-puppeteer/setup',
'globalTeardown': 'jest-environment-puppeteer/teardown',
'testEnvironment': 'jest-environment-puppeteer'
}

View File

@ -0,0 +1,10 @@
import mermaid from '../../dist/mermaid.core'
mermaid.initialize({
theme: 'forest',
gantt: { axisFormatter: [
['%Y-%m-%d', (d) => {
return d.getDay() === 1
}]
] }
})

19
e2e/platform/e2e.html Normal file
View File

@ -0,0 +1,19 @@
<html>
<head>
<script src="/e2e.js"></script>
<link
href="https://fonts.googleapis.com/css?family=Montserrat&display=swap"
rel="stylesheet"
/>
<style></style>
</head>
<body>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
startOnLoad: false,
useMaxWidth: true,
});
</script>
</body>
</html>

37
e2e/platform/viewer.js Normal file
View File

@ -0,0 +1,37 @@
import { Base64 } from 'js-base64'
/**
* ##contentLoaded
* Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and
* calls init for rendering the mermaid diagrams on the page.
*/
const contentLoaded = function () {
let pos = document.location.href.indexOf('?graph=')
if (pos > 0) {
pos = pos + 7
const graphBase64 = document.location.href.substr(pos)
const graphObj = JSON.parse(Base64.decode(graphBase64))
// const graph = 'hello'
console.log(graphObj)
const div = document.createElement('div')
div.id = 'block'
div.className = 'mermaid'
div.innerHTML = graphObj.code
document.getElementsByTagName('body')[0].appendChild(div)
global.mermaid.initialize(graphObj.mermaid)
global.mermaid.init()
}
}
if (typeof document !== 'undefined') {
/*!
* Wait for document loaded before starting the execution
*/
window.addEventListener(
'load',
function () {
contentLoaded()
},
false
)
}

View File

@ -0,0 +1,23 @@
<!doctype html>
<html>
<body>
<div class="mermaid">
graph LR
A-->B
</div>
<div class="mermaid">
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
</div>
<script src="./bundle-test.js" charset="utf-8"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,27 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../helpers/util.js'
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Sequencediagram', () => {
it('should render a simple class diagrams', async () => {
await imgSnapshotTest(page, `
classDiagram
Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
`,
{})
})
})

267
e2e/spec/flowchart.spec.js Normal file
View File

@ -0,0 +1,267 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../helpers/util.js'
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Flowcart', () => {
it('should render a simple flowchart', async () => {
await imgSnapshotTest(page, `graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{})
})
it('should render a simple flowchart with line breaks', async () => {
await imgSnapshotTest(page, `
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me thinksssss<br/>ssssssssssssssssssssss<br/>sssssssssssssssssssssssssss}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
`,
{})
})
it('should render a flowchart full of circles', async () => {
await imgSnapshotTest(page, `
graph LR
47(SAM.CommonFA.FMESummary)-->48(SAM.CommonFA.CommonFAFinanceBudget)
37(SAM.CommonFA.BudgetSubserviceLineVolume)-->48(SAM.CommonFA.CommonFAFinanceBudget)
35(SAM.CommonFA.PopulationFME)-->47(SAM.CommonFA.FMESummary)
41(SAM.CommonFA.MetricCost)-->47(SAM.CommonFA.FMESummary)
44(SAM.CommonFA.MetricOutliers)-->47(SAM.CommonFA.FMESummary)
46(SAM.CommonFA.MetricOpportunity)-->47(SAM.CommonFA.FMESummary)
40(SAM.CommonFA.OPVisits)-->47(SAM.CommonFA.FMESummary)
38(SAM.CommonFA.CommonFAFinanceRefund)-->47(SAM.CommonFA.FMESummary)
43(SAM.CommonFA.CommonFAFinancePicuDays)-->47(SAM.CommonFA.FMESummary)
42(SAM.CommonFA.CommonFAFinanceNurseryDays)-->47(SAM.CommonFA.FMESummary)
45(SAM.CommonFA.MetricPreOpportunity)-->46(SAM.CommonFA.MetricOpportunity)
35(SAM.CommonFA.PopulationFME)-->45(SAM.CommonFA.MetricPreOpportunity)
41(SAM.CommonFA.MetricCost)-->45(SAM.CommonFA.MetricPreOpportunity)
41(SAM.CommonFA.MetricCost)-->44(SAM.CommonFA.MetricOutliers)
39(SAM.CommonFA.ChargeDetails)-->43(SAM.CommonFA.CommonFAFinancePicuDays)
39(SAM.CommonFA.ChargeDetails)-->42(SAM.CommonFA.CommonFAFinanceNurseryDays)
39(SAM.CommonFA.ChargeDetails)-->41(SAM.CommonFA.MetricCost)
39(SAM.CommonFA.ChargeDetails)-->40(SAM.CommonFA.OPVisits)
35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails)
36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails)
`,
{})
})
it('should render a flowchart full of icons', async () => {
await imgSnapshotTest(page, `
graph TD
9e122290_1ec3_e711_8c5a_005056ad0002("fa:fa-creative-commons My System | Test Environment")
82072290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Business Logic Server:Service 1")
db052290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Business Logic Server:Service 2")
4e112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Report Server:Service 1")
30122290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Report Server:Service 2")
5e112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Dedicated Test Business Logic Server:Service 1")
c1112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Dedicated Test Business Logic Server:Service 2")
b7042290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[SupportDb]")
8f102290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[DevelopmentDb]")
0e102290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[TestDb]")
07132290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[SharedReportingDb]")
c7072290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Shared Business Logic Server")
ca122290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Shared Report Server")
68102290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Dedicated Test Business Logic Server")
f4112290_1ec3_e711_8c5a_005056ad0002("fa:fa-database [DBServer\\SharedDbInstance]")
d6072290_1ec3_e711_8c5a_005056ad0002("fa:fa-server DBServer")
71082290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:MSSQLSERVER")
c0102290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:SQLAgent")
9a072290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:SQLBrowser")
1d0a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost1")
200a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost2")
1c0a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost3")
9e122290_1ec3_e711_8c5a_005056ad0002-->82072290_1ec3_e711_8c5a_005056ad0002
9e122290_1ec3_e711_8c5a_005056ad0002-->db052290_1ec3_e711_8c5a_005056ad0002
9e122290_1ec3_e711_8c5a_005056ad0002-->4e112290_1ec3_e711_8c5a_005056ad0002
9e122290_1ec3_e711_8c5a_005056ad0002-->30122290_1ec3_e711_8c5a_005056ad0002
9e122290_1ec3_e711_8c5a_005056ad0002-->5e112290_1ec3_e711_8c5a_005056ad0002
9e122290_1ec3_e711_8c5a_005056ad0002-->c1112290_1ec3_e711_8c5a_005056ad0002
82072290_1ec3_e711_8c5a_005056ad0002-->b7042290_1ec3_e711_8c5a_005056ad0002
82072290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
82072290_1ec3_e711_8c5a_005056ad0002-->0e102290_1ec3_e711_8c5a_005056ad0002
82072290_1ec3_e711_8c5a_005056ad0002-->c7072290_1ec3_e711_8c5a_005056ad0002
db052290_1ec3_e711_8c5a_005056ad0002-->c7072290_1ec3_e711_8c5a_005056ad0002
db052290_1ec3_e711_8c5a_005056ad0002-->82072290_1ec3_e711_8c5a_005056ad0002
4e112290_1ec3_e711_8c5a_005056ad0002-->b7042290_1ec3_e711_8c5a_005056ad0002
4e112290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
4e112290_1ec3_e711_8c5a_005056ad0002-->0e102290_1ec3_e711_8c5a_005056ad0002
4e112290_1ec3_e711_8c5a_005056ad0002-->07132290_1ec3_e711_8c5a_005056ad0002
4e112290_1ec3_e711_8c5a_005056ad0002-->ca122290_1ec3_e711_8c5a_005056ad0002
30122290_1ec3_e711_8c5a_005056ad0002-->ca122290_1ec3_e711_8c5a_005056ad0002
30122290_1ec3_e711_8c5a_005056ad0002-->4e112290_1ec3_e711_8c5a_005056ad0002
5e112290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
5e112290_1ec3_e711_8c5a_005056ad0002-->68102290_1ec3_e711_8c5a_005056ad0002
c1112290_1ec3_e711_8c5a_005056ad0002-->68102290_1ec3_e711_8c5a_005056ad0002
c1112290_1ec3_e711_8c5a_005056ad0002-->5e112290_1ec3_e711_8c5a_005056ad0002
b7042290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
8f102290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
0e102290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
07132290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
c7072290_1ec3_e711_8c5a_005056ad0002-->1d0a2290_1ec3_e711_8c5a_005056ad0002
ca122290_1ec3_e711_8c5a_005056ad0002-->200a2290_1ec3_e711_8c5a_005056ad0002
68102290_1ec3_e711_8c5a_005056ad0002-->1c0a2290_1ec3_e711_8c5a_005056ad0002
f4112290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
f4112290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
f4112290_1ec3_e711_8c5a_005056ad0002-->c0102290_1ec3_e711_8c5a_005056ad0002
f4112290_1ec3_e711_8c5a_005056ad0002-->9a072290_1ec3_e711_8c5a_005056ad0002
d6072290_1ec3_e711_8c5a_005056ad0002-->1c0a2290_1ec3_e711_8c5a_005056ad0002
71082290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
c0102290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
c0102290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
`,
{})
})
it('should render subgraphs', async () => {
await imgSnapshotTest(page, `
graph TB
subgraph One
a1-->a2
end
`,
{})
})
it('should render styled subgraphs', async () => {
await imgSnapshotTest(page, `
graph TB
A
B
subgraph foo[Foo SubGraph]
C
D
end
subgraph bar[Bar SubGraph]
E
F
end
G
A-->B
B-->C
C-->D
B-->D
D-->E
E-->A
E-->F
F-->D
F-->G
B-->G
G-->D
style foo fill:#F99,stroke-width:2px,stroke:#F0F
style bar fill:#999,stroke-width:10px,stroke:#0F0
`,
{})
})
it('should render a flowchart with ling sames and class definitoins', async () => {
await imgSnapshotTest(page, `graph LR
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
提交申请
熊大
"];
class sid-B3655226-6C29-4D00-B685-3D5C734DC7E1 node-executed;
sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A["
负责人审批
强子
"];
class sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A node-executed;
sid-E27C0367-E6D6-497F-9736-3CDC21FDE221["
DBA审批
强子
"];
class sid-E27C0367-E6D6-497F-9736-3CDC21FDE221 node-executed;
sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD["
SA审批
阿美
"];
class sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD node-executed;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7["
主管审批
光头强
"];
class sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7 node-executed;
sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89["
DBA确认
强子
"];
class sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89 node-executed;
sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937["
SA确认
阿美
"];
class sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937 node-executed;
sid-4FC27B48-A6F9-460A-A675-021F5854FE22["
结束
"];
class sid-4FC27B48-A6F9-460A-A675-021F5854FE22 node-executed;
sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E["
SA执行1
强子
"];
class sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E node-executed;
sid-6C2120F3-D940-4958-A067-0903DCE879C4["
SA执行2
强子
"];
class sid-6C2120F3-D940-4958-A067-0903DCE879C4 node-executed;
sid-9180E2A0-5C4B-435F-B42F-0D152470A338["
DBA执行1
强子
"];
class sid-9180E2A0-5C4B-435F-B42F-0D152470A338 node-executed;
sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD["
DBA执行3
强子
"];
class sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD node-executed;
sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756["
DBA执行2
强子
"];
class sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756 node-executed;
sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93["
DBA执行4
强子
"];
class sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93 node-executed;
sid-1897B30A-9C5C-4D5B-B80B-76A038785070["
负责人确认
梁静茹
"];
class sid-1897B30A-9C5C-4D5B-B80B-76A038785070 node-executed;
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1-->sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7;
sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A-->sid-1897B30A-9C5C-4D5B-B80B-76A038785070;
sid-E27C0367-E6D6-497F-9736-3CDC21FDE221-->sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89;
sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD-->sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937;
sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E-->sid-6C2120F3-D940-4958-A067-0903DCE879C4;
sid-9180E2A0-5C4B-435F-B42F-0D152470A338-->sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756;
sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD-->sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93;
sid-6C2120F3-D940-4958-A067-0903DCE879C4-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
sid-1897B30A-9C5C-4D5B-B80B-76A038785070-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937-->sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E;
sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89-->sid-9180E2A0-5C4B-435F-B42F-0D152470A338;
sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89-->sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD;
sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-E27C0367-E6D6-497F-9736-3CDC21FDE221;
sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937-->sid-6C2120F3-D940-4958-A067-0903DCE879C4;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
`,
{})
})
})

42
e2e/spec/gantt.spec.js Normal file
View File

@ -0,0 +1,42 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../helpers/util.js'
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Sequencediagram', () => {
it('should render a gantt chart', async () => {
await imgSnapshotTest(page, `
gantt
dateFormat YYYY-MM-DD
axisFormat %d/%m
title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d
section Documentation
Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page : 20h
Add another diagram to demo page : 48h
`,
{})
})
})

29
e2e/spec/gitGraph.spec.js Normal file
View File

@ -0,0 +1,29 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../helpers/util.js'
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Sequencediagram', () => {
it('should render a simple git graph', async () => {
await imgSnapshotTest(page, `
gitGraph:
options
{
"nodeSpacing": 150,
"nodeRadius": 10
}
end
commit
branch newbranch
checkout newbranch
commit
commit
checkout master
commit
commit
merge newbranch
`,
{})
})
})

15
e2e/spec/info.spec.js Normal file
View File

@ -0,0 +1,15 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../helpers/util.js'
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Sequencediagram', () => {
it('should render a simple info diagrams', async () => {
await imgSnapshotTest(page, `
info
showInfo
`,
{})
})
})

View File

@ -0,0 +1,35 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../helpers/util.js'
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Sequencediagram', () => {
it('should render a simple sequence diagrams', async () => {
await imgSnapshotTest(page, `
sequenceDiagram
participant Alice
participant Bob
participant John as John<br/>Second Line
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{})
})
})

View File

@ -0,0 +1,16 @@
/* eslint-env jest */
const { toMatchImageSnapshot } = require('jest-image-snapshot')
expect.extend({ toMatchImageSnapshot })
describe('Sequencediagram', () => {
it('should render a simple sequence diagrams', async () => {
const url = 'http://localhost:9000/webpackUsage.html'
await page.goto(url)
const image = await page.screenshot()
expect(image).toMatchImageSnapshot()
})
})

9
jest.config.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
transform: {
'^.+\\.jsx?$': './transformer.js'
},
transformIgnorePatterns: ['/node_modules/(?!dagre-d3-renderer/lib).*\\.js'],
moduleNameMapper: {
'\\.(css|scss)$': 'identity-obj-proxy'
}
}

View File

@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "8.0.0-rc.8",
"version": "8.1.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.js",
"keywords": [
@ -15,12 +15,14 @@
"scripts": {
"build": "webpack --progress --colors",
"build:watch": "yarn build --watch",
"minify": "minify ./dist/mermaid.js > ./dist/mermaid.min.js",
"release": "yarn build -p --config webpack.config.prod.babel.js",
"upgrade": "yarn-upgrade-all",
"lint": "standard",
"test": "yarn lint && jest",
"test:watch": "jest --watch",
"jison": "node -r babel-register node_modules/.bin/gulp jison",
"e2e": "yarn lint && jest e2e --config e2e/jest.config.js",
"dev": "yarn lint && webpack-dev-server --config webpack.config.e2e.js",
"test": "yarn lint && jest src",
"test:watch": "jest --watch src",
"jison": "node -r @babel/register node_modules/.bin/gulp jison",
"prepublishOnly": "yarn build && yarn release && yarn test",
"prepush": "yarn test"
},
@ -34,47 +36,59 @@
"ignore": [
"**/parser/*.js",
"dist/**/*.js"
],
"globals": [
"page"
]
},
"dependencies": {
"d3": "^4.13.0",
"d3": "^5.7.0",
"dagre-d3-renderer": "^0.5.8",
"dagre-layout": "^0.8.8",
"graphlibrary": "^2.2.0",
"he": "^1.1.1",
"lodash": "^4.17.5",
"moment": "^2.21.0",
"scope-css": "^1.0.5"
"he": "^1.2.0",
"moment-mini": "^2.22.1",
"lodash": "^4.17.11",
"minify": "^4.1.1",
"scope-css": "^1.2.1"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"coveralls": "^3.0.0",
"css-loader": "^0.28.11",
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.2.0",
"@babel/register": "^7.0.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.4",
"coveralls": "^3.0.2",
"css-loader": "^2.0.1",
"css-to-string-loader": "^0.1.3",
"gulp": "^3.9.1",
"gulp": "^4.0.0",
"gulp-filelog": "^0.4.1",
"gulp-jison": "^1.2.0",
"husky": "^0.14.3",
"husky": "^1.2.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^22.4.2",
"jest": "^23.6.0",
"jest-environment-puppeteer": "^4.2.0",
"jest-image-snapshot": "^2.8.2",
"jest-puppeteer": "^4.2.0",
"jison": "^0.4.18",
"node-sass": "^4.7.2",
"sass-loader": "^6.0.7",
"standard": "^11.0.1",
"webpack": "^4.1.1",
"webpack-cli": "^2.0.12",
"webpack-node-externals": "^1.6.0",
"yarn-upgrade-all": "^0.3.0"
"moment": "^2.23.0",
"node-sass": "^4.11.0",
"puppeteer": "^1.17.0",
"sass-loader": "^7.1.0",
"standard": "^12.0.1",
"webpack": "^4.27.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.4.1",
"webpack-node-externals": "^1.7.2",
"yarn-upgrade-all": "^0.5.0"
},
"files": [
"dist",
"src"
"dist"
],
"jest": {
"moduleNameMapper": {
"\\.(css|scss)$": "identity-obj-proxy"
}
"yarn-upgrade-all": {
"ignore": [
"babel-core"
]
}
}
}

View File

@ -44,17 +44,23 @@ export const addRelation = function (relation) {
relations.push(relation)
}
export const addMembers = function (className, MembersArr) {
export const addMember = function (className, member) {
const theClass = classes[className]
if (typeof MembersArr === 'string') {
if (MembersArr.substr(-1) === ')') {
theClass.methods.push(MembersArr)
if (typeof member === 'string') {
if (member.substr(-1) === ')') {
theClass.methods.push(member)
} else {
theClass.members.push(MembersArr)
theClass.members.push(member)
}
}
}
export const addMembers = function (className, MembersArr) {
if (Array.isArray(MembersArr)) {
MembersArr.forEach(member => addMember(className, member))
}
}
export const cleanupLabel = function (label) {
if (label.substring(0, 1) === ':') {
return label.substr(2).trim()
@ -82,6 +88,7 @@ export default {
getClasses,
getRelations,
addRelation,
addMember,
addMembers,
cleanupLabel,
lineType,

View File

@ -1,9 +1,8 @@
import * as d3 from 'd3'
import dagre from 'dagre-layout'
import graphlib from 'graphlibrary'
import * as d3 from 'd3'
import classDb from './classDb'
import { logger } from '../../logger'
import classDb from './classDb'
import { parser } from './parser/classDiagram'
parser.yy = classDb
@ -34,7 +33,9 @@ const getGraphId = function (label) {
* Setup arrow head and define the marker. The result is appended to the svg.
*/
const insertMarkers = function (elem) {
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'extensionStart')
.attr('class', 'extension')
.attr('refX', 0)
@ -45,7 +46,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 1,7 L18,13 V 1 Z')
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'extensionEnd')
.attr('refX', 19)
.attr('refY', 7)
@ -55,7 +58,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 1,1 V 13 L18,7 Z') // this is actual shape for arrowhead
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'compositionStart')
.attr('class', 'extension')
.attr('refX', 0)
@ -66,7 +71,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'compositionEnd')
.attr('refX', 19)
.attr('refY', 7)
@ -76,7 +83,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'aggregationStart')
.attr('class', 'extension')
.attr('refX', 0)
@ -87,7 +96,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'aggregationEnd')
.attr('refX', 19)
.attr('refY', 7)
@ -97,7 +108,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'dependencyStart')
.attr('class', 'extension')
.attr('refX', 0)
@ -108,7 +121,9 @@ const insertMarkers = function (elem) {
.append('path')
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z')
elem.append('defs').append('marker')
elem
.append('defs')
.append('marker')
.attr('id', 'dependencyEnd')
.attr('refX', 19)
.attr('refY', 7)
@ -120,6 +135,7 @@ const insertMarkers = function (elem) {
}
let edgeCount = 0
let total = 0
const drawEdge = function (elem, path, relation) {
const getRelationType = function (type) {
switch (type) {
@ -134,11 +150,14 @@ const drawEdge = function (elem, path, relation) {
}
}
path.points = path.points.filter(p => !Number.isNaN(p.y))
// The data for our line
const lineData = path.points
// This is the accessor function we talked about above
const lineFunction = d3.line()
const lineFunction = d3
.line()
.x(function (d) {
return d.x
})
@ -147,27 +166,49 @@ const drawEdge = function (elem, path, relation) {
})
.curve(d3.curveBasis)
const svgPath = elem.append('path')
const svgPath = elem
.append('path')
.attr('d', lineFunction(lineData))
.attr('id', 'edge' + edgeCount)
.attr('class', 'relation')
let url = ''
if (conf.arrowMarkerAbsolute) {
url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
url =
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search
url = url.replace(/\(/g, '\\(')
url = url.replace(/\)/g, '\\)')
}
if (relation.relation.type1 !== 'none') {
svgPath.attr('marker-start', 'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')')
svgPath.attr(
'marker-start',
'url(' +
url +
'#' +
getRelationType(relation.relation.type1) +
'Start' +
')'
)
}
if (relation.relation.type2 !== 'none') {
svgPath.attr('marker-end', 'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')')
svgPath.attr(
'marker-end',
'url(' +
url +
'#' +
getRelationType(relation.relation.type2) +
'End' +
')'
)
}
let x, y
const l = path.points.length
if ((l % 2) !== 0) {
if (l % 2 !== 0 && l > 1) {
const p1 = path.points[Math.floor(l / 2)]
const p2 = path.points[Math.ceil(l / 2)]
x = (p1.x + p2.x) / 2
@ -179,9 +220,9 @@ const drawEdge = function (elem, path, relation) {
}
if (typeof relation.title !== 'undefined') {
const g = elem.append('g')
.attr('class', 'classLabel')
const label = g.append('text')
const g = elem.append('g').attr('class', 'classLabel')
const label = g
.append('text')
.attr('class', 'label')
.attr('x', x)
.attr('y', y)
@ -207,7 +248,8 @@ const drawClass = function (elem, classDef) {
logger.info('Rendering class ' + classDef)
const addTspan = function (textEl, txt, isFirst) {
const tSpan = textEl.append('tspan')
const tSpan = textEl
.append('tspan')
.attr('x', conf.padding)
.text(txt)
if (!isFirst) {
@ -215,7 +257,7 @@ const drawClass = function (elem, classDef) {
}
}
const id = 'classId' + classCnt
const id = 'classId' + (classCnt % total)
const classInfo = {
id: id,
label: classDef.id,
@ -223,24 +265,28 @@ const drawClass = function (elem, classDef) {
height: 0
}
const g = elem.append('g')
const g = elem
.append('g')
.attr('id', id)
.attr('class', 'classGroup')
const title = g.append('text')
const title = g
.append('text')
.attr('x', conf.padding)
.attr('y', conf.textHeight + conf.padding)
.text(classDef.id)
const titleHeight = title.node().getBBox().height
const membersLine = g.append('line') // text label for the x axis
const membersLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2)
const members = g.append('text') // text label for the x axis
const members = g
.append('text') // text label for the x axis
.attr('x', conf.padding)
.attr('y', titleHeight + (conf.dividerMargin) + conf.textHeight)
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
.attr('fill', 'white')
.attr('class', 'classText')
@ -252,14 +298,25 @@ const drawClass = function (elem, classDef) {
const membersBox = members.node().getBBox()
const methodsLine = g.append('line') // text label for the x axis
const methodsLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
.attr(
'y1',
conf.padding + titleHeight + conf.dividerMargin + membersBox.height
)
.attr(
'y2',
conf.padding + titleHeight + conf.dividerMargin + membersBox.height
)
const methods = g.append('text') // text label for the x axis
const methods = g
.append('text') // text label for the x axis
.attr('x', conf.padding)
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
.attr(
'y',
titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight
)
.attr('fill', 'white')
.attr('class', 'classText')
@ -307,7 +364,7 @@ export const draw = function (text, id) {
logger.info('Rendering diagram ' + text)
/// / Fetch the default direction, use TD if none was found
const diagram = d3.select(`[id="${id}"]`)
const diagram = d3.select(`[id='${id}']`)
insertMarkers(diagram)
// Layout graph, Create a new directed graph
@ -327,6 +384,7 @@ export const draw = function (text, id) {
const classes = classDb.getClasses()
const keys = Object.keys(classes)
total = keys.length
for (let i = 0; i < keys.length; i++) {
const classDef = classes[keys[i]]
const node = drawClass(diagram, classDef)
@ -339,24 +397,45 @@ export const draw = function (text, id) {
const relations = classDb.getRelations()
relations.forEach(function (relation) {
logger.info('tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation))
g.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), { relation: relation })
logger.info(
'tjoho' +
getGraphId(relation.id1) +
getGraphId(relation.id2) +
JSON.stringify(relation)
)
g.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), {
relation: relation
})
})
dagre.layout(g)
g.nodes().forEach(function (v) {
if (typeof v !== 'undefined') {
if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') {
logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)))
d3.select('#' + v).attr('transform', 'translate(' + (g.node(v).x - (g.node(v).width / 2)) + ',' + (g.node(v).y - (g.node(v).height / 2)) + ' )')
d3.select('#' + v).attr(
'transform',
'translate(' +
(g.node(v).x - g.node(v).width / 2) +
',' +
(g.node(v).y - g.node(v).height / 2) +
' )'
)
}
})
g.edges().forEach(function (e) {
logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e)))
drawEdge(diagram, g.edge(e), g.edge(e).relation)
if (typeof e !== 'undefined' && typeof g.edge(e) !== 'undefined') {
logger.debug(
'Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))
)
drawEdge(diagram, g.edge(e), g.edge(e).relation)
}
})
diagram.attr('height', '100%')
diagram.attr('width', '100%')
diagram.attr('viewBox', '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20))
diagram.attr(
'viewBox',
'0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20)
)
}
export default {

View File

@ -127,6 +127,7 @@ graphConfig
statements
: statement
| statement NEWLINE
| statement NEWLINE statements
;
@ -144,8 +145,8 @@ statement
;
classStatement
: CLASS className
| CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addMembers($2,$4);}
: CLASS className {yy.addClass($2);}
| CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addClass($2);yy.addMembers($2,$4);}
;
members
@ -155,7 +156,7 @@ members
methodStatement
: className {/*console.log('Rel found',$1);*/}
| className LABEL {yy.addMembers($1,yy.cleanupLabel($2));}
| className LABEL {yy.addMember($1,yy.cleanupLabel($2));}
| MEMBER {console.warn('Member',$1);}
| SEPARATOR {/*console.log('sep found',$1);*/}
;

View File

@ -77,91 +77,94 @@ var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"mermaidDoc":3,"graphConfig":4,"CLASS_DIAGRAM":5,"NEWLINE":6,"statements":7,"EOF":8,"statement":9,"className":10,"alphaNumToken":11,"relationStatement":12,"LABEL":13,"classStatement":14,"methodStatement":15,"CLASS":16,"STRUCT_START":17,"members":18,"STRUCT_STOP":19,"MEMBER":20,"SEPARATOR":21,"relation":22,"STR":23,"relationType":24,"lineType":25,"AGGREGATION":26,"EXTENSION":27,"COMPOSITION":28,"DEPENDENCY":29,"LINE":30,"DOTTED_LINE":31,"commentToken":32,"textToken":33,"graphCodeTokens":34,"textNoTagsToken":35,"TAGSTART":36,"TAGEND":37,"==":38,"--":39,"PCT":40,"DEFAULT":41,"SPACE":42,"MINUS":43,"keywords":44,"UNICODE_TEXT":45,"NUM":46,"ALPHA":47,"$accept":0,"$end":1},
terminals_: {2:"error",5:"CLASS_DIAGRAM",6:"NEWLINE",8:"EOF",13:"LABEL",16:"CLASS",17:"STRUCT_START",19:"STRUCT_STOP",20:"MEMBER",21:"SEPARATOR",23:"STR",26:"AGGREGATION",27:"EXTENSION",28:"COMPOSITION",29:"DEPENDENCY",30:"LINE",31:"DOTTED_LINE",34:"graphCodeTokens",36:"TAGSTART",37:"TAGEND",38:"==",39:"--",40:"PCT",41:"DEFAULT",42:"SPACE",43:"MINUS",44:"keywords",45:"UNICODE_TEXT",46:"NUM",47:"ALPHA"},
productions_: [0,[3,1],[4,4],[7,1],[7,3],[10,2],[10,1],[9,1],[9,2],[9,1],[9,1],[14,2],[14,5],[18,1],[18,2],[15,1],[15,2],[15,1],[15,1],[12,3],[12,4],[12,4],[12,5],[22,3],[22,2],[22,2],[22,1],[24,1],[24,1],[24,1],[24,1],[25,1],[25,1],[32,1],[32,1],[33,1],[33,1],[33,1],[33,1],[33,1],[33,1],[33,1],[35,1],[35,1],[35,1],[35,1],[11,1],[11,1],[11,1]],
productions_: [0,[3,1],[4,4],[7,1],[7,2],[7,3],[10,2],[10,1],[9,1],[9,2],[9,1],[9,1],[14,2],[14,5],[18,1],[18,2],[15,1],[15,2],[15,1],[15,1],[12,3],[12,4],[12,4],[12,5],[22,3],[22,2],[22,2],[22,1],[24,1],[24,1],[24,1],[24,1],[25,1],[25,1],[32,1],[32,1],[33,1],[33,1],[33,1],[33,1],[33,1],[33,1],[33,1],[35,1],[35,1],[35,1],[35,1],[11,1],[11,1],[11,1]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */
var $0 = $$.length - 1;
switch (yystate) {
case 5:
case 6:
this.$=$$[$0-1]+$$[$0];
break;
case 6:
case 7:
this.$=$$[$0];
break;
case 7:
case 8:
yy.addRelation($$[$0]);
break;
case 8:
case 9:
$$[$0-1].title = yy.cleanupLabel($$[$0]); yy.addRelation($$[$0-1]);
break;
case 12:
/*console.log($$[$0-3],JSON.stringify($$[$0-1]));*/yy.addMembers($$[$0-3],$$[$0-1]);
yy.addClass($$[$0]);
break;
case 13:
this.$ = [$$[$0]];
/*console.log($$[$0-3],JSON.stringify($$[$0-1]));*/yy.addClass($$[$0-3]);yy.addMembers($$[$0-3],$$[$0-1]);
break;
case 14:
$$[$0].push($$[$0-1]);this.$=$$[$0];
this.$ = [$$[$0]];
break;
case 15:
/*console.log('Rel found',$$[$0]);*/
$$[$0].push($$[$0-1]);this.$=$$[$0];
break;
case 16:
yy.addMembers($$[$0-1],yy.cleanupLabel($$[$0]));
/*console.log('Rel found',$$[$0]);*/
break;
case 17:
console.warn('Member',$$[$0]);
yy.addMember($$[$0-1],yy.cleanupLabel($$[$0]));
break;
case 18:
/*console.log('sep found',$$[$0]);*/
console.warn('Member',$$[$0]);
break;
case 19:
this.$ = {'id1':$$[$0-2],'id2':$$[$0], relation:$$[$0-1], relationTitle1:'none', relationTitle2:'none'};
/*console.log('sep found',$$[$0]);*/
break;
case 20:
this.$ = {id1:$$[$0-3], id2:$$[$0], relation:$$[$0-1], relationTitle1:$$[$0-2], relationTitle2:'none'}
this.$ = {'id1':$$[$0-2],'id2':$$[$0], relation:$$[$0-1], relationTitle1:'none', relationTitle2:'none'};
break;
case 21:
this.$ = {id1:$$[$0-3], id2:$$[$0], relation:$$[$0-2], relationTitle1:'none', relationTitle2:$$[$0-1]};
this.$ = {id1:$$[$0-3], id2:$$[$0], relation:$$[$0-1], relationTitle1:$$[$0-2], relationTitle2:'none'}
break;
case 22:
this.$ = {id1:$$[$0-4], id2:$$[$0], relation:$$[$0-2], relationTitle1:$$[$0-3], relationTitle2:$$[$0-1]}
this.$ = {id1:$$[$0-3], id2:$$[$0], relation:$$[$0-2], relationTitle1:'none', relationTitle2:$$[$0-1]};
break;
case 23:
this.$={type1:$$[$0-2],type2:$$[$0],lineType:$$[$0-1]};
this.$ = {id1:$$[$0-4], id2:$$[$0], relation:$$[$0-2], relationTitle1:$$[$0-3], relationTitle2:$$[$0-1]}
break;
case 24:
this.$={type1:'none',type2:$$[$0],lineType:$$[$0-1]};
this.$={type1:$$[$0-2],type2:$$[$0],lineType:$$[$0-1]};
break;
case 25:
this.$={type1:$$[$0-1],type2:'none',lineType:$$[$0]};
this.$={type1:'none',type2:$$[$0],lineType:$$[$0-1]};
break;
case 26:
this.$={type1:'none',type2:'none',lineType:$$[$0]};
this.$={type1:$$[$0-1],type2:'none',lineType:$$[$0]};
break;
case 27:
this.$=yy.relationType.AGGREGATION;
this.$={type1:'none',type2:'none',lineType:$$[$0]};
break;
case 28:
this.$=yy.relationType.EXTENSION;
this.$=yy.relationType.AGGREGATION;
break;
case 29:
this.$=yy.relationType.COMPOSITION;
this.$=yy.relationType.EXTENSION;
break;
case 30:
this.$=yy.relationType.DEPENDENCY;
this.$=yy.relationType.COMPOSITION;
break;
case 31:
this.$=yy.lineType.LINE;
this.$=yy.relationType.DEPENDENCY;
break;
case 32:
this.$=yy.lineType.LINE;
break;
case 33:
this.$=yy.lineType.DOTTED_LINE;
break;
}
},
table: [{3:1,4:2,5:[1,3]},{1:[3]},{1:[2,1]},{6:[1,4]},{7:5,9:6,10:10,11:14,12:7,14:8,15:9,16:$V0,20:$V1,21:$V2,45:$V3,46:$V4,47:$V5},{8:[1,18]},{6:[1,19],8:[2,3]},o($V6,[2,7],{13:[1,20]}),o($V6,[2,9]),o($V6,[2,10]),o($V6,[2,15],{22:21,24:24,25:25,13:[1,23],23:[1,22],26:$V7,27:$V8,28:$V9,29:$Va,30:$Vb,31:$Vc}),{10:32,11:14,45:$V3,46:$V4,47:$V5},o($V6,[2,17]),o($V6,[2,18]),o($Vd,[2,6],{11:14,10:33,45:$V3,46:$V4,47:$V5}),o($Ve,[2,46]),o($Ve,[2,47]),o($Ve,[2,48]),{1:[2,2]},{7:34,9:6,10:10,11:14,12:7,14:8,15:9,16:$V0,20:$V1,21:$V2,45:$V3,46:$V4,47:$V5},o($V6,[2,8]),{10:35,11:14,23:[1,36],45:$V3,46:$V4,47:$V5},{22:37,24:24,25:25,26:$V7,27:$V8,28:$V9,29:$Va,30:$Vb,31:$Vc},o($V6,[2,16]),{25:38,30:$Vb,31:$Vc},o($Vf,[2,26],{24:39,26:$V7,27:$V8,28:$V9,29:$Va}),o($Vg,[2,27]),o($Vg,[2,28]),o($Vg,[2,29]),o($Vg,[2,30]),o($Vh,[2,31]),o($Vh,[2,32]),o($V6,[2,11],{17:[1,40]}),o($Vd,[2,5]),{8:[2,4]},o($Vi,[2,19]),{10:41,11:14,45:$V3,46:$V4,47:$V5},{10:42,11:14,23:[1,43],45:$V3,46:$V4,47:$V5},o($Vf,[2,25],{24:44,26:$V7,27:$V8,28:$V9,29:$Va}),o($Vf,[2,24]),{18:45,20:$Vj},o($Vi,[2,21]),o($Vi,[2,20]),{10:47,11:14,45:$V3,46:$V4,47:$V5},o($Vf,[2,23]),{19:[1,48]},{18:49,19:[2,13],20:$Vj},o($Vi,[2,22]),o($V6,[2,12]),{19:[2,14]}],
defaultActions: {2:[2,1],18:[2,2],34:[2,4],49:[2,14]},
table: [{3:1,4:2,5:[1,3]},{1:[3]},{1:[2,1]},{6:[1,4]},{7:5,9:6,10:10,11:14,12:7,14:8,15:9,16:$V0,20:$V1,21:$V2,45:$V3,46:$V4,47:$V5},{8:[1,18]},{6:[1,19],8:[2,3]},o($V6,[2,8],{13:[1,20]}),o($V6,[2,10]),o($V6,[2,11]),o($V6,[2,16],{22:21,24:24,25:25,13:[1,23],23:[1,22],26:$V7,27:$V8,28:$V9,29:$Va,30:$Vb,31:$Vc}),{10:32,11:14,45:$V3,46:$V4,47:$V5},o($V6,[2,18]),o($V6,[2,19]),o($Vd,[2,7],{11:14,10:33,45:$V3,46:$V4,47:$V5}),o($Ve,[2,47]),o($Ve,[2,48]),o($Ve,[2,49]),{1:[2,2]},{7:34,8:[2,4],9:6,10:10,11:14,12:7,14:8,15:9,16:$V0,20:$V1,21:$V2,45:$V3,46:$V4,47:$V5},o($V6,[2,9]),{10:35,11:14,23:[1,36],45:$V3,46:$V4,47:$V5},{22:37,24:24,25:25,26:$V7,27:$V8,28:$V9,29:$Va,30:$Vb,31:$Vc},o($V6,[2,17]),{25:38,30:$Vb,31:$Vc},o($Vf,[2,27],{24:39,26:$V7,27:$V8,28:$V9,29:$Va}),o($Vg,[2,28]),o($Vg,[2,29]),o($Vg,[2,30]),o($Vg,[2,31]),o($Vh,[2,32]),o($Vh,[2,33]),o($V6,[2,12],{17:[1,40]}),o($Vd,[2,6]),{8:[2,5]},o($Vi,[2,20]),{10:41,11:14,45:$V3,46:$V4,47:$V5},{10:42,11:14,23:[1,43],45:$V3,46:$V4,47:$V5},o($Vf,[2,26],{24:44,26:$V7,27:$V8,28:$V9,29:$Va}),o($Vf,[2,25]),{18:45,20:$Vj},o($Vi,[2,22]),o($Vi,[2,21]),{10:47,11:14,45:$V3,46:$V4,47:$V5},o($Vf,[2,24]),{19:[1,48]},{18:49,19:[2,14],20:$Vj},o($Vi,[2,23]),o($V6,[2,13]),{19:[2,15]}],
defaultActions: {2:[2,1],18:[2,2],34:[2,5],49:[2,15]},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
this.trace(str);

View File

@ -7,6 +7,7 @@ let vertices = {}
let edges = []
let classes = []
let subGraphs = []
let subGraphLookup = {}
let tooltips = {}
let subCount = 0
let direction
@ -18,8 +19,9 @@ let funs = []
* @param text
* @param type
* @param style
* @param classes
*/
export const addVertex = function (id, text, type, style) {
export const addVertex = function (id, text, type, style, classes) {
let txt
if (typeof id === 'undefined') {
@ -45,9 +47,6 @@ export const addVertex = function (id, text, type, style) {
if (typeof type !== 'undefined') {
vertices[id].type = type
}
if (typeof type !== 'undefined') {
vertices[id].type = type
}
if (typeof style !== 'undefined') {
if (style !== null) {
style.forEach(function (s) {
@ -55,6 +54,13 @@ export const addVertex = function (id, text, type, style) {
})
}
}
if (typeof classes !== 'undefined') {
if (classes !== null) {
classes.forEach(function (s) {
vertices[id].classes.push(s)
})
}
}
}
/**
@ -90,12 +96,14 @@ export const addLink = function (start, end, type, linktext) {
* @param pos
* @param interpolate
*/
export const updateLinkInterpolate = function (pos, interp) {
if (pos === 'default') {
edges.defaultInterpolate = interp
} else {
edges[pos].interpolate = interp
}
export const updateLinkInterpolate = function (positions, interp) {
positions.forEach(function (pos) {
if (pos === 'default') {
edges.defaultInterpolate = interp
} else {
edges[pos].interpolate = interp
}
})
}
/**
@ -103,15 +111,17 @@ export const updateLinkInterpolate = function (pos, interp) {
* @param pos
* @param style
*/
export const updateLink = function (pos, style) {
if (pos === 'default') {
edges.defaultStyle = style
} else {
if (utils.isSubstringInArray('fill', style) === -1) {
style.push('fill:none')
export const updateLink = function (positions, style) {
positions.forEach(function (pos) {
if (pos === 'default') {
edges.defaultStyle = style
} else {
if (utils.isSubstringInArray('fill', style) === -1) {
style.push('fill:none')
}
edges[pos].style = style
}
edges[pos].style = style
}
})
}
export const addClass = function (id, style) {
@ -137,27 +147,28 @@ export const setDirection = function (dir) {
}
/**
* Called by parser when a graph definition is found, stores the direction of the chart.
* @param dir
* Called by parser when a special node is found, e.g. a clickable element.
* @param ids Comma separated list of ids
* @param className Class to add
*/
export const setClass = function (id, className) {
if (id.indexOf(',') > 0) {
id.split(',').forEach(function (id2) {
if (typeof vertices[id2] !== 'undefined') {
vertices[id2].classes.push(className)
}
})
} else {
export const setClass = function (ids, className) {
ids.split(',').forEach(function (id) {
if (typeof vertices[id] !== 'undefined') {
vertices[id].classes.push(className)
}
}
if (typeof subGraphLookup[id] !== 'undefined') {
subGraphLookup[id].classes.push(className)
}
})
}
const setTooltip = function (id, tooltip) {
if (typeof tooltip !== 'undefined') {
tooltips[id] = tooltip
}
const setTooltip = function (ids, tooltip) {
ids.split(',').forEach(function (id) {
if (typeof tooltip !== 'undefined') {
tooltips[id] = tooltip
}
})
}
const setClickFun = function (id, functionName) {
@ -176,43 +187,35 @@ const setClickFun = function (id, functionName) {
}
}
const setLink = function (id, linkStr) {
if (typeof linkStr === 'undefined') {
return
}
if (typeof vertices[id] !== 'undefined') {
funs.push(function (element) {
const elem = d3.select(element).select(`[id="${id}"]`)
if (elem !== null) {
elem.on('click', function () {
window.open(linkStr, 'newTab')
})
}
})
}
/**
* Called by parser when a link is found. Adds the URL to the vertex data.
* @param ids Comma separated list of ids
* @param linkStr URL to create a link for
* @param tooltip Tooltip for the clickable element
*/
export const setLink = function (ids, linkStr, tooltip) {
ids.split(',').forEach(function (id) {
if (typeof vertices[id] !== 'undefined') {
vertices[id].link = linkStr
}
})
setTooltip(ids, tooltip)
setClass(ids, 'clickable')
}
export const getTooltip = function (id) {
return tooltips[id]
}
/**
* Called by parser when a graph definition is found, stores the direction of the chart.
* @param dir
* Called by parser when a click definition is found. Registers an event handler.
* @param ids Comma separated list of ids
* @param functionName Function to be called on click
* @param tooltip Tooltip for the clickable element
*/
export const setClickEvent = function (id, functionName, link, tooltip) {
if (id.indexOf(',') > 0) {
id.split(',').forEach(function (id2) {
setTooltip(id2, tooltip)
setClickFun(id2, functionName)
setLink(id2, link)
setClass(id, 'clickable')
})
} else {
setTooltip(id, tooltip)
setClickFun(id, functionName)
setLink(id, link)
setClass(id, 'clickable')
}
export const setClickEvent = function (ids, functionName, tooltip) {
ids.split(',').forEach(function (id) { setClickFun(id, functionName) })
setTooltip(ids, tooltip)
setClass(ids, 'clickable')
}
export const bindFunctions = function (element) {
@ -297,6 +300,7 @@ export const clear = function () {
funs = []
funs.push(setupToolTips)
subGraphs = []
subGraphLookup = {}
subCount = 0
tooltips = []
}
@ -311,7 +315,7 @@ export const defaultStyle = function () {
/**
* Clears the internal graph db so that a new graph can be parsed.
*/
export const addSubGraph = function (list, title) {
export const addSubGraph = function (id, list, title) {
function uniq (a) {
const prims = { 'boolean': {}, 'number': {}, 'string': {} }
const objs = []
@ -329,10 +333,13 @@ export const addSubGraph = function (list, title) {
nodeList = uniq(nodeList.concat.apply(nodeList, list))
const subGraph = { id: 'subGraph' + subCount, nodes: nodeList, title: title.trim() }
subGraphs.push(subGraph)
id = id || ('subGraph' + subCount)
title = title || ''
subCount = subCount + 1
return subGraph.id
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }
subGraphs.push(subGraph)
subGraphLookup[id] = subGraph
return id
}
const getPosForId = function (id) {
@ -409,6 +416,7 @@ export default {
setClass,
getTooltip,
setClickEvent,
setLink,
bindFunctions,
getDirection,
getVertices,

View File

@ -4,6 +4,7 @@ import * as d3 from 'd3'
import flowDb from './flowDb'
import flow from './parser/flow'
import dagreD3 from 'dagre-d3-renderer'
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js'
import { logger } from '../../logger'
import { interpolateToCurve } from '../../utils'
@ -21,7 +22,8 @@ export const setConf = function (cnf) {
* @param vert Object containing the vertices.
* @param g The graph that is to be drawn.
*/
export const addVertices = function (vert, g) {
export const addVertices = function (vert, g, svgId) {
const svg = d3.select(`[id="${svgId}"]`)
const keys = Object.keys(vert)
const styleFromStyleArr = function (styleStr, arr) {
@ -35,45 +37,41 @@ export const addVertices = function (vert, g) {
return styleStr
}
// Iterate through each item in the vertice object (containing all the vertices found) in the graph definition
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
const vertice = vert[id]
let verticeText
const vertex = vert[id]
/**
* Variable for storing the classes for the vertice
* Variable for storing the classes for the vertex
* @type {string}
*/
let classStr = ''
if (vertice.classes.length > 0) {
classStr = vertice.classes.join(' ')
if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ')
}
/**
* Variable for storing the extracted style for the vertice
* Variable for storing the extracted style for the vertex
* @type {string}
*/
let style = ''
// Create a compound style definition from the style definitions found for the node in the graph definition
style = styleFromStyleArr(style, vertice.styles)
style = styleFromStyleArr(style, vertex.styles)
// Use vertice id as text in the box if no text is provided by the graph definition
if (typeof vertice.text === 'undefined') {
verticeText = vertice.id
} else {
verticeText = vertice.text
}
// Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id
let labelTypeStr = ''
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode
if (conf.htmlLabels) {
labelTypeStr = 'html'
verticeText = verticeText.replace(/fa:fa[\w-]+/g, function (s) {
return '<i class="fa ' + s.substring(3) + '"></i>'
})
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = { label: vertexText.replace(/fa[lrsb]?:fa-[\w-]+/g, s => `<i class='${s.replace(':', ' ')}'></i>`) }
vertexNode = addHtmlLabel(svg, node).node()
vertexNode.parentNode.removeChild(vertexNode)
} else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text')
const rows = verticeText.split(/<br>/)
const rows = vertexText.split(/<br[/]{0,1}>/)
for (let j = 0; j < rows.length; j++) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
@ -83,15 +81,22 @@ export const addVertices = function (vert, g) {
tspan.textContent = rows[j]
svgLabel.appendChild(tspan)
}
vertexNode = svgLabel
}
labelTypeStr = 'svg'
verticeText = svgLabel
// If the node has a link, we wrap it in a SVG link
if (vertex.link) {
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a')
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link)
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener')
link.appendChild(vertexNode)
vertexNode = link
}
let radious = 0
let _shape = ''
// Set the shape based parameters
switch (vertice.type) {
switch (vertex.type) {
case 'round':
radious = 5
_shape = 'rect'
@ -122,14 +127,12 @@ export const addVertices = function (vert, g) {
break
case 'group':
_shape = 'rect'
// Need to create a text node if using svg labels, see #367
verticeText = conf.htmlLabels ? '' : document.createElementNS('http://www.w3.org/2000/svg', 'text')
break
default:
_shape = 'rect'
}
// Add the node
g.setNode(vertice.id, { labelType: labelTypeStr, shape: _shape, label: verticeText, rx: radious, ry: radious, 'class': classStr, style: style, id: vertice.id })
g.setNode(vertex.id, { labelType: 'svg', shape: _shape, label: vertexNode, rx: radious, ry: radious, 'class': classStr, style: style, id: vertex.id })
})
}
@ -201,7 +204,7 @@ export const addEdges = function (edges, g) {
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>'
} else {
edgeData.labelType = 'text'
edgeData.style = 'stroke: #333; stroke-width: 1.5px;fill:none'
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'
edgeData.label = edge.text.replace(/<br>/g, '\n')
}
} else {
@ -270,7 +273,7 @@ export const draw = function (text, id) {
const subGraphs = flowDb.getSubGraphs()
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i]
flowDb.addVertex(subG.id, subG.title, 'group', undefined)
flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes)
}
// Fetch the verices/nodes and edges/links from the parsed graph definition
@ -288,7 +291,7 @@ export const draw = function (text, id) {
g.setParent(subG.nodes[j], subG.id)
}
}
addVertices(vert, g)
addVertices(vert, g, id)
addEdges(edges, g)
// Create the renderer
@ -464,6 +467,7 @@ export const draw = function (text, id) {
// Index nodes
flowDb.indexNodes('subGraph' + i)
// reposition labels
for (i = 0; i < subGraphs.length; i++) {
subG = subGraphs[i]
@ -475,19 +479,9 @@ export const draw = function (text, id) {
const yPos = clusterRects[0].y.baseVal.value
const width = clusterRects[0].width.baseVal.value
const cluster = d3.select(clusterEl[0])
const te = cluster.append('text')
te.attr('x', xPos + width / 2)
te.attr('y', yPos + 14)
te.attr('fill', 'black')
te.attr('stroke', 'none')
const te = cluster.select('.label')
te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`)
te.attr('id', id + 'Text')
te.style('text-anchor', 'middle')
if (typeof subG.title === 'undefined') {
te.text('Undef')
} else {
te.text(subG.title)
}
}
}

View File

@ -227,10 +227,14 @@ statement
{$$=[];}
| clickStatement separator
{$$=[];}
| subgraph text separator document end
{$$=yy.addSubGraph($4,$2);}
| subgraph SPACE alphaNum SQS text SQE separator document end
{$$=yy.addSubGraph($3,$8,$5);}
| subgraph SPACE STR separator document end
{$$=yy.addSubGraph(undefined,$5,$3);}
| subgraph SPACE alphaNum separator document end
{$$=yy.addSubGraph($3,$5,$3);}
| subgraph separator document end
{$$=yy.addSubGraph($3,undefined);}
{$$=yy.addSubGraph(undefined,$3,undefined);}
;
separator: NEWLINE | SEMI | EOF ;
@ -406,10 +410,10 @@ classStatement:CLASS SPACE alphaNum SPACE alphaNum
;
clickStatement
: CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined, undefined);}
| CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, undefined, $7) ;}
| CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, undefined);}
| CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, $7 );}
: CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined);}
| CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, $7) ;}
| CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setLink($3, $5, undefined);}
| CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setLink($3, $5, $7 );}
;
styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
@ -420,21 +424,27 @@ styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
linkStyleStatement
: LINKSTYLE SPACE DEFAULT SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);}
| LINKSTYLE SPACE NUM SPACE stylesOpt
{$$ = $1;yy.updateLink([$3],$5);}
| LINKSTYLE SPACE numList SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
| LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate([$3],$7);yy.updateLink([$3],$9);}
| LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate($3,$7);}
| LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate([$3],$7);}
| LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate($3,$7);}
;
commentStatement: PCT PCT commentText;
numList: NUM
{$$ = [$1]}
| numList COMMA NUM
{$1.push($3);$$ = $1;}
;
stylesOpt: style
{$$ = [$1]}
| stylesOpt COMMA style

File diff suppressed because one or more lines are too long

View File

@ -31,9 +31,34 @@ describe('when parsing ', function () {
expect(subgraph.nodes[0]).toBe('a1')
expect(subgraph.nodes[1]).toBe('a2')
expect(subgraph.title).toBe('One')
expect(subgraph.id).toBe('One')
})
it('should handle angle bracket ' > ' as direction LR', function () {
it('should handle subgraph with multiple words in title', function () {
const res = flow.parser.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend')
const subgraphs = flow.parser.yy.getSubGraphs()
expect(subgraphs.length).toBe(1)
const subgraph = subgraphs[0]
expect(subgraph.nodes.length).toBe(2)
expect(subgraph.nodes[0]).toBe('a1')
expect(subgraph.nodes[1]).toBe('a2')
expect(subgraph.title).toBe('Some Title')
expect(subgraph.id).toBe('subGraph0')
});
it('should handle subgraph with id and title notation', function () {
const res = flow.parser.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend')
const subgraphs = flow.parser.yy.getSubGraphs()
expect(subgraphs.length).toBe(1)
const subgraph = subgraphs[0]
expect(subgraph.nodes.length).toBe(2)
expect(subgraph.nodes[0]).toBe('a1')
expect(subgraph.nodes[1]).toBe('a2')
expect(subgraph.title).toBe('Some Title')
expect(subgraph.id).toBe('some-id')
});
it("should handle angle bracket ' > ' as direction LR", function () {
const res = flow.parser.parse('graph >;A-->B;')
const vert = flow.parser.yy.getVertices()
@ -51,7 +76,7 @@ describe('when parsing ', function () {
expect(edges[0].text).toBe('')
})
it('should handle angle bracket ' < ' as direction RL', function () {
it("should handle angle bracket ' < ' as direction RL", function () {
const res = flow.parser.parse('graph <;A-->B;')
const vert = flow.parser.yy.getVertices()
@ -69,7 +94,7 @@ describe('when parsing ', function () {
expect(edges[0].text).toBe('')
})
it('should handle caret ' ^ ' as direction BT', function () {
it("should handle caret ' ^ ' as direction BT", function () {
const res = flow.parser.parse('graph ^;A-->B;')
const vert = flow.parser.yy.getVertices()
@ -428,6 +453,28 @@ describe('when parsing ', function () {
expect(edges[0].type).toBe('arrow')
})
it('should handle multi-numbered style definitons with more then 1 digit in a row', function () {
const res = flow.parser.parse('graph TD\n' +
'A-->B1\n' +
'A-->B2\n' +
'A-->B3\n' +
'A-->B4\n' +
'A-->B5\n' +
'A-->B6\n' +
'A-->B7\n' +
'A-->B8\n' +
'A-->B9\n' +
'A-->B10\n' +
'A-->B11\n' +
'A-->B12\n' +
'linkStyle 10,11 stroke-width:1px;')
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(edges[0].type).toBe('arrow')
})
it('should handle line interpolation default definitions', function () {
const res = flow.parser.parse('graph TD\n' +
'A-->B\n' +
@ -453,6 +500,19 @@ describe('when parsing ', function () {
expect(edges[1].interpolate).toBe('cardinal')
})
it('should handle line interpolation multi-numbered definitions', function () {
const res = flow.parser.parse('graph TD\n' +
'A-->B\n' +
'A-->C\n' +
'linkStyle 0,1 interpolate basis')
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(edges[0].interpolate).toBe('basis')
expect(edges[1].interpolate).toBe('basis')
})
it('should handle line interpolation default with style', function () {
const res = flow.parser.parse('graph TD\n' +
'A-->B\n' +
@ -478,6 +538,19 @@ describe('when parsing ', function () {
expect(edges[1].interpolate).toBe('cardinal')
})
it('should handle line interpolation multi-numbered with style', function () {
const res = flow.parser.parse('graph TD\n' +
'A-->B\n' +
'A-->C\n' +
'linkStyle 0,1 interpolate basis stroke-width:1px;')
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(edges[0].interpolate).toBe('basis')
expect(edges[1].interpolate).toBe('basis')
})
describe('it should handle interaction, ', function () {
it('it should be possible to use click to a callback', function () {
spyOn(flowDb, 'setClickEvent')
@ -486,7 +559,7 @@ describe('when parsing ', function () {
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', undefined, undefined)
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', undefined)
})
it('it should be possible to use click to a callback with toolip', function () {
@ -496,26 +569,26 @@ describe('when parsing ', function () {
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', undefined, 'tooltip')
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', 'tooltip')
})
it('should handle interaction - click to a link', function () {
spyOn(flowDb, 'setClickEvent')
spyOn(flowDb, 'setLink')
const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html"')
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', undefined, 'click.html', undefined)
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', undefined)
})
it('should handle interaction - click to a link with tooltip', function () {
spyOn(flowDb, 'setClickEvent')
spyOn(flowDb, 'setLink')
const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html" "tooltip"')
const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges()
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', undefined, 'click.html', 'tooltip')
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', 'tooltip')
})
})

View File

@ -1,48 +0,0 @@
/* eslint-env jasmine */
import { parser } from './parser/gantt'
import ganttDb from './ganttDb'
describe('when parsing a gantt diagram it', function () {
beforeEach(function () {
parser.yy = ganttDb
})
it('should handle a dateFormat definition', function () {
const str = 'gantt\ndateFormat yyyy-mm-dd'
parser.parse(str)
})
it('should handle a title definition', function () {
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid'
parser.parse(str)
})
it('should handle a section definition', function () {
const str = 'gantt\n' +
'dateFormat yyyy-mm-dd\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation'
parser.parse(str)
})
/**
* Beslutsflöde inligt nedan. Obs bla bla bla
* ```
* graph TD
* A[Hard pledge] -- text on link -->B(Round edge)
* B --> C{to do or not to do}
* C -->|Too| D[Result one]
* C -->|Doo| E[Result two]
```
* params bapa - a unique bapap
*/
it('should handle a task definition', function () {
const str = 'gantt\n' +
'dateFormat yyyy-mm-dd\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' +
'Design jison grammar:des1, 2014-01-01, 2014-01-04'
parser.parse(str)
})
})

View File

@ -1,17 +1,22 @@
import moment from 'moment'
import moment from 'moment-mini'
import { logger } from '../../logger'
import * as d3 from 'd3'
let dateFormat = ''
let axisFormat = ''
let excludes = []
let title = ''
let sections = []
let tasks = []
let currentSection = ''
const tags = ['active', 'done', 'crit', 'milestone']
let funs = []
export const clear = function () {
sections = []
tasks = []
currentSection = ''
funs = []
title = ''
taskCnt = 0
lastTask = undefined
@ -31,6 +36,10 @@ export const setDateFormat = function (txt) {
dateFormat = txt
}
export const setExcludes = function (txt) {
excludes = txt.toLowerCase().split(/[\s,]+/)
}
export const setTitle = function (txt) {
title = txt
}
@ -58,6 +67,42 @@ export const getTasks = function () {
return tasks
}
const isInvalidDate = function (date, dateFormat, excludes) {
if (date.isoWeekday() >= 6 && excludes.indexOf('weekends') >= 0) {
return true
}
if (excludes.indexOf(date.format('dddd').toLowerCase()) >= 0) {
return true
}
return excludes.indexOf(date.format(dateFormat.trim())) >= 0
}
const checkTaskDates = function (task, dateFormat, excludes) {
if (!excludes.length || task.manualEndTime) return
let startTime = moment(task.startTime, dateFormat, true)
startTime.add(1, 'd')
let endTime = moment(task.endTime, dateFormat, true)
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes)
task.endTime = endTime.toDate()
task.renderEndTime = renderEndTime
}
const fixTaskDates = function (startTime, endTime, dateFormat, excludes) {
let invalid = false
let renderEndTime = null
while (startTime.date() <= endTime.date()) {
if (!invalid) {
renderEndTime = endTime.toDate()
}
invalid = isInvalidDate(startTime, dateFormat, excludes)
if (invalid) {
endTime.add(1, 'd')
}
startTime.add(1, 'd')
}
return renderEndTime
}
const getStartDate = function (prevTime, dateFormat, str) {
str = str.trim()
@ -77,8 +122,9 @@ const getStartDate = function (prevTime, dateFormat, str) {
}
// Check for actual date set
if (moment(str, dateFormat.trim(), true).isValid()) {
return moment(str, dateFormat.trim(), true).toDate()
let mDate = moment(str, dateFormat.trim(), true)
if (mDate.isValid()) {
return mDate.toDate()
} else {
logger.debug('Invalid date:' + str)
logger.debug('With date format:' + dateFormat.trim())
@ -92,8 +138,9 @@ const getEndDate = function (prevTime, dateFormat, str) {
str = str.trim()
// Check for actual date
if (moment(str, dateFormat.trim(), true).isValid()) {
return moment(str, dateFormat.trim()).toDate()
let mDate = moment(str, dateFormat.trim(), true)
if (mDate.isValid()) {
return mDate.toDate()
}
const d = moment(prevTime)
@ -119,7 +166,6 @@ const getEndDate = function (prevTime, dateFormat, str) {
d.add(durationStatement[1], 'weeks')
break
}
return d.toDate()
}
// Default date - now
return d.toDate()
@ -157,49 +203,39 @@ const compileData = function (prevTask, dataStr) {
const task = {}
// Get tags like active, done cand crit
let matchFound = true
while (matchFound) {
matchFound = false
if (data[0].match(/^\s*active\s*$/)) {
task.active = true
data.shift(1)
matchFound = true
}
if (data[0].match(/^\s*done\s*$/)) {
task.done = true
data.shift(1)
matchFound = true
}
if (data[0].match(/^\s*crit\s*$/)) {
task.crit = true
data.shift(1)
matchFound = true
}
}
// Get tags like active, done, crit and milestone
getTaskTags(data, task, tags)
for (let i = 0; i < data.length; i++) {
data[i] = data[i].trim()
}
let endTimeData = ''
switch (data.length) {
case 1:
task.id = parseId()
task.startTime = prevTask.endTime
task.endTime = getEndDate(task.startTime, dateFormat, data[0])
endTimeData = data[0]
break
case 2:
task.id = parseId()
task.startTime = getStartDate(undefined, dateFormat, data[0])
task.endTime = getEndDate(task.startTime, dateFormat, data[1])
endTimeData = data[1]
break
case 3:
task.id = parseId(data[0])
task.startTime = getStartDate(undefined, dateFormat, data[1])
task.endTime = getEndDate(task.startTime, dateFormat, data[2])
endTimeData = data[2]
break
default:
}
if (endTimeData) {
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData)
task.manualEndTime = endTimeData === moment(task.endTime).format(dateFormat.trim())
checkTaskDates(task, dateFormat, excludes)
}
return task
}
@ -215,26 +251,9 @@ const parseData = function (prevTaskId, dataStr) {
const task = {}
// Get tags like active, done cand crit
let matchFound = true
while (matchFound) {
matchFound = false
if (data[0].match(/^\s*active\s*$/)) {
task.active = true
data.shift(1)
matchFound = true
}
if (data[0].match(/^\s*done\s*$/)) {
task.done = true
data.shift(1)
matchFound = true
}
if (data[0].match(/^\s*crit\s*$/)) {
task.crit = true
data.shift(1)
matchFound = true
}
}
// Get tags like active, done, crit and milestone
getTaskTags(data, task, tags)
for (let i = 0; i < data.length; i++) {
data[i] = data[i].trim()
}
@ -242,18 +261,33 @@ const parseData = function (prevTaskId, dataStr) {
switch (data.length) {
case 1:
task.id = parseId()
task.startTime = { type: 'prevTaskEnd', id: prevTaskId }
task.endTime = { data: data[0] }
task.startTime = {
type: 'prevTaskEnd',
id: prevTaskId
}
task.endTime = {
data: data[0]
}
break
case 2:
task.id = parseId()
task.startTime = { type: 'getStartDate', startData: data[0] }
task.endTime = { data: data[1] }
task.startTime = {
type: 'getStartDate',
startData: data[0]
}
task.endTime = {
data: data[1]
}
break
case 3:
task.id = parseId(data[0])
task.startTime = { type: 'getStartDate', startData: data[1] }
task.endTime = { data: data[2] }
task.startTime = {
type: 'getStartDate',
startData: data[1]
}
task.endTime = {
data: data[2]
}
break
default:
}
@ -270,8 +304,11 @@ export const addTask = function (descr, data) {
section: currentSection,
type: currentSection,
processed: false,
manualEndTime: false,
renderEndTime: null,
raw: { data: data },
task: descr
task: descr,
classes: []
}
const taskInfo = parseData(lastTaskID, data)
rawTask.raw.startTime = taskInfo.startTime
@ -281,6 +318,7 @@ export const addTask = function (descr, data) {
rawTask.active = taskInfo.active
rawTask.done = taskInfo.done
rawTask.crit = taskInfo.crit
rawTask.milestone = taskInfo.milestone
const pos = rawTasks.push(rawTask)
@ -299,7 +337,8 @@ export const addTaskOrg = function (descr, data) {
section: currentSection,
type: currentSection,
description: descr,
task: descr
task: descr,
classes: []
}
const taskInfo = compileData(lastTask, data)
newTask.startTime = taskInfo.startTime
@ -308,6 +347,7 @@ export const addTaskOrg = function (descr, data) {
newTask.active = taskInfo.active
newTask.done = taskInfo.done
newTask.crit = taskInfo.crit
newTask.milestone = taskInfo.milestone
lastTask = newTask
tasks.push(newTask)
}
@ -333,6 +373,8 @@ const compileTasks = function () {
rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data)
if (rawTasks[pos].endTime) {
rawTasks[pos].processed = true
rawTasks[pos].manualEndTime = rawTasks[pos].raw.endTime.data === moment(rawTasks[pos].endTime).format(dateFormat.trim())
checkTaskDates(rawTasks[pos], dateFormat, excludes)
}
}
@ -348,6 +390,108 @@ const compileTasks = function () {
return allProcessed
}
/**
* Called by parser when a link is found. Adds the URL to the vertex data.
* @param ids Comma separated list of ids
* @param linkStr URL to create a link for
*/
export const setLink = function (ids, linkStr) {
ids.split(',').forEach(function (id) {
let rawTask = findTaskById(id)
if (typeof rawTask !== 'undefined') {
pushFun(id, () => { window.open(linkStr, '_self') })
}
})
setClass(ids, 'clickable')
}
/**
* Called by parser when a special node is found, e.g. a clickable element.
* @param ids Comma separated list of ids
* @param className Class to add
*/
export const setClass = function (ids, className) {
ids.split(',').forEach(function (id) {
let rawTask = findTaskById(id)
if (typeof rawTask !== 'undefined') {
rawTask.classes.push(className)
}
})
}
const setClickFun = function (id, functionName, functionArgs) {
if (typeof functionName === 'undefined') {
return
}
let argList = []
if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)
for (let i = 0; i < argList.length; i++) {
let item = argList[i].trim()
/* Removes all double quotes at the start and end of an argument */
/* This preserves all starting and ending whitespace inside */
if (item.charAt(0) === '"' && item.charAt(item.length - 1) === '"') {
item = item.substr(1, item.length - 2)
}
argList[i] = item
}
}
let rawTask = findTaskById(id)
if (typeof rawTask !== 'undefined') {
pushFun(id, () => { window[functionName](...argList) })
}
}
/**
* The callbackFunction is executed in a click event bound to the task with the specified id or the task's assigned text
* @param id The task's id
* @param callbackFunction A function to be executed when clicked on the task or the task's text
*/
const pushFun = function (id, callbackFunction) {
funs.push(function (element) {
const elem = d3.select(element).select(`[id="${id}"]`)
if (elem !== null) {
elem.on('click', function () {
callbackFunction()
})
}
})
funs.push(function (element) {
const elem = d3.select(element).select(`[id="${id}-text"]`)
if (elem !== null) {
elem.on('click', function () {
callbackFunction()
})
}
})
}
/**
* Called by parser when a click definition is found. Registers an event handler.
* @param ids Comma separated list of ids
* @param functionName Function to be called on click
* @param functionArgs Function args the function should be called with
*/
export const setClickEvent = function (ids, functionName, functionArgs) {
ids.split(',').forEach(function (id) {
setClickFun(id, functionName, functionArgs)
})
setClass(ids, 'clickable')
}
/**
* Binds all functions previously added to fun (specified through click) to the element
* @param element
*/
export const bindFunctions = function (element) {
funs.forEach(function (fun) {
fun(element)
})
}
export default {
clear,
setDateFormat,
@ -359,5 +503,25 @@ export default {
getTasks,
addTask,
findTaskById,
addTaskOrg
addTaskOrg,
setExcludes,
setClickEvent,
setLink,
bindFunctions
}
function getTaskTags (data, task, tags) {
let matchFound = true
while (matchFound) {
matchFound = false
tags.forEach(function (t) {
const pattern = '^\\s*' + t + '\\s*$'
const regex = new RegExp(pattern)
if (data[0].match(regex)) {
task[t] = true
data.shift(1)
matchFound = true
}
})
}
}

View File

@ -1,5 +1,5 @@
/* eslint-env jasmine */
import moment from 'moment'
import moment from 'moment-mini'
import ganttDb from './ganttDb'
describe('when using the ganttDb', function () {
@ -7,151 +7,48 @@ describe('when using the ganttDb', function () {
ganttDb.clear()
})
it('should handle an fixed dates', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2013-01-12')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle duration (days) instead of fixed date to determine end date', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2d')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-03', 'YYYY-MM-DD').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle duration (hours) instead of fixed date to determine end date', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2h')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-01 2:00', 'YYYY-MM-DD hh:mm').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle duration (minutes) instead of fixed date to determine end date', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2m')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-01 00:02', 'YYYY-MM-DD hh:mm').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle duration (seconds) instead of fixed date to determine end date', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2s')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-01 00:00:02', 'YYYY-MM-DD hh:mm:ss').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle duration (weeks) instead of fixed date to determine end date', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
})
it.each`
testName | section | taskName | taskData | expStartDate | expEndDate | expId | expTask
${'should handle fixed dates'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2013-01-12'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 12)} | ${'id1'} | ${'test1'}
${'should handle duration (days) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2d'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 3)} | ${'id1'} | ${'test1'}
${'should handle duration (hours) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2h'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 1, 2)} | ${'id1'} | ${'test1'}
${'should handle duration (minutes) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2m'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 1, 0, 2)} | ${'id1'} | ${'test1'}
${'should handle duration (seconds) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2s'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 1, 0, 0, 2)} | ${'id1'} | ${'test1'}
${'should handle duration (weeks) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2w'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 15)} | ${'id1'} | ${'test1'}
${'should handle fixed dates without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,2013-01-12'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 12)} | ${'task1'} | ${'test1'}
${'should handle duration instead of a fixed date to determine end date without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,4d'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 5)} | ${'task1'} | ${'test1'}
`('$testName', ({ section, taskName, taskData, expStartDate, expEndDate, expId, expTask }) => {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection(section)
ganttDb.addTask(taskName, taskData)
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(expStartDate)
expect(tasks[0].endTime).toEqual(expEndDate)
expect(tasks[0].id).toEqual(expId)
expect(tasks[0].task).toEqual(expTask)
})
it('should handle relative start date based on id', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
ganttDb.addTask('test2', 'id2,after id1,1d')
it.each`
section | taskName1 | taskName2 | taskData1 | taskData2 | expStartDate2 | expEndDate2 | expId2 | expTask2
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'id2'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id3,1d'} | ${new Date((new Date()).setHours(0, 0, 0, 0))} | ${undefined} | ${'id2'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'task1'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2013-01-26'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 26)} | ${'task1'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2d'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 17)} | ${'task1'} | ${'test2'}
`('$testName', ({ section, taskName1, taskName2, taskData1, taskData2, expStartDate2, expEndDate2, expId2, expTask2 }) => {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection(section)
ganttDb.addTask(taskName1, taskData1)
ganttDb.addTask(taskName2, taskData2)
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(expStartDate2)
if (!expEndDate2 === undefined) {
expect(tasks[1].endTime).toEqual(expEndDate2)
}
expect(tasks[1].id).toEqual(expId2)
expect(tasks[1].task).toEqual(expTask2)
})
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
expect(tasks[1].id).toEqual('id2')
expect(tasks[1].task).toEqual('test2')
})
it('should handle relative start date based on id when id is invalid', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
ganttDb.addTask('test2', 'id2,after id3,1d')
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(new Date((new Date()).setHours(0, 0, 0, 0)))
expect(tasks[1].id).toEqual('id2')
expect(tasks[1].task).toEqual('test2')
})
it('should handle fixed dates without id', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', '2013-01-01,2013-01-12')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate())
expect(tasks[0].id).toEqual('task1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle duration instead of a fixed date to determine end date without id', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', '2013-01-01,4d')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2013-01-05', 'YYYY-MM-DD').toDate())
expect(tasks[0].id).toEqual('task1')
expect(tasks[0].task).toEqual('test1')
})
it('should handle relative start date of a fixed date to determine end date without id', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
ganttDb.addTask('test2', 'after id1,1d')
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
expect(tasks[1].id).toEqual('task1')
expect(tasks[1].task).toEqual('test2')
})
it('should handle a new task with only an end date as definition', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
ganttDb.addTask('test2', '2013-01-26')
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
expect(tasks[1].endTime).toEqual(moment('2013-01-26', 'YYYY-MM-DD').toDate())
expect(tasks[1].id).toEqual('task1')
expect(tasks[1].task).toEqual('test2')
})
it('should handle a new task with only an end date as definition', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
ganttDb.addTask('test2', '2d')
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
expect(tasks[1].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate())
expect(tasks[1].id).toEqual('task1')
expect(tasks[1].task).toEqual('test2')
})
it('should handle relative start date based on id regardless of sections', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1')
@ -162,14 +59,70 @@ describe('when using the ganttDb', function () {
const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate())
expect(tasks[1].endTime).toEqual(moment('2013-01-18', 'YYYY-MM-DD').toDate())
expect(tasks[1].startTime).toEqual(new Date(2013, 0, 17))
expect(tasks[1].endTime).toEqual(new Date(2013, 0, 18))
expect(tasks[1].id).toEqual('id2')
expect(tasks[1].task).toEqual('test2')
expect(tasks[2].id).toEqual('id3')
expect(tasks[2].task).toEqual('test3')
expect(tasks[2].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate())
expect(tasks[2].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate())
expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15))
expect(tasks[2].endTime).toEqual(new Date(2013, 0, 17))
})
it('should ignore weekends', function () {
ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.setExcludes('weekends 2019-02-06,friday')
ganttDb.addSection('weekends skip test')
ganttDb.addTask('test1', 'id1,2019-02-01,1d')
ganttDb.addTask('test2', 'id2,after id1,2d')
ganttDb.addTask('test3', 'id3,after id2,7d')
ganttDb.addTask('test4', 'id4,2019-02-01,2019-02-20') // Fixed endTime
ganttDb.addTask('test5', 'id5,after id4,1d')
ganttDb.addSection('full ending taks on last day')
ganttDb.addTask('test6', 'id6,2019-02-13,2d')
ganttDb.addTask('test7', 'id7,after id6,1d')
const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate())
expect(tasks[0].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate())
expect(tasks[0].renderEndTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate())
expect(tasks[0].id).toEqual('id1')
expect(tasks[0].task).toEqual('test1')
expect(tasks[1].startTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate())
expect(tasks[1].endTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate())
expect(tasks[1].renderEndTime).toEqual(moment('2019-02-06', 'YYYY-MM-DD').toDate())
expect(tasks[1].id).toEqual('id2')
expect(tasks[1].task).toEqual('test2')
expect(tasks[2].startTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate())
expect(tasks[2].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
expect(tasks[2].renderEndTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
expect(tasks[2].id).toEqual('id3')
expect(tasks[2].task).toEqual('test3')
expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate())
expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
expect(tasks[3].renderEndTime).toBeNull() // Fixed end
expect(tasks[3].id).toEqual('id4')
expect(tasks[3].task).toEqual('test4')
expect(tasks[4].startTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
expect(tasks[4].endTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate())
expect(tasks[4].renderEndTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate())
expect(tasks[4].id).toEqual('id5')
expect(tasks[4].task).toEqual('test5')
expect(tasks[5].startTime).toEqual(moment('2019-02-13', 'YYYY-MM-DD').toDate())
expect(tasks[5].endTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate())
expect(tasks[5].renderEndTime).toEqual(moment('2019-02-15', 'YYYY-MM-DD').toDate())
expect(tasks[5].id).toEqual('id6')
expect(tasks[5].task).toEqual('test6')
expect(tasks[6].startTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate())
expect(tasks[6].endTime).toEqual(moment('2019-02-19', 'YYYY-MM-DD').toDate())
expect(tasks[6].id).toEqual('id7')
expect(tasks[6].task).toEqual('test7')
})
})

View File

@ -98,6 +98,7 @@ export const draw = function (text, id) {
}
function drawRects (theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
// Draw background rects covering the entire width of the graph, these form the section rows.
svg.append('g')
.selectAll('rect')
.data(theArray)
@ -120,26 +121,42 @@ export const draw = function (text, id) {
return 'section section0'
})
// Draw the rects representing the tasks
const rectangles = svg.append('g')
.selectAll('rect')
.data(theArray)
.enter()
rectangles.append('rect')
.attr('id', function (d) { return d.id })
.attr('rx', 3)
.attr('ry', 3)
.attr('x', function (d) {
if (d.milestone) {
return timeScale(d.startTime) + theSidePad + (0.5 * (timeScale(d.endTime) - timeScale(d.startTime))) - (0.5 * theBarHeight)
}
return timeScale(d.startTime) + theSidePad
})
.attr('y', function (d, i) {
return i * theGap + theTopPad
})
.attr('width', function (d) {
return (timeScale(d.endTime) - timeScale(d.startTime))
if (d.milestone) {
return theBarHeight
}
return (timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime))
})
.attr('height', theBarHeight)
.attr('transform-origin', function (d, i) {
return (timeScale(d.startTime) + theSidePad + 0.5 * (timeScale(d.endTime) - timeScale(d.startTime))).toString() + 'px ' + (i * theGap + theTopPad + 0.5 * theBarHeight).toString() + 'px'
})
.attr('class', function (d) {
const res = 'task '
const res = 'task'
let classStr = ''
if (d.classes.length > 0) {
classStr = d.classes.join(' ')
}
let secNum = 0
for (let i = 0; i < categories.length; i++) {
@ -148,37 +165,55 @@ export const draw = function (text, id) {
}
}
let taskClass = ''
if (d.active) {
if (d.crit) {
return res + ' activeCrit' + secNum
taskClass += ' activeCrit'
} else {
return res + ' active' + secNum
taskClass = ' active'
}
}
if (d.done) {
} else if (d.done) {
if (d.crit) {
return res + ' doneCrit' + secNum
taskClass = ' doneCrit'
} else {
return res + ' done' + secNum
taskClass = ' done'
}
} else {
if (d.crit) {
taskClass += ' crit'
}
}
if (d.crit) {
return res + ' crit' + secNum
if (taskClass.length === 0) {
taskClass = ' task'
}
return res + ' task' + secNum
if (d.milestone) {
taskClass = ' milestone ' + taskClass
}
taskClass += secNum
taskClass += ' ' + classStr
return res + taskClass
})
// Append task labels
rectangles.append('text')
.text(function (d) {
return d.task
})
.attr('font-size', conf.fontSize)
.attr('x', function (d) {
const startX = timeScale(d.startTime)
const endX = timeScale(d.endTime)
let startX = timeScale(d.startTime)
let endX = timeScale(d.renderEndTime || d.endTime)
if (d.milestone) {
startX += (0.5 * (timeScale(d.endTime) - timeScale(d.startTime))) - (0.5 * theBarHeight)
}
if (d.milestone) {
endX = startX + theBarHeight
}
const textWidth = this.getBBox().width
// Check id text width > width of rectangle
@ -198,8 +233,17 @@ export const draw = function (text, id) {
.attr('text-height', theBarHeight)
.attr('class', function (d) {
const startX = timeScale(d.startTime)
const endX = timeScale(d.endTime)
let endX = timeScale(d.endTime)
if (d.milestone) {
endX = startX + theBarHeight
}
const textWidth = this.getBBox().width
let classStr = ''
if (d.classes.length > 0) {
classStr = d.classes.join(' ')
}
let secNum = 0
for (let i = 0; i < categories.length; i++) {
if (d.type === categories[i]) {
@ -228,15 +272,19 @@ export const draw = function (text, id) {
}
}
if (d.milestone) {
taskType += ' milestoneText'
}
// Check id text width > width of rectangle
if (textWidth > (endX - startX)) {
if (endX + textWidth + 1.5 * conf.leftPadding > w) {
return 'taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType
return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType
} else {
return 'taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType
return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType
}
} else {
return 'taskText taskText' + secNum + ' ' + taskType
return classStr + ' taskText taskText' + secNum + ' ' + taskType
}
})
}

View File

@ -7,27 +7,64 @@
%options case-insensitive
%{
// Pre-lexer code can go here
%}
%x click
%x href
%x callbackname
%x callbackargs
%%
[\n]+ return 'NL';
\s+ /* skip whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"gantt" return 'gantt';
/*
---interactivity command---
'href' adds a link to the specified task. 'href' can only be specified when the
line was introduced with 'click'.
'href "<link>"' attaches the specified link to the task that was specified by 'click'.
*/
"href"[\s]+["] this.begin("href");
<href>["] this.popState();
<href>[^"]* return 'href';
/*
---interactivity command---
'call' adds a callback to the specified task. 'call' can only be specified when
the line was introdcued with 'click'.
'call <callbackname>(<args>)' attaches the function 'callbackname' with the specified
arguments to the task that was specified by 'click'.
Function arguments are optional: 'call <callbackname>()' simply executes 'callbackname' without any arguments.
*/
"call"[\s]+ this.begin("callbackname");
<callbackname>\([\s]*\) this.popState();
<callbackname>\( this.popState(); this.begin("callbackargs");
<callbackname>[^(]* return 'callbackname';
<callbackargs>\) this.popState();
<callbackargs>[^)]* return 'callbackargs';
/*
'click' is the keyword to introduce a line that contains interactivity commands.
'click' must be followed by an existing task-id. All commands are attached to
that id.
'click <id>' can be followed by href or call commands in any desired order
*/
"click"[\s]+ this.begin("click");
<click>[\s\n] this.popState();
<click>[^\s\n]* return 'click';
"gantt" return 'gantt';
"dateFormat"\s[^#\n;]+ return 'dateFormat';
"axisFormat"\s[^#\n;]+ return 'axisFormat';
"excludes"\s[^#\n;]+ return 'excludes';
\d\d\d\d"-"\d\d"-"\d\d return 'date';
"title"\s[^#\n;]+ return 'title';
"section"\s[^#:\n;]+ return 'section';
[^#:\n;]+ return 'taskTxt';
[^#:\n;]+ return 'taskTxt';
":"[^#\n;]+ return 'taskData';
":" return ':';
<<EOF>> return 'EOF';
. return 'INVALID';
":" return ':';
<<EOF>> return 'EOF';
. return 'INVALID';
/lex
@ -54,11 +91,40 @@ line
;
statement
: 'dateFormat' {yy.setDateFormat($1.substr(11));$$=$1.substr(11);}
: 'dateFormat' {yy.setDateFormat($1.substr(11));$$=$1.substr(11);}
| 'axisFormat' {yy.setAxisFormat($1.substr(11));$$=$1.substr(11);}
| title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| taskTxt taskData {yy.addTask($1,$2);$$='task';}
;
| 'excludes' {yy.setExcludes($1.substr(9));$$=$1.substr(9);}
| title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| clickStatement
| taskTxt taskData {yy.addTask($1,$2);$$='task';}
;
%%
/*
click allows any combination of href and call.
*/
clickStatement
: click callbackname {$$ = $1;yy.setClickEvent($1, $2, null);}
| click callbackname callbackargs {$$ = $1;yy.setClickEvent($1, $2, $3);}
| click callbackname href {$$ = $1;yy.setClickEvent($1, $2, null);yy.setLink($1,$3);}
| click callbackname callbackargs href {$$ = $1;yy.setClickEvent($1, $2, $3);yy.setLink($1,$4);}
| click href callbackname {$$ = $1;yy.setClickEvent($1, $3, null);yy.setLink($1,$2);}
| click href callbackname callbackargs {$$ = $1;yy.setClickEvent($1, $3, $4);yy.setLink($1,$2);}
| click href {$$ = $1;yy.setLink($1, $2);}
;
clickStatementDebug
: click callbackname {$$=$1 + ' ' + $2;}
| click callbackname href {$$=$1 + ' ' + $2 + ' ' + $3;}
| click callbackname callbackargs {$$=$1 + ' ' + $2 + ' ' + $3;}
| click callbackname callbackargs href {$$=$1 + ' ' + $2 + ' ' + $3 + ' ' + $4;}
| click href callbackname {$$=$1 + ' ' + $2 + ' ' + $3;}
| click href callbackname callbackargs {$$=$1 + ' ' + $2 + ' ' + $3 + ' ' + $4;}
| click href {$$=$1 + ' ' + $2;}
;%%

View File

@ -72,12 +72,12 @@
}
*/
var parser = (function(){
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[6,8,10,11,12,13,14,15],$V1=[1,9],$V2=[1,10],$V3=[1,11],$V4=[1,12],$V5=[1,13];
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[6,8,10,11,12,13,14,15,17,19],$V1=[1,9],$V2=[1,10],$V3=[1,11],$V4=[1,12],$V5=[1,13],$V6=[1,15],$V7=[1,16];
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"start":3,"gantt":4,"document":5,"EOF":6,"line":7,"SPACE":8,"statement":9,"NL":10,"dateFormat":11,"axisFormat":12,"title":13,"section":14,"taskTxt":15,"taskData":16,"$accept":0,"$end":1},
terminals_: {2:"error",4:"gantt",6:"EOF",8:"SPACE",10:"NL",11:"dateFormat",12:"axisFormat",13:"title",14:"section",15:"taskTxt",16:"taskData"},
productions_: [0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,1],[9,1],[9,1],[9,2]],
symbols_: {"error":2,"start":3,"gantt":4,"document":5,"EOF":6,"line":7,"SPACE":8,"statement":9,"NL":10,"dateFormat":11,"axisFormat":12,"excludes":13,"title":14,"section":15,"clickStatement":16,"taskTxt":17,"taskData":18,"click":19,"callbackname":20,"callbackargs":21,"href":22,"clickStatementDebug":23,"$accept":0,"$end":1},
terminals_: {2:"error",4:"gantt",6:"EOF",8:"SPACE",10:"NL",11:"dateFormat",12:"axisFormat",13:"excludes",14:"title",15:"section",17:"taskTxt",18:"taskData",19:"click",20:"callbackname",21:"callbackargs",22:"href"},
productions_: [0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,2],[16,2],[16,3],[16,3],[16,4],[16,3],[16,4],[16,2],[23,2],[23,3],[23,3],[23,4],[23,3],[23,4],[23,2]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */
@ -105,17 +105,50 @@ case 9:
yy.setAxisFormat($$[$0].substr(11));this.$=$$[$0].substr(11);
break;
case 10:
yy.setTitle($$[$0].substr(6));this.$=$$[$0].substr(6);
yy.setExcludes($$[$0].substr(9));this.$=$$[$0].substr(9);
break;
case 11:
yy.addSection($$[$0].substr(8));this.$=$$[$0].substr(8);
yy.setTitle($$[$0].substr(6));this.$=$$[$0].substr(6);
break;
case 12:
yy.addSection($$[$0].substr(8));this.$=$$[$0].substr(8);
break;
case 14:
yy.addTask($$[$0-1],$$[$0]);this.$='task';
break;
case 15:
this.$ = $$[$0-1];yy.setClickEvent($$[$0-1], $$[$0], null);
break;
case 16:
this.$ = $$[$0-2];yy.setClickEvent($$[$0-2], $$[$0-1], $$[$0]);
break;
case 17:
this.$ = $$[$0-2];yy.setClickEvent($$[$0-2], $$[$0-1], null);yy.setLink($$[$0-2],$$[$0]);
break;
case 18:
this.$ = $$[$0-3];yy.setClickEvent($$[$0-3], $$[$0-2], $$[$0-1]);yy.setLink($$[$0-3],$$[$0]);
break;
case 19:
this.$ = $$[$0-2];yy.setClickEvent($$[$0-2], $$[$0], null);yy.setLink($$[$0-2],$$[$0-1]);
break;
case 20:
this.$ = $$[$0-3];yy.setClickEvent($$[$0-3], $$[$0-1], $$[$0]);yy.setLink($$[$0-3],$$[$0-2]);
break;
case 21:
this.$ = $$[$0-1];yy.setLink($$[$0-1], $$[$0]);
break;
case 22: case 28:
this.$=$$[$0-1] + ' ' + $$[$0];
break;
case 23: case 24: case 26:
this.$=$$[$0-2] + ' ' + $$[$0-1] + ' ' + $$[$0];
break;
case 25: case 27:
this.$=$$[$0-3] + ' ' + $$[$0-2] + ' ' + $$[$0-1] + ' ' + $$[$0];
break;
}
},
table: [{3:1,4:[1,2]},{1:[3]},o($V0,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:$V1,12:$V2,13:$V3,14:$V4,15:$V5},o($V0,[2,7],{1:[2,1]}),o($V0,[2,3]),{9:14,11:$V1,12:$V2,13:$V3,14:$V4,15:$V5},o($V0,[2,5]),o($V0,[2,6]),o($V0,[2,8]),o($V0,[2,9]),o($V0,[2,10]),o($V0,[2,11]),{16:[1,15]},o($V0,[2,4]),o($V0,[2,12])],
table: [{3:1,4:[1,2]},{1:[3]},o($V0,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:$V1,12:$V2,13:$V3,14:$V4,15:$V5,16:14,17:$V6,19:$V7},o($V0,[2,7],{1:[2,1]}),o($V0,[2,3]),{9:17,11:$V1,12:$V2,13:$V3,14:$V4,15:$V5,16:14,17:$V6,19:$V7},o($V0,[2,5]),o($V0,[2,6]),o($V0,[2,8]),o($V0,[2,9]),o($V0,[2,10]),o($V0,[2,11]),o($V0,[2,12]),o($V0,[2,13]),{18:[1,18]},{20:[1,19],22:[1,20]},o($V0,[2,4]),o($V0,[2,14]),o($V0,[2,15],{21:[1,21],22:[1,22]}),o($V0,[2,21],{20:[1,23]}),o($V0,[2,16],{22:[1,24]}),o($V0,[2,17]),o($V0,[2,19],{21:[1,25]}),o($V0,[2,18]),o($V0,[2,20])],
defaultActions: {},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
@ -593,8 +626,6 @@ stateStackSize:function stateStackSize() {
},
options: {"case-insensitive":true},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
// Pre-lexer code can go here
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:return 10;
@ -605,32 +636,58 @@ case 2:/* skip comments */
break;
case 3:/* skip comments */
break;
case 4:return 4;
case 4:this.begin("href");
break;
case 5:return 11;
case 5:this.popState();
break;
case 6:return 12;
case 6:return 22;
break;
case 7:return 'date';
case 7:this.begin("callbackname");
break;
case 8:return 13;
case 8:this.popState();
break;
case 9:return 14;
case 9:this.popState(); this.begin("callbackargs");
break;
case 10:return 15;
case 10:return 20;
break;
case 11:return 16;
case 11:this.popState();
break;
case 12:return ':';
case 12:return 21;
break;
case 13:return 6;
case 13:this.begin("click");
break;
case 14:return 'INVALID';
case 14:this.popState();
break;
case 15:return 19;
break;
case 16:return 4;
break;
case 17:return 11;
break;
case 18:return 12;
break;
case 19:return 13;
break;
case 20:return 'date';
break;
case 21:return 14;
break;
case 22:return 15;
break;
case 23:return 17;
break;
case 24:return 18;
break;
case 25:return ':';
break;
case 26:return 6;
break;
case 27:return 'INVALID';
break;
}
},
rules: [/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"inclusive":true}}
rules: [/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],
conditions: {"callbackargs":{"rules":[11,12],"inclusive":false},"callbackname":{"rules":[8,9,10],"inclusive":false},"href":{"rules":[5,6],"inclusive":false},"click":{"rules":[14,15],"inclusive":false},"INITIAL":{"rules":[0,1,2,3,4,7,13,16,17,18,19,20,21,22,23,24,25,26,27],"inclusive":true}}
});
return lexer;
})();

View File

@ -0,0 +1,91 @@
/* eslint-env jasmine */
/* eslint-disable no-eval */
import { parser } from './gantt'
import ganttDb from '../ganttDb'
describe('when parsing a gantt diagram it', function () {
beforeEach(function () {
parser.yy = ganttDb
parser.yy.clear()
})
it('should handle a dateFormat definition', function () {
const str = 'gantt\ndateFormat yyyy-mm-dd'
parser.parse(str)
})
it('should handle a title definition', function () {
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid'
parser.parse(str)
})
it('should handle an excludes definition', function () {
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid\nexcludes weekdays 2019-02-01'
parser.parse(str)
})
it('should handle a section definition', function () {
const str = 'gantt\n' +
'dateFormat yyyy-mm-dd\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'excludes weekdays 2019-02-01\n' +
'section Documentation'
parser.parse(str)
})
/**
* Beslutsflöde inligt nedan. Obs bla bla bla
* ```
* graph TD
* A[Hard pledge] -- text on link -->B(Round edge)
* B --> C{to do or not to do}
* C -->|Too| D[Result one]
* C -->|Doo| E[Result two]
```
* params bapa - a unique bapap
*/
it('should handle a task definition', function () {
const str = 'gantt\n' +
'dateFormat YYYY-MM-DD\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' +
'Design jison grammar:des1, 2014-01-01, 2014-01-04'
parser.parse(str)
const tasks = parser.yy.getTasks()
expect(tasks[0].startTime).toEqual(new Date(2014, 0, 1))
expect(tasks[0].endTime).toEqual(new Date(2014, 0, 4))
expect(tasks[0].id).toEqual('des1')
expect(tasks[0].task).toEqual('Design jison grammar')
})
it.each`
tags | milestone | done | crit | active
${'milestone'} | ${true} | ${false} | ${false} | ${false}
${'done'} | ${false} | ${true} | ${false} | ${false}
${'crit'} | ${false} | ${false} | ${true} | ${false}
${'active'} | ${false} | ${false} | ${false} | ${true}
${'crit,milestone,done'} | ${true} | ${true} | ${true} | ${false}
`('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => {
const str = 'gantt\n' +
'dateFormat YYYY-MM-DD\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' +
'test task:' + tags + ', 2014-01-01, 2014-01-04'
const allowedTags = ['active', 'done', 'crit', 'milestone']
parser.parse(str)
const tasks = parser.yy.getTasks()
allowedTags.forEach(function (t) {
if (eval(t)) {
expect(tasks[0][t]).toBeTruthy()
} else {
expect(tasks[0][t]).toBeFalsy()
}
})
})
})

View File

@ -155,9 +155,9 @@ function prettyPrintCommitHistory (commitArr) {
}
})
const label = [line, commit.id, commit.seq]
_.each(branches, function (value, key) {
if (value === commit.id) label.push(key)
})
for (let branch in branches) {
if (branches[branch] === commit.id) label.push(branch)
}
logger.debug(label.join(' '))
if (Array.isArray(commit.parent)) {
const newCommit = commits[commit.parent[0]]
@ -188,9 +188,10 @@ export const clear = function () {
}
export const getBranchesAsObjArray = function () {
const branchArr = _.map(branches, function (value, key) {
return { 'name': key, 'commit': commits[value] }
})
const branchArr = []
for (let branch in branches) {
branchArr.push({ name: branch, commit: commits[branches[branch]] })
}
return branchArr
}

View File

@ -1,5 +1,5 @@
import _ from 'lodash'
import * as d3 from 'd3'
import _ from 'lodash'
import db from './gitGraphAst'
import gitGraphParser from './parser/gitGraph'
@ -160,7 +160,7 @@ function cloneNode (svg, selector) {
function renderCommitHistory (svg, commitid, branches, direction) {
let commit
const numCommits = Object.keys(allCommitsDict).length
if (_.isString(commitid)) {
if (typeof commitid === 'string') {
do {
commit = allCommitsDict[commitid]
logger.debug('in renderCommitHistory', commit.id, commit.seq)
@ -189,7 +189,13 @@ function renderCommitHistory (svg, commitid, branches, direction) {
.attr('stroke', config.nodeStrokeColor)
.attr('stroke-width', config.nodeStrokeWidth)
const branch = _.find(branches, ['commit', commit])
let branch
for (let branchName in branches) {
if (branches[branchName].commit === commit) {
branch = branches[branchName]
break
}
}
if (branch) {
logger.debug('found branch ', branch.name)
svg.select('#node-' + commit.id + ' p')
@ -211,7 +217,7 @@ function renderCommitHistory (svg, commitid, branches, direction) {
} while (commitid && allCommitsDict[commitid])
}
if (_.isArray(commitid)) {
if (Array.isArray(commitid)) {
logger.debug('found merge commmit', commitid)
renderCommitHistory(svg, commitid[0], branches, direction)
branchNum++
@ -223,11 +229,11 @@ function renderCommitHistory (svg, commitid, branches, direction) {
function renderLines (svg, commit, direction, branchColor) {
branchColor = branchColor || 0
while (commit.seq > 0 && !commit.lineDrawn) {
if (_.isString(commit.parent)) {
if (typeof commit.parent === 'string') {
svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor)
commit.lineDrawn = true
commit = allCommitsDict[commit.parent]
} else if (_.isArray(commit.parent)) {
} else if (Array.isArray(commit.parent)) {
svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor)
svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1)
renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1)
@ -246,7 +252,7 @@ export const draw = function (txt, id, ver) {
// Parse the graph definition
parser.parse(txt + '\n')
config = _.extend(config, apiConfig, db.getOptions())
config = _.assign(config, apiConfig, db.getOptions())
logger.debug('effective options', config)
const direction = db.getDirection()
allCommitsDict = db.getCommits()
@ -259,11 +265,12 @@ export const draw = function (txt, id, ver) {
const svg = d3.select(`[id="${id}"]`)
svgCreateDefs(svg)
branchNum = 1
_.each(branches, function (v) {
for (let branch in branches) {
const v = branches[branch]
renderCommitHistory(svg, v.commit.id, branches, direction)
renderLines(svg, v.commit, direction)
branchNum++
})
}
svg.attr('height', function () {
if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing
return (branches.length + 1) * config.branchOffset

View File

@ -0,0 +1,15 @@
/* eslint-env jasmine */
describe('when parsing an info graph it', function () {
var ex
beforeEach(function () {
ex = require('./parser/info').parser
ex.yy = require('./infoDb')
})
it('should handle an info definition', function () {
var str = `info
showInfo`
ex.parse(str)
})
})

View File

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

View File

@ -0,0 +1,57 @@
/**
* Created by knut on 14-12-11.
*/
import * as d3 from 'd3'
import db from './infoDb'
import infoParser from './parser/info.js'
import { logger } from '../../logger'
const conf = {
}
export const setConf = function (cnf) {
const keys = Object.keys(cnf)
keys.forEach(function (key) {
conf[key] = cnf[key]
})
}
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
* @param text
* @param id
*/
export const draw = (txt, id, ver) => {
try {
const parser = infoParser.parser
parser.yy = db
logger.debug('Renering info diagram\n' + txt)
// Parse the graph definition
parser.parse(txt)
logger.debug('Parsed info diagram')
// Fetch the default direction, use TD if none was found
const svg = d3.select('#' + id)
const g = svg.append('g')
g.append('text') // text label for the x axis
.attr('x', 100)
.attr('y', 40)
.attr('class', 'version')
.attr('font-size', '32px')
.style('text-anchor', 'middle')
.text('v ' + ver)
svg.attr('height', 100)
svg.attr('width', 400)
// svg.attr('viewBox', '0 0 300 150');
} catch (e) {
logger.error('Error while rendering info diagram')
logger.error(e.message)
}
}
export default {
setConf,
draw
}

View File

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

View File

@ -0,0 +1,621 @@
/* parser generated by jison 0.4.18 */
/*
Returns a Parser object of the following structure:
Parser: {
yy: {}
}
Parser.prototype: {
yy: {},
trace: function(),
symbols_: {associative list: name ==> number},
terminals_: {associative list: number ==> name},
productions_: [...],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
table: [...],
defaultActions: {...},
parseError: function(str, hash),
parse: function(input),
lexer: {
EOF: 1,
parseError: function(str, hash),
setInput: function(input),
input: function(),
unput: function(str),
more: function(),
less: function(n),
pastInput: function(),
upcomingInput: function(),
showPosition: function(),
test_match: function(regex_match_array, rule_index),
next: function(),
lex: function(),
begin: function(condition),
popState: function(),
_currentRules: function(),
topState: function(),
pushState: function(condition),
options: {
ranges: boolean (optional: true ==> token location info will include a .range[] member)
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
},
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
rules: [...],
conditions: {associative list: name ==> set},
}
}
token location info (@$, _$, etc.): {
first_line: n,
last_line: n,
first_column: n,
last_column: n,
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
}
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
text: (matched text)
token: (the produced terminal token, if any)
line: (yylineno)
}
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
loc: (yylloc)
expected: (string describing the set of expected tokens)
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
}
*/
var parser = (function(){
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[6,9,10];
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"start":3,"info":4,"document":5,"EOF":6,"line":7,"statement":8,"NL":9,"showInfo":10,"$accept":0,"$end":1},
terminals_: {2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},
productions_: [0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */
var $0 = $$.length - 1;
switch (yystate) {
case 1:
return yy;
break;
case 4:
break;
case 6:
yy.setInfo(true);
break;
}
},
table: [{3:1,4:[1,2]},{1:[3]},o($V0,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},o($V0,[2,3]),o($V0,[2,4]),o($V0,[2,5]),o($V0,[2,6])],
defaultActions: {4:[2,1]},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
this.trace(str);
} else {
var error = new Error(str);
error.hash = hash;
throw error;
}
},
parse: function parse(input) {
var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
var args = lstack.slice.call(arguments, 1);
var lexer = Object.create(this.lexer);
var sharedState = { yy: {} };
for (var k in this.yy) {
if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
sharedState.yy[k] = this.yy[k];
}
}
lexer.setInput(input, sharedState.yy);
sharedState.yy.lexer = lexer;
sharedState.yy.parser = this;
if (typeof lexer.yylloc == 'undefined') {
lexer.yylloc = {};
}
var yyloc = lexer.yylloc;
lstack.push(yyloc);
var ranges = lexer.options && lexer.options.ranges;
if (typeof sharedState.yy.parseError === 'function') {
this.parseError = sharedState.yy.parseError;
} else {
this.parseError = Object.getPrototypeOf(this).parseError;
}
function popStack(n) {
stack.length = stack.length - 2 * n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
function lex() {
var token;
token = tstack.pop() || lexer.lex() || EOF;
if (typeof token !== 'number') {
if (token instanceof Array) {
tstack = token;
token = tstack.pop();
}
token = self.symbols_[token] || token;
}
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
while (true) {
state = stack[stack.length - 1];
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol === null || typeof symbol == 'undefined') {
symbol = lex();
}
action = table[state] && table[state][symbol];
}
if (typeof action === 'undefined' || !action.length || !action[0]) {
var errStr = '';
expected = [];
for (p in table[state]) {
if (this.terminals_[p] && p > TERROR) {
expected.push('\'' + this.terminals_[p] + '\'');
}
}
if (lexer.showPosition) {
errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
} else {
errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
}
this.parseError(errStr, {
text: lexer.match,
token: this.terminals_[symbol] || symbol,
line: lexer.yylineno,
loc: yyloc,
expected: expected
});
}
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
}
switch (action[0]) {
case 1:
stack.push(symbol);
vstack.push(lexer.yytext);
lstack.push(lexer.yylloc);
stack.push(action[1]);
symbol = null;
if (!preErrorSymbol) {
yyleng = lexer.yyleng;
yytext = lexer.yytext;
yylineno = lexer.yylineno;
yyloc = lexer.yylloc;
if (recovering > 0) {
recovering--;
}
} else {
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2:
len = this.productions_[action[1]][1];
yyval.$ = vstack[vstack.length - len];
yyval._$ = {
first_line: lstack[lstack.length - (len || 1)].first_line,
last_line: lstack[lstack.length - 1].last_line,
first_column: lstack[lstack.length - (len || 1)].first_column,
last_column: lstack[lstack.length - 1].last_column
};
if (ranges) {
yyval._$.range = [
lstack[lstack.length - (len || 1)].range[0],
lstack[lstack.length - 1].range[1]
];
}
r = this.performAction.apply(yyval, [
yytext,
yyleng,
yylineno,
sharedState.yy,
action[1],
vstack,
lstack
].concat(args));
if (typeof r !== 'undefined') {
return r;
}
if (len) {
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
lstack = lstack.slice(0, -1 * len);
}
stack.push(this.productions_[action[1]][0]);
vstack.push(yyval.$);
lstack.push(yyval._$);
newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
stack.push(newState);
break;
case 3:
return true;
}
}
return true;
}};
/* generated by jison-lex 0.3.4 */
var lexer = (function(){
var lexer = ({
EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parser) {
this.yy.parser.parseError(str, hash);
} else {
throw new Error(str);
}
},
// resets the lexer, sets new input
setInput:function (input, yy) {
this.yy = yy || this.yy || {};
this._input = input;
this._more = this._backtrack = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 0
};
if (this.options.ranges) {
this.yylloc.range = [0,0];
}
this.offset = 0;
return this;
},
// consumes and returns one char from the input
input:function () {
var ch = this._input[0];
this.yytext += ch;
this.yyleng++;
this.offset++;
this.match += ch;
this.matched += ch;
var lines = ch.match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno++;
this.yylloc.last_line++;
} else {
this.yylloc.last_column++;
}
if (this.options.ranges) {
this.yylloc.range[1]++;
}
this._input = this._input.slice(1);
return ch;
},
// unshifts one char (or a string) into the input
unput:function (ch) {
var len = ch.length;
var lines = ch.split(/(?:\r\n?|\n)/g);
this._input = ch + this._input;
this.yytext = this.yytext.substr(0, this.yytext.length - len);
//this.yyleng -= len;
this.offset -= len;
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
this.match = this.match.substr(0, this.match.length - 1);
this.matched = this.matched.substr(0, this.matched.length - 1);
if (lines.length - 1) {
this.yylineno -= lines.length - 1;
}
var r = this.yylloc.range;
this.yylloc = {
first_line: this.yylloc.first_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.first_column,
last_column: lines ?
(lines.length === oldLines.length ? this.yylloc.first_column : 0)
+ oldLines[oldLines.length - lines.length].length - lines[0].length :
this.yylloc.first_column - len
};
if (this.options.ranges) {
this.yylloc.range = [r[0], r[0] + this.yyleng - len];
}
this.yyleng = this.yytext.length;
return this;
},
// When called from action, caches matched text and appends it on next action
more:function () {
this._more = true;
return this;
},
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
reject:function () {
if (this.options.backtrack_lexer) {
this._backtrack = true;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
return this;
},
// retain first n characters of the match
less:function (n) {
this.unput(this.match.slice(n));
},
// displays already matched input, i.e. for error messages
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
// displays upcoming input, i.e. for error messages
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
},
// displays the character position where the lexing error occurred, i.e. for error messages
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c + "^";
},
// test the lexed token: return FALSE when not a match, otherwise return token
test_match:function (match, indexed_rule) {
var token,
lines,
backup;
if (this.options.backtrack_lexer) {
// save context
backup = {
yylineno: this.yylineno,
yylloc: {
first_line: this.yylloc.first_line,
last_line: this.last_line,
first_column: this.yylloc.first_column,
last_column: this.yylloc.last_column
},
yytext: this.yytext,
match: this.match,
matches: this.matches,
matched: this.matched,
yyleng: this.yyleng,
offset: this.offset,
_more: this._more,
_input: this._input,
yy: this.yy,
conditionStack: this.conditionStack.slice(0),
done: this.done
};
if (this.options.ranges) {
backup.yylloc.range = this.yylloc.range.slice(0);
}
}
lines = match[0].match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno += lines.length;
}
this.yylloc = {
first_line: this.yylloc.last_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.last_column,
last_column: lines ?
lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
this.yylloc.last_column + match[0].length
};
this.yytext += match[0];
this.match += match[0];
this.matches = match;
this.yyleng = this.yytext.length;
if (this.options.ranges) {
this.yylloc.range = [this.offset, this.offset += this.yyleng];
}
this._more = false;
this._backtrack = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
if (this.done && this._input) {
this.done = false;
}
if (token) {
return token;
} else if (this._backtrack) {
// recover context
for (var k in backup) {
this[k] = backup[k];
}
return false; // rule action called reject() implying the next rule should be tested instead.
}
return false;
},
// return next match in input
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) {
this.done = true;
}
var token,
match,
tempMatch,
index;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i = 0; i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (this.options.backtrack_lexer) {
token = this.test_match(tempMatch, rules[i]);
if (token !== false) {
return token;
} else if (this._backtrack) {
match = false;
continue; // rule action called reject() implying a rule MISmatch.
} else {
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
} else if (!this.options.flex) {
break;
}
}
}
if (match) {
token = this.test_match(match, rules[index]);
if (token !== false) {
return token;
}
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
if (this._input === "") {
return this.EOF;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
},
// return next match that has a token
lex:function lex() {
var r = this.next();
if (r) {
return r;
} else {
return this.lex();
}
},
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
begin:function begin(condition) {
this.conditionStack.push(condition);
},
// pop the previously active lexer condition state off the condition stack
popState:function popState() {
var n = this.conditionStack.length - 1;
if (n > 0) {
return this.conditionStack.pop();
} else {
return this.conditionStack[0];
}
},
// produce the lexer rule set which is active for the currently active lexer condition state
_currentRules:function _currentRules() {
if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
} else {
return this.conditions["INITIAL"].rules;
}
},
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
topState:function topState(n) {
n = this.conditionStack.length - 1 - Math.abs(n || 0);
if (n >= 0) {
return this.conditionStack[n];
} else {
return "INITIAL";
}
},
// alias for begin(condition)
pushState:function pushState(condition) {
this.begin(condition);
},
// return the number of states currently on the stack
stateStackSize:function stateStackSize() {
return this.conditionStack.length;
},
options: {"case-insensitive":true},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
// Pre-lexer code can go here
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:return 4 ;
break;
case 1:return 9 ;
break;
case 2:return 'space';
break;
case 3:return 10;
break;
case 4:return 6 ;
break;
case 5:return 'TXT' ;
break;
}
},
rules: [/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5],"inclusive":true}}
});
return lexer;
})();
parser.lexer = lexer;
function Parser () {
this.yy = {};
}
Parser.prototype = parser;parser.Parser = Parser;
return new Parser;
})();
if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.parser = parser;
exports.Parser = parser.Parser;
exports.parse = function () { return parser.parse.apply(parser, arguments); };
exports.main = function commonjsMain(args) {
if (!args[1]) {
console.log('Usage: '+args[0]+' FILE');
process.exit(1);
}
var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
return exports.parser.parse(source);
};
if (typeof module !== 'undefined' && require.main === module) {
exports.main(process.argv.slice(1));
}
}

View File

@ -17,6 +17,8 @@ const conf = {
width: 150,
// Height of actor boxes
height: 65,
actorFontSize: 14,
actorFontFamily: '"Open-Sans", "sans-serif"',
// Margin around loop boxes
boxMargin: 10,
boxTextMargin: 5,
@ -218,9 +220,13 @@ const drawMessage = function (elem, startx, stopx, verticalPos, msg) {
let line
if (startx === stopx) {
line = g.append('path')
.attr('d', 'M ' + startx + ',' + verticalPos + ' C ' + (startx + 60) + ',' + (verticalPos - 10) + ' ' + (startx + 60) + ',' +
(verticalPos + 30) + ' ' + startx + ',' + (verticalPos + 20))
if (conf.rightAngles) {
line = g.append('path').attr('d', `M ${startx},${verticalPos} H ${startx + (conf.width / 2)} V ${verticalPos + 25} H ${startx}`)
} else {
line = g.append('path')
.attr('d', 'M ' + startx + ',' + verticalPos + ' C ' + (startx + 60) + ',' + (verticalPos - 10) + ' ' + (startx + 60) + ',' +
(verticalPos + 30) + ' ' + startx + ',' + (verticalPos + 20))
}
bounds.bumpVerticalPos(30)
const dx = Math.max(textWidth / 2, 100)
@ -338,7 +344,7 @@ export const draw = function (text, id) {
activationData.starty = verticalPos - 6
verticalPos += 12
}
svgDraw.drawActivation(diagram, activationData, verticalPos, conf)
svgDraw.drawActivation(diagram, activationData, verticalPos, conf, actorActivations(msg.from.actor).length)
bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos)
}

View File

@ -89,7 +89,7 @@ export const drawActor = function (elem, left, verticalPos, description, conf) {
drawRect(g, rect)
_drawTextCandidateFunc(conf)(description, g,
rect.x, rect.y, rect.width, rect.height, { 'class': 'actor' })
rect.x, rect.y, rect.width, rect.height, { 'class': 'actor' }, conf)
}
export const anchorElement = function (elem) {
@ -101,12 +101,12 @@ export const anchorElement = function (elem) {
* @param bounds - activation box bounds
* @param verticalPos - precise y cooridnate of bottom activation box edge
*/
export const drawActivation = function (elem, bounds, verticalPos) {
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
const rect = getNoteRect()
const g = bounds.anchored
rect.x = bounds.startx
rect.y = bounds.starty
rect.fill = '#f4f4f4'
rect.class = 'activation' + (actorActivations % 3) // Will evaluate to 0, 1 or 2
rect.width = bounds.stopx - bounds.startx
rect.height = verticalPos - bounds.starty
drawRect(g, rect)
@ -216,7 +216,7 @@ export const getTextObj = function () {
const txt = {
x: 0,
y: 0,
'fill': 'black',
'fill': undefined,
'text-anchor': 'start',
style: '#666',
width: 100,
@ -252,22 +252,30 @@ const _drawTextCandidateFunc = (function () {
_setTextAttrs(text, textAttrs)
}
function byTspan (content, g, x, y, width, height, textAttrs) {
const text = g.append('text')
.attr('x', x + width / 2).attr('y', y)
.style('text-anchor', 'middle')
text.append('tspan')
.attr('x', x + width / 2).attr('dy', '0')
.text(content)
function byTspan (content, g, x, y, width, height, textAttrs, conf) {
const { actorFontSize, actorFontFamily } = conf
text.attr('y', y + height / 2.0)
.attr('dominant-baseline', 'central')
.attr('alignment-baseline', 'central')
const lines = content.split(/<br\/?>/ig)
for (let i = 0; i < lines.length; i++) {
const dy = (i * actorFontSize) - (actorFontSize * (lines.length - 1) / 2)
const text = g.append('text')
.attr('x', x + width / 2).attr('y', y)
.style('text-anchor', 'middle')
.style('font-size', actorFontSize)
.style('font-family', actorFontFamily)
text.append('tspan')
.attr('x', x + width / 2).attr('dy', dy)
.text(lines[i])
_setTextAttrs(text, textAttrs)
text.attr('y', y + height / 2.0)
.attr('dominant-baseline', 'central')
.attr('alignment-baseline', 'central')
_setTextAttrs(text, textAttrs)
}
}
function byFo (content, g, x, y, width, height, textAttrs) {
function byFo (content, g, x, y, width, height, textAttrs, conf) {
const s = g.append('switch')
const f = s.append('foreignObject')
.attr('x', x).attr('y', y)
@ -280,7 +288,7 @@ const _drawTextCandidateFunc = (function () {
.style('text-align', 'center').style('vertical-align', 'middle')
.text(content)
byTspan(content, s, x, y, width, height, textAttrs)
byTspan(content, s, x, y, width, height, textAttrs, conf)
_setTextAttrs(text, textAttrs)
}

View File

@ -1,4 +1,4 @@
import moment from 'moment'
import moment from 'moment-mini'
export const LEVELS = {
debug: 1,

View File

@ -104,7 +104,7 @@ const init = function () {
}
const initialize = function (config) {
logger.debug('Initializing mermaid')
logger.debug('Initializing mermaid ')
if (typeof config.mermaid !== 'undefined') {
if (typeof config.mermaid.startOnLoad !== 'undefined') {
mermaid.startOnLoad = config.mermaid.startOnLoad

View File

@ -13,6 +13,7 @@
*/
import * as d3 from 'd3'
import scope from 'scope-css'
import pkg from '../package.json'
import { logger, setLogLevel } from './logger'
import utils from './utils'
@ -31,6 +32,9 @@ import classDb from './diagrams/class/classDb'
import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
import gitGraphParser from './diagrams/git/parser/gitGraph'
import gitGraphAst from './diagrams/git/gitGraphAst'
import infoRenderer from './diagrams/info/infoRenderer'
import infoParser from './diagrams/info/parser/info'
import infoDb from './diagrams/info/infoDb'
const themes = {}
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
@ -49,6 +53,17 @@ for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
* ```
*/
const config = {
/** theme , the CSS style sheet
*
* **theme** - Choose one of the built-in themes: default, forest, dark or neutral. To disable any pre-defined mermaid theme, use "null".
* **themeCSS** - Use your own CSS. This overrides **theme**.
*```
* "theme": "forest",
* "themeCSS": ".node rect { fill: red; }"
*```
*/
theme: 'default',
themeCSS: undefined,
@ -153,7 +168,12 @@ const config = {
* **useMaxWidth** - when this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used
*/
useMaxWidth: true
useMaxWidth: true,
/**
* **rightAngles** - this will display arrows that start and begin at the same node as right angles, rather than a curve
*/
rightAngles: false
},
/** ### gantt
@ -220,6 +240,7 @@ function parse (text) {
const graphType = utils.detectType(text)
let parser
logger.debug('Type ' + graphType)
switch (graphType) {
case 'git':
parser = gitGraphParser
@ -241,6 +262,11 @@ function parse (text) {
parser = classParser
parser.parser.yy = classDb
break
case 'info':
logger.debug('info info info')
parser = infoParser
parser.parser.yy = infoDb
break
}
parser.parser.yy.parseError = (str, hash) => {
@ -413,6 +439,11 @@ const render = function (id, txt, cb, container) {
classRenderer.setConf(config.class)
classRenderer.draw(txt, id)
break
case 'info':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
infoRenderer.setConf(config.class)
infoRenderer.draw(txt, id, pkg.version)
break
}
d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')
@ -431,6 +462,7 @@ const render = function (id, txt, cb, container) {
if (typeof cb !== 'undefined') {
cb(svgCode, flowDb.bindFunctions)
cb(svgCode, ganttDb.bindFunctions)
} else {
logger.warn('CB = undefined!')
}
@ -465,7 +497,7 @@ const setConf = function (cnf) {
}
function initialize (options) {
logger.debug('Initializing mermaidAPI')
logger.debug('Initializing mermaidAPI ', pkg.version)
// Update default config with options supplied at initialization
if (typeof options === 'object') {
setConf(options)

View File

@ -27,9 +27,12 @@ $signalColor: $mainContrastColor;
$signalTextColor: $mainContrastColor;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: $mainContrastColor;
$labelTextColor: $darkTextColor;
$loopTextColor: $mainContrastColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
$activationBkgColor: #f4f4f4;
/* Gantt chart variables */
@ -41,6 +44,7 @@ $taskBkgColor: $mainBkg;
$taskTextColor: $darkTextColor;
$taskTextLightColor: $mainContrastColor;
$taskTextOutsideColor: $taskTextLightColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: rgba(255, 255, 255, 0.5);
$activeTaskBkgColor: #81B1DB;
$gridColor: $mainContrastColor;

View File

@ -26,8 +26,11 @@ $signalTextColor: #333;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: $actorTextColor;
$loopTextColor: $actorTextColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
$activationBkgColor: #f4f4f4;
/* Gantt chart variables */
@ -40,6 +43,7 @@ $taskTextLightColor: white;
$taskTextColor: $taskTextLightColor;
$taskTextDarkColor: black;
$taskTextOutsideColor: $taskTextDarkColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: #534fbc;
$activeTaskBkgColor: #bfc7ff;
$gridColor: lightgrey;

View File

@ -30,9 +30,9 @@
}
.cluster rect {
fill: $secondBkg !important;
stroke: $clusterBorder !important;
stroke-width: 1px !important;
fill: $secondBkg;
stroke: $clusterBorder;
stroke-width: 1px;
}
.cluster text {

View File

@ -27,8 +27,11 @@ $signalTextColor: #333;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: #326932;
$labelTextColor: $actorTextColor;
$loopTextColor: $actorTextColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
$activationBkgColor: #f4f4f4;
/* Gantt chart variables */
@ -41,6 +44,7 @@ $taskTextLightColor: white;
$taskTextColor: $taskTextLightColor;
$taskTextDarkColor: black;
$taskTextOutsideColor: $taskTextDarkColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: $taskBorderColor;
$activeTaskBkgColor: $mainBkg;
$gridColor: lightgrey;

View File

@ -66,7 +66,6 @@
/* Task styling */
/* Default task */
.task {
@ -90,6 +89,27 @@
font-size: 11px;
}
/* Special case clickable */
.task.clickable {
cursor: pointer;
}
.taskText.clickable {
cursor: pointer;
fill: $taskTextClickableColor !important;
font-weight: bold;
}
.taskTextOutsideLeft.clickable {
cursor: pointer;
fill: $taskTextClickableColor !important;
font-weight: bold;
}
.taskTextOutsideRight.clickable {
cursor: pointer;
fill: $taskTextClickableColor !important;
font-weight: bold;
}
/* Specific task settings for the sections*/
@ -109,7 +129,7 @@
}
.taskTextOutside0,
.taskTextOutside2,
.taskTextOutside2
{
fill: $taskTextOutsideColor;
}
@ -188,6 +208,13 @@
shape-rendering: crispEdges;
}
.milestone {
transform: rotate(45deg) scale(0.8,0.8);
}
.milestoneText {
font-style: italic;
}
.doneCritText0,
.doneCritText1,
.doneCritText2,

View File

@ -30,9 +30,12 @@ $signalColor: $text;
$signalTextColor: $text;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: white;
$labelTextColor: $text;
$loopTextColor: $text;
$noteBorderColor: darken($note, 60%);
$noteBkgColor: $note;
$activationBorderColor: #666;
$activationBkgColor: #f4f4f4;
/* Gantt chart variables */
@ -45,6 +48,7 @@ $taskTextLightColor: white;
$taskTextColor: $taskTextLightColor;
$taskTextDarkColor: $text;
$taskTextOutsideColor: $taskTextDarkColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: $taskBorderColor;
$activeTaskBkgColor: $mainBkg;
$gridColor: lighten($border1, 30%);

View File

@ -15,7 +15,6 @@ text.actor {
.messageLine0 {
stroke-width: 1.5;
stroke-dasharray: '2 2';
marker-end: 'url(#arrowhead)';
stroke: $signalColor;
}
@ -50,14 +49,13 @@ text.actor {
}
.loopText {
fill: $labelTextColor;
fill: $loopTextColor;
stroke: none;
}
.loopLine {
stroke-width: 2;
stroke-dasharray: '2 2';
marker-end: 'url(#arrowhead)';
stroke: $labelBoxBorderColor;
}
@ -73,3 +71,18 @@ text.actor {
font-family: 'trebuchet ms', verdana, arial;
font-size: 14px;
}
.activation0 {
fill: $activationBkgColor;
stroke: $activationBorderColor;
}
.activation1 {
fill: $activationBkgColor;
stroke: $activationBorderColor;
}
.activation2 {
fill: $activationBkgColor;
stroke: $activationBorderColor;
}

View File

@ -1,4 +1,5 @@
import * as d3 from 'd3'
import { logger } from './logger'
/**
* @function detectType
@ -19,6 +20,7 @@ import * as d3 from 'd3'
*/
export const detectType = function (text) {
text = text.replace(/^\s*%%.*\n/g, '\n')
logger.debug('Detecting diagram type based on the text ' + text)
if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence'
}
@ -34,6 +36,11 @@ export const detectType = function (text) {
if (text.match(/^\s*gitGraph/)) {
return 'git'
}
if (text.match(/^\s*info/)) {
return 'info'
}
return 'flowchart'
}

3
transformer.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = require('babel-jest').createTransformer({
rootMode: 'upward'
})

View File

@ -8,7 +8,10 @@ const amdRule = {
const jsRule = {
test: /\.js$/,
exclude: /node_modules/,
include: [
path.resolve(__dirname, './src'),
path.resolve(__dirname, './node_modules/dagre-d3-renderer/lib')
],
use: {
loader: 'babel-loader'
}

59
webpack.config.e2e.js Normal file
View File

@ -0,0 +1,59 @@
const path = require('path')
const jsRule = {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
const amdRule = {
parser: {
amd: false // https://github.com/lodash/lodash/issues/3052
}
}
const scssRule = {
// load scss to string
test: /\.scss$/,
use: [
{ loader: 'css-to-string-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' }
]
}
module.exports = {
mode: 'development',
target: 'web',
entry: {
mermaid: './src/mermaid.js',
e2e: './e2e/platform/viewer.js',
'bundle-test': './e2e/platform/bundle-test.js'
},
node: {
fs: 'empty' // jison generated code requires 'fs'
},
output: {
path: path.join(__dirname, './dist/'),
filename: '[name].js',
library: 'mermaid',
libraryTarget: 'umd',
libraryExport: 'default'
},
devServer: {
contentBase: [
path.join(__dirname, 'e2e', 'platform'),
path.join(__dirname, 'dist')
],
compress: true,
port: 9000
},
module: {
rules: [amdRule, jsRule, scssRule]
},
externals: {
mermaid: 'mermaid'
},
devtool: 'source-map'
}

9063
yarn.lock

File diff suppressed because it is too large Load Diff